Skip to main content

A Log of Integrating Decap CMS into a Docusaurus Site on Cloud Run

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 the problems I encountered.

Final Configuration

I ultimately settled on a hybrid configuration where the CMS admin panel is hosted on Netlify, and the public-facing site is hosted 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 serves as a helpful reference for anyone considering a similar setup.

1. Architectural Decision Background

Arriving at the final hybrid configuration 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 discovered 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 have complicated the setup. To maintain the project's simplicity, I decided to abandon this approach.

The GitHub Integration Redirect Problem

Next, I attempted to integrate with GitHub using the Implicit Grant flow, which doesn't require a server side. However, despite specifying GitHub in the config.yml file, I encountered an issue where logging in would forcibly redirect to Netlify's authentication screen (https://api.netlify.com/auth).

After investigation, I found that this was by design: when Decap CMS detects that it is running on a Netlify domain (.netlify.app), it prioritizes Netlify Identity over the settings in the configuration file.

Deciding on the Hybrid Configuration

I decided to turn this "problem" into an advantage.

Final Approach

I chose to leverage Netlify's robust authentication infrastructure (Netlify Identity), which is available for free, to elegantly solve the authentication challenge. The final decision was to adopt a hybrid configuration where only the CMS admin functions are handled by Netlify, while the public site remains on Cloud Run.

2. Detailed Implementation Steps

Here are the specific setup steps to achieve the hybrid configuration.

Step 1: Netlify Configuration

  1. Enable Identity: Open the Identity tab in your Netlify project and click Enable Identity.
  2. Configure Authentication Providers: In Identity > Authentication providers, enable GitHub.
  3. Enable Git Gateway: Go to Identity > Services and click Enable Git Gateway. This is crucial for integrating with your repository.
  4. Invite Admin User: 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 you do not intend to host directly with this Netlify project.

Step 2: Repository 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.

    Creating Directories and Files for the CMS
    mkdir -p static/admin
    touch static/admin/index.html static/admin/config.yml
  2. Set up static/admin/index.html: This file is the entry point for the Decap CMS admin panel. It loads netlify-identity-widget.js for authentication and decap-cms.js to build the admin UI.

    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. Set up static/admin/config.yml: This is the most critical configuration file, defining how Decap CMS operates.

    • backend: Defines the authentication method (git-gateway) and repository connection details.
    • media_folder: Specifies the path for uploading media files like images.
    • collections: Defines the content types you want to manage. The fields section specifies the input forms (e.g., title, body, tags) that appear in the CMS editor.
    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: Deploy and Start Using

  1. Commit and push the files you created and edited to your Git repository.
  2. Netlify will automatically start the deployment.
  3. Once the deployment is complete, access the CMS admin panel in your browser:
    https://[your-site-name].netlify.app/admin/
Tip

If configuration changes don't seem to take effect, it might be due to browser caching. Try a hard refresh (Cmd/Ctrl + Shift + R).

3. Troubleshooting During Implementation

Finally, here is a log of the issues I actually faced during this implementation process and their solutions.

Problem 1: Blank Screen (appendChild Error)

  • Symptom: Accessing the CMS admin panel (/admin/) resulted in a blank white screen, with the console showing Uncaught TypeError: Cannot read properties of null (reading 'appendChild').
  • Cause: The JavaScript was attempting 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.

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 to the GitHub repository, was not.
  • Solution: I went to Identity > Services in the Netlify dashboard and clicked Enable Git Gateway.

Problem 3: Redirected to the Wrong Domain After Authentication

  • Symptom: After authenticating with GitHub, I was redirected to my 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 in Domain management as the redirect target after authentication. My public site's domain was set there.
  • Solution: I completely removed the custom domain settings from the Netlify project's Domain management section.

Problem 4: reference does not exist Error on Save

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

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

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

    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; the default commit messages were used instead.
  • Cause: An old summary setting and the new commit_messages setting coexisted in the configuration file. When settings conflict, Decap CMS is designed to ignore both for safety.
  • Solution: I completely removed the old summary entry from the file, consolidating the settings into commit_messages.