Adding a Headless CMS to Your Jekyll Blog
This guide shows you how to add a content management system to your Jekyll blog using Decap CMS (formerly Netlify CMS) and Cloudflare Workers for authentication.
Why Decap CMS?
Decap CMS provides:
- A user-friendly interface for editing content
- Git-based workflow (changes are commits)
- Free and open-source
- No database required
Step 1: Set Up Decap CMS
Create an admin folder in your project root with two files:
admin/
├── index.html
└── config.yml
admin/index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>Content Manager</title>
</head>
<body>
<script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
</body>
</html>
admin/config.yml
backend:
name: github
branch: main
repo: your-username/your-repo.github.io
publish_mode: editorial_workflow
media_folder: "assets/img/uploads"
public_folder: "/assets/img/uploads"
collections:
- name: "blog"
label: "Blog Posts"
folder: "_posts"
create: true
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
fields:
- {label: "Layout", name: "layout", widget: "hidden", default: "post"}
- {label: "Title", name: "title", widget: "string"}
- {label: "Date", name: "date", widget: "datetime"}
- {label: "Description", name: "description", widget: "string"}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "Tags", name: "tags", widget: "list"}
- {label: "Categories", name: "categories", widget: "list"}
Important: Replace your-username/your-repo.github.io with your actual GitHub repository.
Step 2: Add OAuth Authentication
Since we’re not using Netlify, we need an OAuth provider. We’ll use Cloudflare Workers (free).
Follow Sveltia CMS Auth Setup
- Visit Sveltia CMS Auth
- Follow the complete tutorial to set up:
- GitHub OAuth App
- Cloudflare Worker
- Environment variables
Key Steps:
-
Create GitHub OAuth App:
- Go to GitHub Settings > Developer settings > OAuth Apps
- New OAuth App
- Homepage URL:
https://yourdomain.com - Callback URL:
https://your-worker.workers.dev/callback
-
Deploy Cloudflare Worker:
- Use the Sveltia template
- Add your GitHub OAuth credentials as secrets
-
Update config.yml:
backend: name: github repo: your-username/your-repo branch: main base_url: https://your-worker.workers.dev
Step 3: Test Your CMS
- Rebuild your site
- Navigate to
https://yourdomain.com/admin/ - Click “Login with GitHub”
- Start creating content!
Tips
- The editorial workflow creates pull requests instead of direct commits
- Test locally using
npx decap-serverbefore deploying - Customize collections to match your site structure
- Add more content types (pages, projects, etc.) as needed
Troubleshooting
“Error: Failed to load config”
- Check that config.yml syntax is valid
- Ensure file paths are correct
“Authentication failed”
- Verify OAuth app credentials
- Check Cloudflare Worker is deployed
- Confirm callback URL matches
Decap CMS
- In the root folder, create an
adminfolder. We’ll add two files there:
admin
├ index.html
└ config.yml
index.html
---
layout: default
title: Admin
permalink: /admin/
subtitle: login screen for decap
title: Login
nav: true
nav_order: 10
dropdown: false
publish_mode: editorial_workflow
media_folder: "assets/uploads"
---
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>Content Manager</title>
</head>
<body>
<!-- Include the script that builds the page and powers Decap CMS -->
<script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
</body>
</html>
config.yml
backend:
name: github
branch: main # Branch to update (whatever is your default branch. If left blank it uses master)
repo: your-repo/your-repo.github.io
publish_mode: editorial_workflow
media_folder: "assets/img/uploads"
public_folder: static/media
collections:
- name: "blog" # Used in routes, e.g., /admin/collections/blog
label: "Blog" # Used in the UI
folder: "\_posts" # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection
slug: "{{year}}-{{month}}-{{day}}-{{slug}}" # Filename template, e.g., YYYY-MM-DD-title.md
fields: # The fields for each document, usually in front matter
- {label: "Layout", name: "layout", widget: "hidden", default: "post"}
- {label: "Comments", name: "gisqus_comments", widget: "hidden", default: "true"}
- {label: "Title", name: "title", widget: "string"}
- {label: "Publish Date", name: "date", widget: "datetime"}
- {label: "Description", name: "description", widget: "string"}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "Tags", name: "tags", widget: "markdown"}
- {label: "Categories", name: "categories", widget: "markdown"}
- {label: "related", name: "related_posts", widget: "hidden", default: "false"}
- name: "news"
label: "News"
folder: "posts"
create: true
fields:
- {label: "Layout", name: "layout", widget: "hidden", default: "post"}
- {label: "Comments", name: "gisqus_comments", widget: "hidden", default: "true"}
- {label: "Publish Date", name: "date", widget: "datetime"}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "gisqus", name: "giscus_comments", widget: "hidden", default: "true"}
inline: true
This config file tells the CMS that we have two different collections of posts that we want to be able to create. It requires some kinds of fields, and others are conventions that al-folio’s posts already follow. You can add/edit to this list to some degree, like adding another collection for publishing your own book reviews.
Once you rebuild the website with docker-compose, you’ll notice you have a login button on your site. It won’t work out of the box because we’re not on Netlify due to OAuth requirements, so we’ll create one ourselves.
Adding OAuth
Luckily for us, we can run OAuth logic for free with the help of sveltia, a CMS authenticator that relies on an also-free Cloudflare workers script.
Make sure to follow the tutorial exactly, or you won’t be able to make sense of what went wrong.
Conclusion
There ya go. If I’m missing something feel free to request it.