Cloudflare
Next.js
Deployment
Performance

Deploy Next.js to Cloudflare Pages: Full Guide

Shihab Shahriar Antor
4 min read

TL;DR

Deploying Next.js static exports to Cloudflare Pages gives you global CDN, zero cold starts, and free tier hosting. Here's the complete setup guide for 2026.

Cloudflare Pages is the best hosting option for Next.js static exports in 2026: global CDN with 300+ PoPs, unlimited bandwidth on the free tier, automatic HTTPS, and zero cold starts. Here's the complete setup — including the gotchas that aren't in the official docs.

Why Cloudflare Pages for Next.js

FeatureCloudflare PagesVercelNetlify
Free bandwidthUnlimited100GB100GB
Cold starts❌ None (static)❌ Edge Functions❌ Functions
Global CDN300+ PoPs~70 regions~100 PoPs
Custom domainsFreeFreeFree
Build minutes500/month free6000/month300/month
Static export support✅ Perfect✅ Perfect✅ Perfect

For a static Next.js blog or landing page, Cloudflare Pages is strictly better than Vercel Free on bandwidth and CDN coverage.

Next.js config for static export

// next.config.ts
import type { NextConfig } from "next"

const config: NextConfig = {
  output: "export",
  trailingSlash: true,        // Required: CF Pages serves /slug/index.html
  images: { unoptimized: true } // Required: no Next.js image optimization in static
}

export default config

The three settings are all required. output: "export" generates static HTML in /out. trailingSlash: true ensures URLs match Cloudflare's file serving. images: { unoptimized: true } prevents Next.js from trying to optimize images at runtime (not possible in static export).

wrangler.toml configuration

# wrangler.toml
name = "blog-shihub"
compatibility_date = "2026-01-01"

[site]
bucket = "./out"     # where next build puts the static output

[[redirects]]
from = "/feed"
to = "/rss.xml"
status = 301

The [site] section tells Wrangler where your static files are. Redirects work natively in Cloudflare Pages — no separate redirect file needed.

GitHub Actions CI/CD

# .github/workflows/deploy.yml
name: Deploy to Cloudflare Pages

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      deployments: write

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - run: npm ci
      - run: npm run build  # next build → /out

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          projectName: blog-shihub
          directory: out
          gitHubToken: ${{ secrets.GITHUB_TOKEN }}

The CLOUDFLARE_API_TOKEN needs Cloudflare Pages:Edit permission. Create it in Cloudflare Dashboard → My Profile → API Tokens.

Common gotchas

1. Trailing slash 404s

Without trailingSlash: true, /blog-post works but /blog-post/ returns 404 (Cloudflare serves index.html at the trailing slash path).

2. sitemap.ts and robots.ts need force-static

// app/sitemap.ts
export const dynamic = "force-static"  // Required for static export

export default function sitemap() { /* ... */ }

Without force-static, next build throws: "Page cannot be statically generated because it used headers."

3. CSS type declarations for strict TypeScript

If you import CSS files in TypeScript with strict mode:

// types/globals.d.ts
declare module "*.css" {
  const content: { [className: string]: string }
  export default content
}

Without this, tsc throws on import "./styles.css" in strict mode.

4. Dynamic routes need generateStaticParams

// app/[slug]/page.tsx
export async function generateStaticParams() {
  return getAllPosts().map(post => ({ slug: post.slug }))
}

export const dynamicParams = false  // 404 for slugs not in list

generateStaticParams is required for any dynamic route in static export mode.

IndexNow with Cloudflare Workers

Wire IndexNow to notify Bing/DuckDuckGo when new content deploys:

// workers/indexnow.ts (Cloudflare Worker)
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sitemapUrl = "https://blog.shihub.online/sitemap.xml"
    const sitemapResponse = await fetch(sitemapUrl)
    const sitemap = await sitemapResponse.text()

    // Extract URLs from sitemap
    const urls = [...sitemap.matchAll(/<loc>(.*?)<\/loc>/g)]
      .map(m => m[1])

    // Submit to IndexNow
    await fetch("https://api.indexnow.org/indexnow", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        host: "blog.shihub.online",
        key: env.INDEXNOW_KEY,
        urlList: urls,
      }),
    })

    return new Response("Submitted", { status: 200 })
  }
}

Trigger via Cloudflare Pages deployment webhook → Cloudflare Worker → IndexNow API.

Performance results

blog.shihub.online on Cloudflare Pages:

MetricScore
Lighthouse Performance99
FCP0.4s
LCP0.6s
TBT0ms
CLS0
TTFB (global CDN)< 30ms

Sub-30ms TTFB globally is the main win vs managed Node.js hosting.

FAQ

Is Next.js static export limited compared to full Next.js on Vercel? Yes — you lose Server Actions, API Routes (runtime), ISR, and dynamic getServerSideProps. For blogs, landing pages, and documentation sites, static export covers everything needed.

Does Cloudflare Pages support Edge Functions with Next.js? Yes, via the @cloudflare/next-on-pages adapter. This enables API routes and server-side rendering on Cloudflare's edge network. Not covered here — this guide focuses on static export, which is simpler and covers most use cases.

How do I handle form submissions on a static Next.js site? Use a third-party form service (Formspree, Netlify Forms, or your own API on a separate domain). Static export means no server-side form processing.

How fast does Cloudflare Pages deploy? Typically 60–90 seconds for a Next.js static export. Build (next build) takes most of that time; Cloudflare's deploy step is ~10 seconds.


Written by Shihab Shahriar Antor — AI Engineer & Founder of Shahriar Labs. See also: Next.js 15 App Router: SEO & Performance Guide · SEO, GEO, and AEO for AI-Native Products in 2026.

Written by

Shihab Shahriar Antor — AI Engineer & Founder of Shahriar Labs. Creator of LetX, QuantumSketch, and more.

Share this mission log