How I dual-deploy this portfolio to Vercel and Cloudflare Workers using OpenNext — covering the edge runtime differences, environment variables, middleware quirks, and the production gotchas nobody warns you about.
When I built this portfolio, I wanted it to be fast everywhere — not just in US datacenters. Cloudflare Workers runs at 300+ edge locations globally, which means a reader in Dhaka gets the same sub-100ms response as someone in San Francisco.
The challenge: Next.js has a complex runtime and Cloudflare Workers isn't Node.js. This post documents every gotcha I hit deploying Next.js 16 to Workers using OpenNext.
@opennextjs/cloudflare — adapts Next.js build output for WorkersVercel is excellent, and I still deploy there as the primary host. But:
Dual-deploying also means I can cut over to Cloudflare if Vercel has an outage.
Bashnpm install --save-dev @opennextjs/cloudflare wrangler
The wrangler.jsonc config:
JSONC{ "name": "mhrubel-website", "compatibility_date": "2024-12-01", "compatibility_flags": ["nodejs_compat"], "main": ".open-next/worker.js", "assets": { "directory": ".open-next/assets", "binding": "ASSETS" }, "vars": { "NEXT_PUBLIC_APP_URL": "https://rubel.ractstudio.com" } }
The nodejs_compat flag is critical — it polyfills Node.js APIs that Next.js internals rely on.
JSON// package.json { "scripts": { "build": "next build", "build:cf": "npx @opennextjs/cloudflare build", "deploy:cf": "npm run build:cf && wrangler deploy", "preview:cf": "npm run build:cf && wrangler dev" } }
OpenNext transforms Next.js's output into a Worker-compatible bundle. The key transformation: route handlers become Worker fetch handlers, and static assets go to Cloudflare's asset binding.
middleware.ts vs Node.js APIsCloudflare Workers use the Web Crypto API, not Node.js's crypto module. If your middleware uses Node.js crypto (like many JWT libraries), it will fail silently at the edge.
Wrong — uses Node.js crypto:
TYPESCRIPTimport jwt from CE9178">'jsonwebtoken' // Node.js only!
Right — uses Web Crypto API:
TYPESCRIPTimport { jwtVerify } from CE9178">'jose' // Web Crypto, edge-compatible
I replaced all JWT handling with jose — it's built on Web Crypto and works identically on Node.js, Vercel Edge, and Cloudflare Workers.
Workers don't read .env.local. Variables must be in wrangler.jsonc (for non-secrets) or set via the Cloudflare dashboard/CLI (for secrets):
Bash# Set secrets via CLI (never commit these!) wrangler secret put UPSTASH_REDIS_REST_URL wrangler secret put UPSTASH_REDIS_REST_TOKEN wrangler secret put GITHUB_TOKEN
For NEXT_PUBLIC_* variables (baked in at build time), set them in wrangler.jsonc's vars block.
Standard Redis uses TCP connections — not supported in Workers. The solution is Upstash's HTTP REST API:
TYPESCRIPTimport { Redis } from CE9178">'@upstash/redis' // Uses fetch() under the hood — works everywhere const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, })
Upstash is designed for edge runtimes and has automatic global replication — reads come from the nearest Upstash region to the Worker.
Next.js's <Image> component uses a Node.js image optimization server. On Workers, you have two options:
Option A: Use Cloudflare Images (paid, but native) Option B: Configure a Vercel URL as the image loader:
TYPESCRIPT// next.config.ts const config: NextConfig = { images: { remotePatterns: [{ hostname: CE9178">'mhrubel.vercel.app' }], loader: CE9178">'custom', loaderFile: CE9178">'./lib/cf-image-loader.ts', }, }
I went with Option B — simple and free.
My GitHub Actions workflow dual-deploys on every push to main:
YAMLjobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '22' } - run: npm ci # Deploy to Vercel - run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }} # Deploy to Cloudflare Workers - run: npm run build:cf - run: npx wrangler deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
After dual-deploying:
The Cloudflare edge wins on geographic distribution. Vercel wins on debugging tooling and deploy previews.
Dual-deploying Next.js to Vercel and Cloudflare Workers is very doable in 2025. The main things to watch:
jose instead of jsonwebtoken.envThe full setup for this site is open source at github.com/mhrubel/mhrubel-website.