Back to blog
Tutorials

Tutorial: Deploying Next.js to Cloudflare Pages from Scratch

Bruno Bracaioli
Tutorial: Deploying Next.js to Cloudflare Pages from Scratch

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 in out/ instead of requiring a Node server.
  • trailingSlash: true — generates /about/index.html instead of /about.html. Works better with CDNs.
  • images: { unoptimized: true } — disables the Image Optimization Server (which needs runtime). You can still use next/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:

  1. Pages → my-site → Custom domains → Set up a custom domain
  2. Type your domain (e.g., mysite.com)
  3. 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:

  1. Push the project to GitHub.
  2. Cloudflare Pages → Create a project → Connect to Git
  3. Select the repo, choose the main branch.
  4. Configure build:
    • Framework preset: Next.js (Static HTML Export)
    • Build command: npm run build
    • Build output: out
  5. 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.

Compartilhar:

Fique por dentro

Receba novos artigos sobre IA, desenvolvimento e tecnologia direto no seu email.