Skip to main content

A Record of Integrating Decap CMS with a Docusaurus Site on Cloud Run

· 9 min read
hiroaki
Individual Developer

To streamline content management for this site, I integrated the headless CMS, Decap CMS (formerly Netlify CMS). This article documents the implementation process, the technical decisions made, and the solutions to problems I encountered.

Ultimately, I settled on a slightly unusual hybrid architecture: the CMS admin panel is managed by Netlify, while the public-facing site is operated on Google Cloud Run.

  • CMS Admin Environment (Netlify)

    • URL: https://[your-site].netlify.app/admin/
    • Role: Provides the Decap CMS admin interface and handles authentication via Netlify Identity.
  • Public Site Environment (Google Cloud Run)

    • URL: https://[your-custom-domain].com/
    • Role: Hosts the static site, maintaining the existing CI/CD pipeline.

I hope this article will be helpful for anyone considering a similar setup. For subsequent operational improvements, see Optimizing the Decap CMS UX by improving config.yml and index.html.

Note that this article is a record from when it was written. For the continuously maintained procedure, see the technical documentation version.

References​

I referred to the following documents during this implementation.

1. Background of the Architecture Selection​

Arriving at the final hybrid architecture involved several technical considerations and some trial and error.

Abandoning the Auth0 Integration Plan​

Initially, I considered using Auth0 as the authentication provider. However, I found that securely implementing the OAuth 2.0 authorization flow would require a separate server-side authentication proxy.

While Netlify hosting provides this proxy functionality internally, I would have had to build and operate it myself in the Google Cloud Run environment, which would complicate the architecture. To maintain the project's simplicity, I decided against this plan.

Redirection Issue with GitHub Integration​

Next, I tried integrating with GitHub using the server-side-less Implicit Grant flow. However, despite specifying GitHub in the configuration file (config.yml), I encountered an issue where logging in would forcibly redirect to Netlify's authentication screen (https://api.netlify.com/auth).

After investigation, I discovered that this was due to a design specification in Decap CMS: when it detects that it's running on a Netlify domain (.netlify.app), it prioritizes using Netlify Identity over the settings in the configuration file.

Deciding on the Hybrid Architecture​

I decided to turn this specification from a problem into an advantage. It allows me to use Netlify's robust authentication infrastructure (Netlify Identity) for free, elegantly solving the initial authentication challenge. Thus, I ultimately adopted a hybrid architecture where only the CMS management functions are handled by Netlify.


2. Detailed Implementation Steps​

From here, I will explain the specific configuration steps to achieve the final hybrid architecture.

Step 1: Netlify-Side Configuration​

  1. Enable Identity: Open the Identity tab in your Netlify project and click Enable Identity.
  2. Configure authentication providers: Under Identity > Authentication providers, enable GitHub.
  3. Enable Git Gateway: Under Identity > Services, click Enable Git Gateway. This is crucial for integrating with your repository.
  4. Invite admin users: From the Identity tab, use Invite users to send an invitation to the email address associated with your GitHub account, and then accept it.
  5. Remove custom domains: Open Domain management and remove any custom domains that are not directly hosted by this Netlify project.

Step 2: Repository-Side Configuration​

  1. Create directories and files: Run the following commands at the root of your project to create the necessary directories and files for the CMS.

    mkdir -p static/admin
    touch static/admin/index.html static/admin/config.yml
  2. Configure static/admin/index.html: This file is the entry point for the Decap CMS admin panel. The following two script imports are the core of the system:

    • netlify-identity-widget.js: A script to enable Netlify's authentication functionality.
    • decap-cms.js: The main Decap CMS script that loads and builds the admin UI.

    Add the following content to static/admin/index.html.

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=3.0, user-scalable=yes" />
    <title>Content Manager</title>
    <style>
    @media (max-width:799px){ body { padding: 0 !important; } .app-header { display: none !important; } .css-1hvr85l-App-Styles-AppMainView-AppMainView { padding: 0 10px !important; } }
    body { margin: 0; padding: 0 15px; background-color: #f9f9f9; }
    </style>
    <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
    </head>
    <body>
    <script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
    <script>
    (function() {
    function preventPullToRefresh() {
    let startY = 0;
    document.body.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; }, { passive: true });
    document.body.addEventListener('touchmove', (e) => {
    const y = e.touches[0].clientY;
    if (document.scrollingElement.scrollTop === 0 && y > startY && document.body.scrollHeight === document.body.clientHeight) {
    e.preventDefault();
    }
    }, { passive: false });
    }
    if (window.netlifyIdentity) {
    window.netlifyIdentity.on("logout", () => { document.location.href = "/admin/"; });
    }
    window.addEventListener('load', preventPullToRefresh);
    })();
    </script>
    </body>
    </html>
  3. Configure static/admin/config.yml: This is the most crucial configuration file that defines how Decap CMS operates. It specifies 'what' to save, 'where' to save it, and 'how' to save it.

    • backend: Defines the authentication method and repository connection info. Here you specify the git-gateway method, which is the core of this setup, and the target branch.
    • media_folder: The path within the repository where media files like images will be uploaded.
    • collections: Defines the sets of content to be managed (e.g., types of articles).
      • folder: The directory where article files will be saved.
      • fields: The most important part, defining the input forms (title, body, tags, etc.) displayed on the CMS editor screen.

    Add the following content to static/admin/config.yml.

    backend:
    name: git-gateway
    branch: main
    commit_messages:
    create: 'docs(diary): add new entry "{{slug}}"'
    update: 'docs(diary): revise entry "{{slug}}"'
    delete: 'docs(diary): remove entry "{{slug}}"'
    uploadMedia: 'docs(assets): add media "{{path}}"'
    deleteMedia: 'docs(assets): remove media "{{path}}"'

    media_folder: "static/img/uploads/diary"
    public_folder: "/img/uploads/diary"

    collections:
    - name: "diary"
    label: "Diary"
    folder: "diary"
    create: true
    slug: "{{year}}-{{month}}-{{day}}"
    editor:
    preview: true
    fields:
    - { label: "Title", name: "title", widget: "string" }
    - { label: "Authors", name: "authors", widget: "hidden", default: ["hk"] }
    - { label: "Hide Table of Contents", name: "hide_table_of_contents", widget: "boolean", default: false }
    - { label: "Tags", name: "tags", widget: "list", default: [] }
    - { label: "Body", name: "body", widget: "markdown" }

Step 3: Deployment and Usage​

  1. Commit and push the files you created and edited to your Git repository.

  2. A deployment will automatically start on Netlify.

  3. After the deployment is complete, access the CMS admin panel in your browser: https://[your-site-name].netlify.app/admin/

    Note: After changing the settings, the page may not display correctly due to browser cache. Try a hard refresh (Cmd/Ctrl + Shift + R).


3. Troubleshooting During Implementation​

Finally, I'll summarize the problems I actually faced during this implementation process and their solutions as a personal memo.

Problem 1: Blank Screen (appendChild error)​

  • Symptom: Accessing the CMS admin panel (/admin/) resulted in a blank white screen, and the console showed Uncaught TypeError: Cannot read properties of null (reading 'appendChild').

  • Cause: The JavaScript was trying to render the DOM before the target <body> element had been constructed.

  • Solution: I moved the <script> tag inside static/admin/index.html from the <head> to just before the closing </body> tag.

    Example fix for static/admin/index.html
    <!-- Before -->
    <head>
    ...
    <script src=".../decap-cms.js"></script>
    </head>
    <body>
    </body>

    <!-- After -->
    <head>
    ...
    </head>
    <body>
    <!-- Move the script tag to the end of the body -->
    <script src=".../decap-cms.js"></script>
    </body>
    </html>

Problem 2: Unexpected Email/Password Form Appears​

  • Symptom: Instead of the expected Login with GitHub button, the standard Netlify Identity Email/Password form was displayed.
  • Cause: While Netlify Identity was enabled, the Git Gateway service, which is required to connect with the GitHub repository, was not.
  • Solution: In the Netlify dashboard, I went to Identity > Services and clicked Enable Git Gateway.

Problem 3: Redirected to the Wrong Domain After Authentication​

  • Symptom: After authenticating with GitHub, I was redirected to the public site's domain (https://[your-custom-domain].com/...) instead of back to the CMS admin panel (netlify.app).
  • Cause: Netlify Identity prioritizes the primary domain set under Domain management as the redirect target after authentication. The public site's domain was configured there.
  • Solution: I completely removed the custom domain settings for the site not directly hosted by this Netlify project from the project's Domain management settings.

Problem 4: reference does not exist Error on Save​

  • Symptom: When trying to save content in the CMS, I received the error Failed to persist entry: API_Error: reference does not exist.

  • Cause: The repository's default branch was main, but Decap CMS was trying to write to master, the older default branch name.

  • Solution: I explicitly set the branch in the backend section of static/admin/config.yml by adding branch: main.

    Example fix for static/admin/config.yml
    backend:
    name: git-gateway
    # Add this line to match the repository's default branch
    branch: main

Problem 5: Custom Commit Messages Not Applied​

  • Symptom: The custom commit_messages set in config.yml were not being applied, and the default commit messages were used instead.

  • Cause: The old summary setting and the new commit_messages setting coexisted in the file. Decap CMS is designed to ignore both if there is a configuration conflict, as a safety measure.

  • Solution: I completely removed the old summary line from the file and consolidated the configuration into commit_messages.

    Example fix for static/admin/config.yml
    # Before: The old setting (summary) remains
    backend:
    name: git-gateway
    branch: main
    summary: "{{filename}}" # <- This old setting causes commit_messages to be ignored
    commit_messages:
    create: 'docs(diary): add new entry "{{slug}}"'

    # After: Remove the old setting
    backend:
    name: git-gateway
    branch: main
    commit_messages:
    create: 'docs(diary): add new entry "{{slug}}"'
If this site helped you, please support us with 🌟 ☕️