Cloudflare Deep Dive
The promise and pitfalls of running Next.js on Cloudflare. What works, what breaks, and when to make the switch.
Advanced Topic
Cloudflare migration is high-effort, high-reward. Only consider if you've exhausted Vercel optimizations and costs are still problematic. Most apps should start with simpler optimizations.
Cloudflare's edge network is tempting. Global distribution, generous free tier, no cold starts. But Next.js wasn't built for Workers. Here's the reality.
The Appeal
Vercel costs scale with compute time. A function that runs 500ms costs 5x more than one that runs 100ms. Cloudflare Workers have a different model:
| Metric | Vercel | Cloudflare Workers |
|---|---|---|
| Free requests | 100K/month | 100K/day |
| Compute pricing | Per GB-hour | Per request (after free) |
| Cold starts | Yes (serverless) | No (isolates) |
| Global edge | Paid add-on | Default |
Cloudflare's pricing can be 10-50x cheaper for high-traffic sites with simple compute needs.
The Catch
Next.js assumes Node.js. Cloudflare Workers run V8 isolates. This gap creates friction at every layer:
┌─────────────────────────────────────────────────┐
│ Next.js │
│ (Node.js APIs, fs, crypto, Buffer, streams) │
├─────────────────────────────────────────────────┤
│ @opennextjs/cloudflare │
│ (Compatibility layer, adapters) │
├─────────────────────────────────────────────────┤
│ Cloudflare Workers │
│ (V8 isolates, Web APIs, no Node.js) │
└─────────────────────────────────────────────────┘Deployment Options
1. OpenNext for Cloudflare (Recommended)
The @opennextjs/cloudflare adapter is the current best path:
npm install @opennextjs/cloudflareimport type { OpenNextConfig } from "@opennextjs/cloudflare";
export default {
buildCommand: "npx next build",
// Cloudflare-specific options
} satisfies OpenNextConfig;2. Static Export (Safest)
If you can go fully static, Cloudflare Pages just works:
module.exports = {
output: 'export',
};No runtime issues. No compatibility problems. But no SSR, no API routes, no dynamic features.
3. Edge Runtime (Partial)
Force specific routes to edge runtime:
export const runtime = 'edge';
export async function GET() {
// Only Web APIs available here
return Response.json({ time: Date.now() });
}What You're Giving Up
Before migrating, understand what breaks:
| Feature | Vercel | Cloudflare |
|---|---|---|
fs module | ✅ | ❌ |
| Native Node modules | ✅ | ❌ |
next/image optimization | ✅ Built-in | ⚠️ Needs setup |
| ISR (revalidation) | ✅ | ⚠️ Different behavior |
| Streaming SSR | ✅ | ⚠️ Limited |
| Middleware | ✅ | ✅ (edge only) |
| API Routes | ✅ | ⚠️ Edge-compatible only |
Continue to Runtime Limitations for specific APIs that break and their workarounds.