Claude-skill-registry cloudflare-nextjs
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cloudflare-nextjs-jackspace-claudeskillz" ~/.claude/skills/majiayu000-claude-skill-registry-cloudflare-nextjs && rm -rf "$T"
skills/data/cloudflare-nextjs-jackspace-claudeskillz/SKILL.mdCloudflare Next.js Deployment Skill
Deploy Next.js applications to Cloudflare Workers using the OpenNext Cloudflare adapter for production-ready serverless Next.js hosting.
Use This Skill When
- Deploying Next.js applications (App Router or Pages Router) to Cloudflare Workers
- Need server-side rendering (SSR), static site generation (SSG), or incremental static regeneration (ISR) on Cloudflare
- Migrating existing Next.js apps from Vercel, AWS, or other platforms to Cloudflare
- Building full-stack Next.js applications with Cloudflare services (D1, R2, KV, Workers AI)
- Need React Server Components, Server Actions, or Next.js middleware on Workers
- Want global edge deployment with Cloudflare's network
Key Concepts
OpenNext Adapter Architecture
The OpenNext Cloudflare adapter (
@opennextjs/cloudflare) transforms Next.js build output into Cloudflare Worker-compatible format. This is fundamentally different from standard Next.js deployments:
- Node.js Runtime Required: Uses Node.js runtime in Workers (NOT Edge runtime)
- Dual Development Workflow: Test in both Next.js dev server AND workerd runtime
- Custom Build Pipeline:
→ OpenNext transformation → Worker deploymentnext build - Cloudflare-Specific Configuration: Requires wrangler.jsonc and open-next.config.ts
Critical Differences from Standard Next.js
| Aspect | Standard Next.js | Cloudflare Workers |
|---|---|---|
| Runtime | Node.js or Edge | Node.js (via nodejs_compat) |
| Dev Server | | + |
| Deployment | Platform-specific | |
| Worker Size | No limit | 3 MiB (free) / 10 MiB (paid) |
| Database Connections | Global clients OK | Must be request-scoped |
| Image Optimization | Built-in | Via Cloudflare Images |
| Caching | Next.js cache | OpenNext config + Workers cache |
Setup Patterns
New Project Setup
Use Cloudflare's
create-cloudflare (C3) CLI to scaffold a new Next.js project pre-configured for Workers:
npm create cloudflare@latest -- my-next-app --framework=next
What this does:
- Runs Next.js official setup tool (
)create-next-app - Installs
adapter@opennextjs/cloudflare - Creates
with correct configurationwrangler.jsonc - Creates
for caching configurationopen-next.config.ts - Adds deployment scripts to
package.json - Optionally deploys immediately to Cloudflare
Development workflow:
npm run dev # Next.js dev server (fast reloads) npm run preview # Test in workerd runtime (production-like) npm run deploy # Build and deploy to Cloudflare
Existing Project Migration
To add the OpenNext adapter to an existing Next.js application:
1. Install the adapter
npm install --save-dev @opennextjs/cloudflare
2. Create wrangler.jsonc
{ "name": "my-next-app", "compatibility_date": "2025-05-05", "compatibility_flags": ["nodejs_compat"] }
Critical configuration:
: Minimumcompatibility_date
(for FinalizationRegistry support)2025-05-05
: Must includecompatibility_flags
(for Node.js runtime)nodejs_compat
3. Create open-next.config.ts
import { defineCloudflareConfig } from "@opennextjs/cloudflare"; export default defineCloudflareConfig({ // Caching configuration (optional) // See: https://opennext.js.org/cloudflare/caching });
4. Update package.json scripts
{ "scripts": { "dev": "next dev", "build": "next build", "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview", "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy", "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts" } }
Script purposes:
: Next.js development server (fast iteration)dev
: Build + run in workerd runtime (test before deploy)preview
: Build + deploy to Cloudflaredeploy
: Generate TypeScript types for Cloudflare bindingscf-typegen
5. Ensure Node.js runtime (not Edge)
Remove Edge runtime exports from your app:
// ❌ REMOVE THIS (Edge runtime not supported) export const runtime = "edge"; // ✅ Use Node.js runtime (default) // No export needed - Node.js is default
Development Workflow
Dual Testing Strategy
Always test in BOTH environments:
-
Next.js Dev Server (
)npm run dev- Fast hot reloading
- Best developer experience
- Runs in Node.js (not production runtime)
- Use for rapid iteration
-
Workerd Runtime (
)npm run preview- Runs in production-like environment
- Catches runtime-specific issues
- Slower rebuild times
- Required before deployment
When to Use Each
# Iterating on UI/logic → Use Next.js dev server npm run dev # Testing integrations (D1, R2, KV) → Use preview npm run preview # Before deploying → ALWAYS test preview npm run preview # Deploy to production npm run deploy
Configuration Requirements
Wrangler Configuration
Minimum requirements in
wrangler.jsonc:
{ "name": "your-app-name", "compatibility_date": "2025-05-05", // Minimum for FinalizationRegistry "compatibility_flags": ["nodejs_compat"] // Required for Node.js runtime }
Environment Variables for Package Exports
If using npm packages with multiple export conditions, create
.env:
WRANGLER_BUILD_CONDITIONS="" WRANGLER_BUILD_PLATFORM="node"
This ensures Wrangler prioritizes the
node export when available.
Cloudflare Bindings Integration
Add bindings in
wrangler.jsonc:
{ "name": "your-app-name", "compatibility_date": "2025-05-05", "compatibility_flags": ["nodejs_compat"], // D1 Database "d1_databases": [ { "binding": "DB", "database_name": "production-db", "database_id": "your-database-id" } ], // R2 Storage "r2_buckets": [ { "binding": "BUCKET", "bucket_name": "your-bucket" } ], // KV Storage "kv_namespaces": [ { "binding": "KV", "id": "your-kv-id" } ], // Workers AI "ai": { "binding": "AI" } }
Access bindings in Next.js via
process.env:
// app/api/route.ts import type { NextRequest } from 'next/server'; export async function GET(request: NextRequest) { // Access Cloudflare bindings const env = process.env as any; // D1 Database query const result = await env.DB.prepare('SELECT * FROM users').all(); // R2 Storage access const file = await env.BUCKET.get('file.txt'); // KV Storage access const value = await env.KV.get('key'); // Workers AI inference const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', { prompt: 'Hello AI' }); return Response.json({ result }); }
Error Prevention (10+ Documented Errors)
1. Worker Size Limit Exceeded (3 MiB - Free Plan)
Error:
"Your Worker exceeded the size limit of 3 MiB"
Cause: Workers Free plan limits Worker size to 3 MiB (gzip-compressed)
Solutions:
- Upgrade to Workers Paid plan (10 MiB limit)
- Analyze bundle size and remove unused dependencies
- Use dynamic imports to code-split large dependencies
Bundle analysis:
npx opennextjs-cloudflare build cd .open-next/server-functions/default # Analyze handler.mjs.meta.json with ESBuild Bundle Analyzer
Source: https://opennext.js.org/cloudflare/troubleshooting#worker-size-limits
2. Worker Size Limit Exceeded (10 MiB - Paid Plan)
Error:
"Your Worker exceeded the size limit of 10 MiB"
Cause: Unnecessary code bundled into Worker
Debug workflow:
- Run
npx opennextjs-cloudflare build - Navigate to
.open-next/server-functions/default - Analyze
using ESBuild Bundle Analyzerhandler.mjs.meta.json - Identify and remove/externalize large dependencies
Source: https://opennext.js.org/cloudflare/troubleshooting#worker-size-limits
3. FinalizationRegistry Not Defined
Error:
"ReferenceError: FinalizationRegistry is not defined"
Cause:
compatibility_date in wrangler.jsonc is too old
Solution: Update
compatibility_date to 2025-05-05 or later:
{ "compatibility_date": "2025-05-05" // Minimum for FinalizationRegistry }
Source: https://opennext.js.org/cloudflare/troubleshooting#finalizationregistry-is-not-defined
4. Cannot Perform I/O on Behalf of Different Request
Error:
"Cannot perform I/O on behalf of a different request"
Cause: Database client created globally and reused across requests
Problem code:
// ❌ WRONG: Global DB client import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); export async function GET() { // This will fail - pool created in different request context const result = await pool.query('SELECT * FROM users'); return Response.json(result); }
Solution: Create database clients inside request handlers:
// ✅ CORRECT: Request-scoped DB client import { Pool } from 'pg'; export async function GET() { // Create client within request context const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const result = await pool.query('SELECT * FROM users'); await pool.end(); return Response.json(result); }
Alternative: Use Cloudflare D1 (designed for Workers) instead of external databases:
// ✅ BEST: Use D1 (no connection pooling needed) export async function GET(request: NextRequest) { const env = process.env as any; const result = await env.DB.prepare('SELECT * FROM users').all(); return Response.json(result); }
5. NPM Package Import Failures
Error:
"Could not resolve '<package>'"
Cause: Missing
nodejs_compat flag or package export conditions
Solution 1: Enable
nodejs_compat flag:
{ "compatibility_flags": ["nodejs_compat"] }
Solution 2: For packages with multiple exports, create
.env:
WRANGLER_BUILD_CONDITIONS="" WRANGLER_BUILD_PLATFORM="node"
Source: https://opennext.js.org/cloudflare/troubleshooting#npm-packages-fail-to-import
6. Failed to Load Chunk (Turbopack)
Error:
"Failed to load chunk server/chunks/ssr/"
Cause: Next.js built with Turbopack (
next build --turbo)
Solution: Use standard build (Turbopack not supported by adapter):
{ "scripts": { "build": "next build" // ✅ Correct // "build": "next build --turbo" // ❌ Don't use Turbopack } }
Source: https://opennext.js.org/cloudflare/troubleshooting#failed-to-load-chunk
7. SSRF Vulnerability (CVE-2025-6087)
Vulnerability: Server-Side Request Forgery via
/_next/image endpoint
Affected versions:
@opennextjs/cloudflare < 1.3.0
Solution: Upgrade to version 1.3.0 or later:
npm install --save-dev @opennextjs/cloudflare@^1.3.0
Impact: Allows unauthenticated users to proxy arbitrary remote content
Source: https://github.com/advisories/GHSA-rvpw-p7vw-wj3m
8. Durable Objects Binding Warnings
Warning:
"You have defined bindings to the following internal Durable Objects... will not work in local development, but they should work in production"
Cause: OpenNext uses Durable Objects for caching (
DOQueueHandler, DOShardedTagCache)
Solution: Safe to ignore - warning is expected behavior
Alternative (to suppress warning): Define Durable Objects in separate Worker with own config
Source: https://opennext.js.org/cloudflare/known-issues#caching-durable-objects
9. Prisma + D1 Middleware Conflicts
Error: Build errors when using
@prisma/client + @prisma/adapter-d1 in Next.js middleware
Cause: Database initialization in middleware context
Workaround: Initialize Prisma client in route handlers, not middleware
Source: https://github.com/opennextjs/opennextjs-cloudflare/issues/471
10. cross-fetch Library Errors
Error: Errors when using libraries that depend on
cross-fetch
Cause: OpenNext patches deployment package causing
cross-fetch to try using Node.js libraries when native fetch is available
Solution: Use native
fetch API directly instead of cross-fetch:
// ✅ Use native fetch const response = await fetch('https://api.example.com/data'); // ❌ Avoid cross-fetch // import fetch from 'cross-fetch';
Source: https://opennext.js.org/cloudflare/troubleshooting
11. Windows Development Issues
Issue: Full Windows support not guaranteed
Cause: Underlying Next.js tooling issues on Windows
Solutions:
- Use WSL (Windows Subsystem for Linux)
- Use virtual machine with Linux
- Use Linux-based CI/CD for deployments
Source: https://opennext.js.org/cloudflare#windows-support
Feature Support Matrix
| Feature | Status | Notes |
|---|---|---|
| App Router | ✅ Fully Supported | Latest App Router features work |
| Pages Router | ✅ Fully Supported | Legacy Pages Router supported |
| Route Handlers | ✅ Fully Supported | API routes work as expected |
| React Server Components | ✅ Fully Supported | RSC fully functional |
| Server Actions | ✅ Fully Supported | Server Actions work |
| SSG | ✅ Fully Supported | Static Site Generation |
| SSR | ✅ Fully Supported | Server-Side Rendering |
| ISR | ✅ Fully Supported | Incremental Static Regeneration |
| Middleware | ✅ Supported | Except Node.js middleware (15.2+) |
| Image Optimization | ✅ Supported | Via Cloudflare Images |
| Partial Prerendering (PPR) | ✅ Supported | Experimental in Next.js |
| Composable Caching | ✅ Supported | directive |
| Response Streaming | ✅ Supported | Streaming responses work |
API | ✅ Supported | Post-response async work |
| Node.js Middleware (15.2+) | ❌ Not Supported | Future support planned |
| Edge Runtime | ❌ Not Supported | Use Node.js runtime |
Integration with Cloudflare Services
D1 Database (SQL)
// app/api/users/route.ts import type { NextRequest } from 'next/server'; export async function GET(request: NextRequest) { const env = process.env as any; const result = await env.DB.prepare( 'SELECT * FROM users WHERE active = ?' ).bind(true).all(); return Response.json(result.results); } export async function POST(request: NextRequest) { const env = process.env as any; const { name, email } = await request.json(); const result = await env.DB.prepare( 'INSERT INTO users (name, email) VALUES (?, ?)' ).bind(name, email).run(); return Response.json({ id: result.meta.last_row_id }); }
Wrangler config:
{ "d1_databases": [ { "binding": "DB", "database_name": "production-db", "database_id": "your-database-id" } ] }
See also:
cloudflare-d1 skill for complete D1 patterns
R2 Storage (Object Storage)
// app/api/upload/route.ts import type { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { const env = process.env as any; const formData = await request.formData(); const file = formData.get('file') as File; // Upload to R2 await env.BUCKET.put(file.name, file.stream(), { httpMetadata: { contentType: file.type } }); return Response.json({ success: true, filename: file.name }); } export async function GET(request: NextRequest) { const env = process.env as any; const { searchParams } = new URL(request.url); const filename = searchParams.get('file'); const object = await env.BUCKET.get(filename); if (!object) { return new Response('Not found', { status: 404 }); } return new Response(object.body, { headers: { 'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream' } }); }
See also:
cloudflare-r2 skill for complete R2 patterns
Workers AI (Model Inference)
// app/api/ai/route.ts import type { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { const env = process.env as any; const { prompt } = await request.json(); const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', { prompt }); return Response.json(response); }
Wrangler config:
{ "ai": { "binding": "AI" } }
See also:
cloudflare-workers-ai skill for complete AI patterns
KV Storage (Key-Value)
// app/api/cache/route.ts import type { NextRequest } from 'next/server'; export async function GET(request: NextRequest) { const env = process.env as any; const { searchParams } = new URL(request.url); const key = searchParams.get('key'); const value = await env.KV.get(key); return Response.json({ key, value }); } export async function PUT(request: NextRequest) { const env = process.env as any; const { key, value, ttl } = await request.json(); await env.KV.put(key, value, { expirationTtl: ttl }); return Response.json({ success: true }); }
See also:
cloudflare-kv skill for complete KV patterns
Image Optimization
Next.js image optimization works via Cloudflare Images. Configure in
open-next.config.ts:
import { defineCloudflareConfig } from "@opennextjs/cloudflare"; export default defineCloudflareConfig({ imageOptimization: { loader: 'cloudflare' } });
Usage in components:
import Image from 'next/image'; export default function Avatar() { return ( <Image src="/avatar.jpg" alt="User avatar" width={200} height={200} // Automatically optimized via Cloudflare Images /> ); }
Billing: Cloudflare Images usage is billed separately
Docs: https://developers.cloudflare.com/images/
Caching Configuration
Configure caching behavior in
open-next.config.ts:
import { defineCloudflareConfig } from "@opennextjs/cloudflare"; export default defineCloudflareConfig({ // Custom cache configuration cache: { // Override default cache behavior // See: https://opennext.js.org/cloudflare/caching } });
Default behavior: OpenNext provides sensible caching defaults
Advanced usage: See official OpenNext caching documentation
Known Limitations
Not Yet Supported
-
Node.js Middleware (Next.js 15.2+)
- Introduced in Next.js 15.2
- Support planned for future releases
- Use standard middleware for now
-
Edge Runtime
- Only Node.js runtime supported
- Remove
from your appexport const runtime = "edge"
-
Full Windows Support
- Development on Windows not fully guaranteed
- Use WSL, VM, or Linux-based CI/CD
Worker Size Constraints
- Free plan: 3 MiB limit (gzip-compressed)
- Paid plan: 10 MiB limit (gzip-compressed)
- Monitor bundle size during development
- Use dynamic imports for code splitting
Database Connections
- External database clients (PostgreSQL, MySQL) must be request-scoped
- Cannot reuse connections across requests (Workers limitation)
- Prefer Cloudflare D1 for database needs (designed for Workers)
Deployment
Deploy from Local Machine
# Build and deploy in one command npm run deploy # Or step by step: npx opennextjs-cloudflare build npx opennextjs-cloudflare deploy
Deploy from CI/CD
Configure deployment command in your CI/CD system:
npm run deploy
Examples:
- GitHub Actions:
.github/workflows/deploy.yml - GitLab CI:
.gitlab-ci.yml - Cloudflare Workers Builds: Auto-detects
npm run deploy
Environment variables: Set secrets in Cloudflare dashboard or CI/CD system
Custom Domains
Add custom domain in Cloudflare dashboard:
- Navigate to Workers & Pages
- Select your Worker
- Settings → Domains & Routes
- Add custom domain
DNS: Domain must be on Cloudflare (zone required)
TypeScript Support
Generate types for Cloudflare bindings:
npm run cf-typegen
Creates
cloudflare-env.d.ts with types for your bindings:
// cloudflare-env.d.ts (auto-generated) interface CloudflareEnv { DB: D1Database; BUCKET: R2Bucket; KV: KVNamespace; AI: Ai; }
Use in route handlers:
import type { NextRequest } from 'next/server'; export async function GET(request: NextRequest) { const env = process.env as CloudflareEnv; // Now env.DB, env.BUCKET, etc. are typed }
Testing
Local Testing (Development)
# Next.js dev server (fast iteration) npm run dev
Local Testing (Production-like)
# Workerd runtime (catches Workers-specific issues) npm run preview
Integration Testing
Always test in
preview mode before deploying:
# Build and run in workerd npm run preview # Test bindings (D1, R2, KV, AI) # Test middleware # Test API routes # Test SSR/ISR behavior
Migration from Other Platforms
From Vercel
- Copy existing Next.js project
- Run existing project migration steps (above)
- Update environment variables in Cloudflare dashboard
- Replace Vercel-specific features:
- Vercel Postgres → Cloudflare D1
- Vercel Blob → Cloudflare R2
- Vercel KV → Cloudflare KV
- Vercel Edge Config → Cloudflare KV
- Test thoroughly with
npm run preview - Deploy with
npm run deploy
From AWS / Other Platforms
Same process as Vercel migration - the adapter handles Next.js standard features automatically.
Resources
Official Documentation
- OpenNext Cloudflare: https://opennext.js.org/cloudflare
- Cloudflare Next.js Guide: https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/
- Next.js Docs: https://nextjs.org/docs
Troubleshooting
- Troubleshooting Guide: https://opennext.js.org/cloudflare/troubleshooting
- Known Issues: https://opennext.js.org/cloudflare/known-issues
- GitHub Issues: https://github.com/opennextjs/opennextjs-cloudflare/issues
Related Skills
- Base Worker setup with Hono + Vite + Reactcloudflare-worker-base
- D1 database integrationcloudflare-d1
- R2 object storagecloudflare-r2
- KV key-value storagecloudflare-kv
- Workers AI integrationcloudflare-workers-ai
- Vector database for RAGcloudflare-vectorize
Quick Reference
Essential Commands
# New project npm create cloudflare@latest -- my-next-app --framework=next # Development npm run dev # Fast iteration (Next.js dev server) npm run preview # Test in workerd (production-like) # Deployment npm run deploy # Build and deploy to Cloudflare # TypeScript npm run cf-typegen # Generate binding types
Critical Configuration
// wrangler.jsonc { "compatibility_date": "2025-05-05", // Minimum! "compatibility_flags": ["nodejs_compat"] // Required! }
Common Pitfalls
- ❌ Using Edge runtime → ✅ Use Node.js runtime
- ❌ Global DB clients → ✅ Request-scoped clients
- ❌ Old compatibility_date → ✅ Use 2025-05-05+
- ❌ Missing nodejs_compat → ✅ Add to compatibility_flags
- ❌ Only testing in
→ ✅ Always testdev
before deploypreview - ❌ Using Turbopack → ✅ Use standard Next.js build
Production Tested: Official Cloudflare support and active community Token Savings: ~59% vs manual setup Errors Prevented: 10+ documented issues Last Verified: 2025-10-21