You can host a fast, secure blog for free using:
- Cloudflare Pages for static hosting and Functions
- Jekyll for static site generation
- Optional: Cloudflare R2 for object storage (images, downloads, or future API-backed content)
- Wrangler for local testing
You’ll pay only for your domain. The process below is exactly what I use to deploy the site you’re reading.
Why this stack
- Jekyll turns Markdown into static pages that are simple to build and version in Git.
- Cloudflare Pages serves those files at the edge and can run Functions for dynamic features—no servers to patch or scale.
- R2 stores static assets or data you want to fetch at runtime (e.g., via a Worker or Pages Function) without egress fees to Cloudflare services.
What you’ll build
- A Jekyll site
- Local preview with Wrangler
- A Pages project you can deploy by uploading a ZIP of your built site
- Optional: R2 ready for future dynamic content or asset storage
Prerequisites
- Ruby
- Jekyll
- Cloudflare account and a domain
- Wrangler CLI (installed a bit later)
Build locally
I’ll show you how to create and preview a Jekyll site locally.
# Install Jekyll & Bundler
gem install jekyll bundler
# Create a new site
jekyll new myblog
# Move into the project
cd myblog
# Serve locally
bundle exec jekyll serve
# Visit http://127.0.0.1:4000/
# To serve on all sddresses so its viewable on the network; add --host
bundle exec jekyll serve --host 0.0.0.0
Build vs serve
Jekyll outputs the production-ready site to the _site directory.
Use serve for local preview.
Use build when you’re ready to deploy.
# Build for production (outputs to _site)
bundle exec jekyll build
# Then deploy whatever’s inside _site
Test with Cloudflare Wrangler (Pages emulation)
Preview your Pages deployment locally with Wrangler.
Wrangler simulates Cloudflare Pages so you can test the exact build output.
Install Wrangler:
Build your site with Jekyll (don’t run the Jekyll server for this step).
Run the Pages emulator against the _site folder.
# From your project root
bundle exec jekyll build
# Emulate Cloudflare Pages locally on port 8788, serving from the _site directory
wrangler pages dev _site
# Output will include a local URL like:
# Ready on http://localhost:8788
Create a Cloudflare Pages project
- Sign in: Cloudflare Pages
- Go to Workers & Pages.
- Create a new application and choose Upload Static Files.
- You’ll later connect your domain to the Pages project (custom domain).
R2 is optional and useful when you want object storage (e.g., images or data) that you read via a Function or Worker.
Prepare a ZIP for upload Zip everything inside_siteand upload it as a deployment.
cd _site
zip -r ../myblog-site.zip *
Deploy from the Pages dashboard
- Open your Pages app in the Cloudflare dashboard
- Click Create deployment.
- Upload the ZIP you created from
_site. - Finish the deployment flow, then attach your custom domain to the Pages project.
Add posts and republish
Jekyll looks for posts in _posts with a specific naming format and front matter.
Add a Markdown file, rebuild, and upload a fresh ZIP.
.
├── _config.yml
├── _includes
├── _layouts
├── _posts # Your Markdown posts go here
├── _site # Build output (don’t edit directly)
├── 404.html
├── about.markdown
├── assets
├── functions # Optional: add Pages Functions here
├── Gemfile
├── Gemfile.lock
├── index.markdown
└── vendor
Post filenames must follow: YYYY-MM-DD-title.md
_posts/2026-01-02-serverless-blog.md
Each post needs front matter with layout, title, and date; categories are optional.
The front matter looks like:
---
layout: post
title: A Serverless Blog For Free
date: 2025-12-27 14:49:48 -0600
categories: Programming
---
Your post content starts here as markdpwn content
Where R2 fits in
For a basic blog, you can deploy entirely with Pages and never touch R2.
Add R2 if you want:
- Large media or download storage
- A data bucket fetched at runtime by a Worker or Pages Function
- A place to push content from CI without egress charges to Cloudflare services
You can bind an R2 bucket to a Pages Function or Worker and fetch objects as needed.
Keep static pages in Pages; put dynamic or large assets in R2.
Wrap-up
That’s it: a cost-free, secure, and fast blog at the edge.
You write Markdown, Jekyll builds static pages, and Cloudflare Pages serves them globally.
If you later need dynamic features or storage, add a Pages Function and R2 without changing your basic deployment flow.
Enjoy!