vercel-to-cloudflare-workers
npx skills add https://github.com/drewkwak/skills --skill vercel-to-cloudflare-workers
Skill 文档
Vercel â Cloudflare Workers Migration
Migration Decision Tree
-
Determine app type:
- Static site / SPA (React, Vue, no SSR) â See Static & SPA Migration
- Next.js with SSR/ISR/API routes â See Next.js Migration (OpenNext)
- Other SSR frameworks (Astro, Remix, SvelteKit) â See Other Frameworks
- API-only (serverless functions) â See API Routes Migration
-
Identify storage dependencies â See
references/storage-migration.md -
Audit Node.js API usage â See
references/nodejs-compat.md -
Plan DNS cutover â See DNS & Domain Cutover
Critical Prerequisites
Before any migration work:
// wrangler.jsonc â minimum viable config
{
"name": "my-app",
"compatibility_date": "2025-04-01", // CRITICAL: â¥2025-04-01 enables process.env auto-population
"compatibility_flags": ["nodejs_compat"],
"observability": { "logs": { "enabled": true } }
}
Why these values matter:
compatibility_dateâ¥2024-09-23: enablesnodejs_compat_v2automatically (Buffer, AsyncLocalStorage, etc.)compatibility_dateâ¥2025-04-01: enablesprocess.envpopulation as default (without this, all env vars are empty unless accessed viaenvparameter)nodejs_compatflag: reduces bundle bloat by using Cloudflare’s built-in Node.js polyfills instead of bundling themobservability: essential for debugging â without it, errors are invisible
Environment variables â the #1 silent killer:
- Vercel:
process.env.MY_VARworks everywhere - Workers:
process.env.MY_VARworks ONLY with compatibility_date ⥠2025-04-01 - Workers (older compat dates): must use the
envparameter from fetch handler:export default { async fetch(req, env, ctx) { env.MY_VAR } } - Local dev: use
.dev.varsfile (NOT.env) - Secrets:
npx wrangler secret put SECRET_NAME
Static & SPA Migration
Simplest migration path. No server-side code involved.
- Get build command and output directory from Vercel dashboard â Settings â Build & Development Settings
- Create
wrangler.jsonc:
{
"name": "my-app",
"compatibility_date": "2025-04-01",
"assets": {
"directory": "./dist", // your build output directory
"not_found_handling": "single-page-application" // SPA client-side routing
// Use "404-page" for static sites with custom 404
}
}
- Build and deploy:
npx wrangler deploy - Connect GitHub repo via Cloudflare dashboard for CI/CD (Workers & Pages â Create â Connect to Git)
Vercel config mappings:
vercel.jsonrewritesâ handled bynot_found_handling: "single-page-application"vercel.jsonredirectsâ implement in Worker code or use_redirectsfilevercel.jsonheadersâ implement in Worker code or use_headersfile (no.txtextension)
Next.js Migration (OpenNext)
OpenNext is the only recommended path for full Next.js apps on Cloudflare. It supports App Router, ISR, image optimization, and server-side features. Do NOT use @cloudflare/next-on-pages for new migrations (legacy, edge-runtime only).
Step 1: Install dependencies
npm install @opennextjs/cloudflare@latest
npm install -D wrangler@latest
Step 2: Add build scripts to package.json
{
"scripts": {
"build": "next build",
"build:worker": "opennextjs-cloudflare 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"
}
}
Step 3: Create open-next.config.ts
// open-next.config.ts
import type { OpenNextConfig } from "@opennextjs/cloudflare";
const config: OpenNextConfig = {
default: {
override: {
wrapper: "cloudflare-node",
converter: "edge",
incrementalCache: "dummy", // CRITICAL: without this, static pages aren't cached
tagCache: "dummy", // and Worker falls back to dynamic SSR for everything,
queue: "dummy", // causing cryptic 500 errors
},
},
};
export default config;
â Add
open-next.config.tsto yourtsconfig.jsonâexcludearray to avoid type conflicts.
Step 4: Create wrangler.jsonc
{
"name": "my-nextjs-app",
"main": ".open-next/worker.js",
"assets": { "directory": ".open-next/assets" },
"compatibility_date": "2025-04-01",
"compatibility_flags": ["nodejs_compat"],
"observability": {
"logs": {
"enabled": true,
"head_sampling_rate": 1,
"invocation_logs": true
}
}
}
Step 5: Handle common Next.js gotchas
Read references/nextjs-gotchas.md for the full list. Critical items:
- Bundle size limit: 3 MiB (free) / 10 MiB (paid) compressed. Analyze with
references/bundle-size.mdif exceeded. - next/image: OpenNext supports image optimization. If using older adapter, see
references/nextjs-gotchas.md. - Turbopack:
next build --turbois NOT supported by OpenNext. Usenext build. - Next.js 15/16 params:
paramsandsearchParamsare now Promises â mustawaitthem. - Database connections: Cannot reuse across requests. See I/O Isolation.
Step 6: Deploy
npm run deploy
Other SSR Frameworks
Astro, Remix, SvelteKit, and Nuxt all have official Cloudflare adapters:
- Astro:
@astrojs/cloudflare - Remix:
@remix-run/cloudflare - SvelteKit:
@sveltejs/adapter-cloudflare - Nuxt:
nitrowithcloudflarepreset
Each uses the same wrangler.jsonc pattern from Critical Prerequisites.
API Routes Migration
For standalone API routes (no framework SSR), use Hono as an Express replacement:
import { Hono } from "hono";
const app = new Hono();
app.get("/api/users", async (c) => {
// c.env gives you bindings (KV, D1, R2, etc.)
const data = await c.env.DB.prepare("SELECT * FROM users").all();
return c.json(data);
});
export default app;
Key differences from Express:
- No
require()â ESM imports only - No
app.listen()â Workers handle routing c.envreplacesprocess.envfor bindings- Use
c.executionCtx.waitUntil()for background work (replaces fire-and-forget patterns)
I/O Isolation (Database Connections)
This is the most common runtime error after migration.
Workers use V8 isolates â each request gets its own execution context. You CANNOT share I/O objects (DB connections, streams, response bodies) across requests.
â Error: Cannot perform I/O on behalf of a different request.
Wrong â global connection:
const client = postgres(process.env.DATABASE_URL); // shared across requests
export { client };
Right â per-request connection:
export function getDb() {
return postgres(process.env.DATABASE_URL); // new connection each request
}
For connection pooling, use Cloudflare Hyperdrive. See references/storage-migration.md.
DNS & Domain Cutover
Workers Custom Domains requires the domain as a zone on your Cloudflare account (nameserver delegation).
- Add domain to Cloudflare as a zone (change nameservers at registrar)
- Wait for nameserver propagation (minutes to 48 hours)
- Deploy Worker and add custom domain via dashboard or wrangler
- Verify Worker serves correctly on the new domain
- Remove old Vercel DNS records (A, CNAME)
- Delete Vercel project after confirming everything works
Zero-downtime approach: Deploy Worker on *.workers.dev subdomain first. Test thoroughly. Switch DNS only after full validation.
Common Migration Errors
See references/error-guide.md for complete reference with code examples.
| Error | Cause | Fix |
|---|---|---|
fs is not defined |
File system access | Use KV/R2 for storage |
Buffer is not defined |
Missing Node.js global | import { Buffer } from "node:buffer" |
process.env undefined |
Wrong env access | Set compat date ⥠2025-04-01 or use env param |
setTimeout not returning |
Lambda async pattern | Use ctx.waitUntil() |
require() not found |
CommonJS module | Convert to ESM import |
Exceeded CPU time |
Long computation | Chunk work, use Durable Objects or Queues |
body already consumed |
Reading body twice | req.clone() before reading |
Cannot perform I/O |
Shared connection | Create DB client per-request |
FinalizationRegistry not defined |
Old compat date | Set compat date ⥠2025-05-05 |
Worker exceeded size limit |
Bundle too large | See references/bundle-size.md |
Cannot find module |
Missing polyfill | Check Workers Node.js compat matrix |
Reference Files
Load as needed â do NOT read all at once:
references/storage-migration.mdâ Migrating Vercel KV/Postgres/Blob/Edge Config â Cloudflare KV/D1/R2/Hyperdrivereferences/nodejs-compat.mdâ Node.js API audit, module resolution, polyfill patternsreferences/error-guide.mdâ Detailed error reference with fix code for each common errorreferences/nextjs-gotchas.mdâ Next.js-specific: image optimization, caching, middleware, ISRreferences/bundle-size.mdâ Diagnosing and fixing Worker bundle size limit issues