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.
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.
- URL:
- Public Site Environment (Google Cloud Run)
- URL:
https://[your-custom-domain].com/ - Role: Hosts the static site, maintaining the existing CI/CD pipeline.
- URL:
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.
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
- Enable Identity: Open the
Identitytab in your Netlify project and clickEnable Identity. - Configure Authentication Providers: In
Identity>Authentication providers, enableGitHub. - Enable Git Gateway: Go to
Identity>Servicesand clickEnable Git Gateway. This is crucial for integrating with your repository. - Invite Admin User: From the
Identitytab, useInvite usersto send an invitation to the email address associated with your GitHub account, and then accept it. - Remove Custom Domains: Open
Domain managementand remove any custom domains that you do not intend to host directly with this Netlify project.
Step 2: Repository Configuration
-
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 CMSmkdir -p static/admin
touch static/admin/index.html static/admin/config.yml -
Set up
static/admin/index.html: This file is the entry point for the Decap CMS admin panel. It loadsnetlify-identity-widget.jsfor authentication anddecap-cms.jsto 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> -
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. Thefieldssection specifies the input forms (e.g., title, body, tags) that appear in the CMS editor.
static/admin/config.ymlbackend:
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
- Commit and push the files you created and edited to your Git repository.
- Netlify will automatically start the deployment.
- Once the deployment is complete, access the CMS admin panel in your browser:
https://[your-site-name].netlify.app/admin/
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 showingUncaught 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 insidestatic/admin/index.htmlfrom the<head>to just before the closing</body>tag.
Problem 2: Unexpected Email/Password Form Appears
- Symptom: Instead of the expected
Login with GitHubbutton, the standard Netlify Identity Email/Password form was displayed. - Cause: While Netlify Identity was enabled, the
Git Gatewayservice, which is required to connect to the GitHub repository, was not. - Solution: I went to
Identity>Servicesin the Netlify dashboard and clickedEnable 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 managementas 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 managementsection.
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 existerror. -
Cause: The repository's default branch was
main, but Decap CMS was attempting to write tomaster, the older default branch name. -
Solution: I explicitly set the target branch by adding
branch: mainto thebackendsection ofstatic/admin/config.yml.Example fix for static/admin/config.ymlbackend:
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_messagesset inconfig.ymlwere not being applied; the default commit messages were used instead. - Cause: An old
summarysetting and the newcommit_messagessetting coexisted in the configuration file. When settings conflict, Decap CMS is designed to ignore both for safety. - Solution: I completely removed the old
summaryentry from the file, consolidating the settings intocommit_messages.