
Why Cloudflare Pages?
Cloudflare Pages is the cheapest, fastest, simplest option to host modern static sites in 2026. Generous free tier (500 builds/month, unlimited requests, unlimited bandwidth, no egress fees), CDN in 300+ PoPs, custom domains with auto SSL, and native Workers integration.
Compared to Vercel: Cloudflare is genuinely free, no trial, no surprise billing. Compared to Netlify: superior edge network. The trade-off is that some advanced Next.js features (Server Actions, on-demand ISR, integrated Image Optimization) need adaptation.
For static sites or apps that fit output: "export", Pages is unbeatable. That's exactly what we'll do.
Prerequisites
- Node.js 20+
- Free Cloudflare account
- GitHub account (optional, recommended)
- Basic Next.js knowledge
Step 1: Create the Next.js project
npx create-next-app@latest my-site
cd my-site
Choose:
- TypeScript: Yes
- ESLint: Yes
- Tailwind: Yes (optional)
- App Router: Yes
- src/ directory: Yes
- Turbopack: Yes
Step 2: Configure for static export
Edit next.config.ts:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
trailingSlash: true,
images: {
unoptimized: true,
},
};
export default nextConfig;
What each option does:
output: "export"— Next generates pure static HTML inout/instead of requiring a Node server.trailingSlash: true— generates/about/index.htmlinstead of/about.html. Works better with CDNs.images: { unoptimized: true }— disables the Image Optimization Server (which needs runtime). You can still usenext/image, but images come out as you provided them.
What YOU lose with static export
Before continuing, know: static export doesn't support:
- API Routes
- Middleware
- Server Actions
- On-demand ISR (Incremental Static Regeneration)
- Dynamic SSR
If you need any of those features, use Cloudflare Workers + OpenNext (more advanced path, outside this tutorial) or Vercel.
Step 3: Add content
Edit src/app/page.tsx:
export default function Home() {
return (
<main className="min-h-screen flex items-center justify-center bg-black text-white">
<div className="text-center">
<h1 className="text-6xl font-bold mb-4">My Site</h1>
<p className="text-xl text-gray-400">Hosted on Cloudflare Pages</p>
</div>
</main>
);
}
Add metadata in src/app/layout.tsx:
export const metadata = {
title: "My Site",
description: "Static site on Cloudflare Pages",
};
Step 4: Local build
npm run build
You'll see an out/ folder created with all the HTML. To test locally:
npx serve out
Open http://localhost:3000 (or whatever port serve shows).
Step 5: Install Wrangler
Wrangler is Cloudflare's CLI. We'll use it to deploy.
npm install --save-dev wrangler
npx wrangler login
It opens your browser to authenticate with Cloudflare.
Step 6: Create the Cloudflare Pages project
npx wrangler pages project create my-site --production-branch=main
Cloudflare creates the project and gives you an initial URL: https://my-site.pages.dev.
Step 7: Deploy
npx wrangler pages deploy out --project-name=my-site --branch=main
In ~5 seconds:
✨ Deployment complete!
✅ https://my-site.pages.dev
Open the URL in your browser. Site is live, global, on CDN.
Step 8: Automate with an npm script
Edit package.json:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"deploy": "next build && npx wrangler pages deploy out --project-name=my-site --branch=main"
}
}
Now npm run deploy does build + publish in one command.
Step 9: Custom domain
In the Cloudflare dashboard:
- Pages → my-site → Custom domains → Set up a custom domain
- Type your domain (e.g.,
mysite.com) - Cloudflare detects if the domain is already in your account. If yes, it configures DNS automatically. If not, it gives you a CNAME to add.
SSL is automatic and free. In ~1 minute your site responds on the custom domain.
Step 10: CI/CD via GitHub (optional)
If you prefer automatic deploy on every push:
- Push the project to GitHub.
- Cloudflare Pages → Create a project → Connect to Git
- Select the repo, choose the
mainbranch. - Configure build:
- Framework preset: Next.js (Static HTML Export)
- Build command:
npm run build - Build output:
out
- Deploy.
From here, every git push to main triggers an auto deploy. PRs get preview deployments with unique URLs.
Important optimizations
Headers and cache
Create public/_headers:
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/*.css
Cache-Control: public, max-age=31536000, immutable
/*.js
Cache-Control: public, max-age=31536000, immutable
Cloudflare applies these headers automatically.
Redirects
Create public/_redirects:
/old-page /new-page 301
/blog/* https://blog.mysite.com/:splat 302
Env vars
npx wrangler pages secret put MY_VAR --project-name=my-site
Accessible at build time as process.env.MY_VAR.
Troubleshooting
Error: "Project not found" — confirm the project name matches what you created.
404 on dynamic routes — you need generateStaticParams() on every page with [param]. Static export requires it.
Broken images — next/image with unoptimized: true still works, but relative paths can break. Use absolute paths starting with /.
Build fails on CI — confirm node is version 20+. Configure in Cloudflare Pages → Settings → Environment variables → NODE_VERSION = 20.
Conclusion
In under 15 minutes you have a production-ready Next.js site hosted on a global CDN, with SSL, custom domain, security headers and automatic CI/CD — spending $0. For personal projects, landing pages, static blogs, technical docs, institutional sites and MVPs, it's hard to beat Cloudflare Pages in 2026.


