If you ask me to build a content site in 2026 — a blog, a marketing site, a small storefront, a docs portal, a portfolio — my answer is the same: Astro on Cloudflare Pages. I’ve shipped enough variations to know it’s not a fad.
Here’s what makes that pairing stick.
Table of contents
Open Table of contents
The case for Astro
Astro’s pitch is “ship less JavaScript.” It’s been the same pitch for four years, and the execution keeps getting better.
Three things that matter when I evaluate a content stack:
- HTML-first rendering. The default output is HTML with zero hydration. Components opt in to interactivity via
client:load,client:idle,client:visible. Most marketing sites need none of it. - Framework-agnostic islands. I can drop a React component, a Svelte component, and a Vue component into the same page if I have to. I rarely have to, but the option keeps me honest.
- Real content collections. Markdown and MDX get typed frontmatter via Zod schemas. Stop me from publishing a post with a missing description. Catch it at
astro check, not in production.
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
const posts = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
title: z.string(),
pubDatetime: z.date(),
description: z.string(),
tags: z.array(z.string()).default(['others']),
featured: z.boolean().optional(),
ogImage: image().or(z.string()).optional(),
}),
});
export const collections = { posts };
That schema is the contract. The build fails if I forget a description. The dev server fails if I add a tag that isn’t a string. This is more value than any “headless CMS” I’ve used.
The case for Cloudflare Pages
There are five hosts that can serve a static site well: Cloudflare Pages, Netlify, Vercel, GitHub Pages, AWS Amplify. Of those, Pages is the one I keep choosing for new content sites. Why:
- Free tier is not a trap. 500 builds/month, unlimited bandwidth, unlimited sites. No surprise overage email.
- Custom domains take 30 seconds. Cloudflare runs the DNS. The certificate provisions automatically. The Edge configuration is one tab away.
- Pages Functions live next to the static site. When a contact form needs a server, I don’t deploy a separate service. I add
functions/contact.ts. Same project, same deploy. - Cloudflare-specific bindings. A Pages site can bind D1, KV, R2, Email Routing — the same way a Worker does. If I want comments, signups, or a tiny API later, the path is paved.
The other hosts are fine. None of them give me the Cloudflare ecosystem behind the same deploy button.
What this site is built with
This blog (jccbbb.com) is the same template, deployed the same way:
- AstroPaper as the base theme — minimal, fast, dark-mode-first, no JS-heavy chrome.
- Astro 6 with content collections, MDX, and Shiki for code highlighting.
- Tailwind CSS v4 for styling. Utility-first; zero stylesheet imports beyond the theme tokens.
- Pagefind for static search — search index built at build time, no backend.
@astrojs/cloudflareadapter, output static. Adapter installed so the path to server endpoints (contact form, newsletter API) is one config change away.
Deploy is one command:
npm run build
npx wrangler pages deploy dist --project-name=jccbbb --branch=main
That’s the whole CI step. Build, deploy, done.
The build characteristics
For a content site, build-time work beats request-time work every time. Astro + Pages collapses the runtime to “serve the file.”
A representative Lighthouse run on this blog:
| Metric | Score |
|---|---|
| Performance | 99 |
| Accessibility | 100 |
| Best Practices | 100 |
| SEO | 100 |
| LCP | 0.8s |
| CLS | 0.001 |
| TBT | 0ms |
That’s not a finely tuned setup. That’s the default AstroPaper + Pages, with one blog post on the page.
The reason it’s that fast: there’s no JS. There’s no SSR latency. There’s no client-side router waiting to hydrate. There’s one HTML file, a sliver of CSS, and a few SVGs. The CDN serves it in 30ms from your nearest PoP. Lighthouse can’t find anything to complain about because there isn’t anything happening.
When this stack stops fitting
Three cases where I reach for something else:
- The site is mostly an app. Auth, state, dashboards. Move to a real React or Vue setup, probably still on Cloudflare (Pages + a Worker, or a single SSR Worker), but not Astro.
- The content lives in a CMS the editors love. If a non-technical team has been editing in WordPress for ten years, headless WP into Astro is fine, but pure markdown isn’t going to fly.
- The site needs heavy server-rendered personalisation. Pages can do it via SSR, but at that point you’re shipping a Worker that happens to render HTML. Cut the middleman and design it as a Worker from day one.
Why I keep coming back
The honest answer: Astro + Cloudflare makes shipping a content site boring. No DevOps surprises. No mysterious build errors. No “we’re rate-limited on the CDN.” No “the framework changed its data API.” Boring is the goal.
When the next interesting project lands, the content site doesn’t need attention. That’s how you get to ship the next thing.
For the broader picture of why I run everything on Cloudflare, Why I’m Building Cloudflare-Native Systems. For how the fullstack apps that sit alongside these content sites are structured, How I Structure Fullstack Projects on Cloudflare.