This guide documents the migration from GitHub Pages to Cloudflare Pages and the configuration required.
_headers fileGitHub Pages uses a locked-down environment with Jekyll 3.x. Cloudflare Pages allows Jekyll 4.x but requires a separate Gemfile to avoid native compilation issues.
A separate Gemfile.cloudflare is used for Cloudflare builds:
source 'https://rubygems.org'
gem "jekyll", "~> 4.3"
# Pin sass converter to v2 to avoid sass-embedded native compilation issues
gem "jekyll-sass-converter", "~> 2.0"
gem "sassc", "~> 2.4"
group :jekyll_plugins do
gem 'jekyll-seo-tag'
gem 'jekyll-gist'
gem 'jekyll-feed'
gem 'jemoji'
gem "jekyll-paginate-v2", "~> 3.0"
gem "html-proofer"
gem "jekyll-sitemap"
gem 'jekyll-minifier'
gem 'jekyll-include-cache'
end
gem "webrick", "~> 1.8"
gem "faraday-retry", "~> 2.2"
gem "csv"
gem "base64"
| Setting | GitHub Pages | Cloudflare |
|---|---|---|
| Jekyll version | 3.10.x (locked) | 4.3.x |
| Sass compiler | sass-embedded | sassc (pinned) |
| Plugins | Limited | Full control |
In Cloudflare Dashboard > Pages > Your Project > Settings > Builds & deployments:
| Setting | Value |
|---|---|
| Framework preset | None |
| Build command | export BUNDLE_GEMFILE=Gemfile.cloudflare && JEKYLL_ENV=production bundle install && bundle exec jekyll build |
| Build output directory | _site |
| Root directory | / |
| Variable | Value | Notes |
|---|---|---|
RUBY_VERSION | 3.2.2 | Required |
JEKYLL_ENV | production | Already in build command; optional here |
CNAME@<project-name>.pages.dev185.199.108.153185.199.109.153185.199.110.153185.199.111.153Add CNAME pointing to <project-name>.pages.dev
Cache headers are configured via the _headers file in the project root:
/*
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer-when-downgrade
/*.html
Cache-Control: public, max-age=3600
/assets/*
Cache-Control: public, max-age=31536000, immutable
Important: Add _headers to Jekyll’s include list in _config.yml:
include:
- _headers
curl -sI https://yourdomain.com | grep -i server
# Should show: server: cloudflare
curl -sI https://yourdomain.com | grep -i github
# Should return nothing
curl -sI https://yourdomain.com/assets/js/some-file.js | grep -i cache-control
# Should show: cache-control: public, max-age=31536000, immutable
Symptom: sass-embedded fails to compile native extensions
Fix: Use sassc instead by pinning versions in Gemfile.cloudflare:
gem "jekyll-sass-converter", "~> 2.0"
gem "sassc", "~> 2.4"
Symptom: Error like Incompatible units: 'ch' and 'px'
Fix: Wrap CSS min()/max()/clamp() in interpolation to pass through as literal CSS:
// Before (fails with sassc)
width: min(210px, 24ch);
// After (works)
width: #{"min(210px, 24ch)"};
Symptom: www.yourdomain.com shows Cloudflare 522 error
Fix: Add www.yourdomain.com as a Custom Domain in Pages project, not just a DNS CNAME record.
Symptom: PageSpeed shows 10-minute cache despite _headers file
Causes:
_headers not in Jekyll’s include listCloudflare builds may be slower than GitHub Pages due to:
Current build time: ~7-8 minutes (vs ~2 minutes on GitHub Pages)
This is acceptable for a blog with infrequent updates.
The main Gemfile still works with GitHub Pages if needed as a fallback. Keep both files in sync when adding new plugins.
| Environment | Gemfile | Command |
|---|---|---|
| Local dev | Gemfile | bundle exec jekyll serve |
| GitHub Pages | Gemfile | Automatic |
| Cloudflare | Gemfile.cloudflare | Custom build command |