Coding Dimension Logo
CodingDimension
DevOps

How to Deploy a Next.js Project on Cloudflare

Published
Aug 11, 2026
Reading Time
7 min read

Admin

Author

Deploy Next.js App Router to Cloudflare Workers with OpenNext: wrangler config, middleware.ts vs proxy.ts, secrets, Git auto-deploy, and the .npmrc peer-deps fix.

Why Cloudflare instead of Vercel

Vercel is the obvious default for Next.js — it is built by the same team. But if you are already using Cloudflare for DNS, or you want a generous free tier for a side project, Workers is a real alternative.

The catch: Next.js was not built for Cloudflare’s runtime natively, so you need a translation layer called OpenNext to turn a standard Next.js build into something Workers can run.

This guide covers a real App Router setup on Cloudflare Workers — including two mistakes that each cost a full debugging session.

Installing OpenNext for Cloudflare

Two packages get you started:

npm install --save-dev @opennextjs/cloudflare wrangler --legacy-peer-deps
  • wrangler — Cloudflare’s CLI for deploying Workers
  • @opennextjs/cloudflare — adapter that converts your .next build into a Worker

open-next.config.ts

import { defineCloudflareConfig } from "@opennextjs/cloudflare";

export default defineCloudflareConfig();

wrangler.jsonc

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "your-app-name",
  "main": ".open-next/worker.js",
  "compatibility_date": "2026-06-01",
  "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
  "assets": {
    "directory": ".open-next/assets",
    "binding": "ASSETS"
  },
  "observability": {
    "enabled": true
  }
}

The nodejs_compat flag matters more than it looks — without it, any dependency using Node built-ins like crypto or buffer fails at runtime, not build time.

Build scripts

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "cf:build": "opennextjs-cloudflare build",
    "cf:preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
    "cf:deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy"
  }
}

cf:preview is the step people skip — and it is exactly what would have caught the middleware bug below before production.

Gotcha #1: middleware.ts vs proxy.ts

On Next.js 16, the framework nudges you toward proxy.ts instead of middleware.ts for request interception.

Problem: proxy.ts runs on the Node.js runtime. Cloudflare Workers via OpenNext only supports the Edge runtime for middleware. Rename to proxy.ts, deploy, and production silently stops running your middleware — no error, no warning. Auth redirects look like a Clerk/cookie bug.

Fix: keep the file named middleware.ts.

import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";

const isDashboard = createRouteMatcher(["/dashboard(.*)"]);
const isPublicOnly = createRouteMatcher(["/", "/sign-in(.*)", "/sign-up(.*)"]);

export default clerkMiddleware(async (auth, req) => {
  const { userId } = await auth();

  if (userId && isPublicOnly(req)) {
    return NextResponse.redirect(new URL("/dashboard", req.url));
  }

  if (!userId && isDashboard(req)) {
    await auth.protect();
  }
});

// middleware.ts (not proxy.ts): Cloudflare Workers via OpenNext only supports
// the Edge middleware runtime, and Next 16's proxy.ts is Node-only.
export const config = {
  matcher: [
    "/((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
    "/(api|trpc)(.*)",
  ],
};

If you deploy Next.js 16 to Cloudflare: ignore the proxy.ts migration guide for Workers and stay on middleware.ts.

Test with the real runtime before deploying

npm run cf:build
npm run cf:preview

next dev runs on Node no matter what your production target is. cf:preview runs the built Worker through workerd — Cloudflare’s real runtime — on your machine.

Environment variables: two buckets

  • Variables — plain text. Use for NEXT_PUBLIC_* values (baked into the client bundle at build time).
  • Secrets — encrypted. Use for Clerk secret keys, deploy keys, payment keys.
npx wrangler secret put CLERK_SECRET_KEY

NEXT_PUBLIC_* values must exist at build time, not only runtime. With Cloudflare Git integration, set them in that build’s environment screen — it is separate from Worker runtime variables.

Wiring up GitHub auto-deploy

  1. Cloudflare dashboard → Workers & Pages → Connect to Git
  2. Authorize GitHub and pick the repo
  3. Build command: npm run cf:build
  4. Add every NEXT_PUBLIC_* value and secret in the build environment

After that, every push to your production branch builds and deploys automatically.

Gotcha #2: peer-dependency error only on Cloudflare

A GitHub-connected build can fail at install with a peer range error for @opennextjs/cloudflare, even when the same Next version installs fine locally. Cloudflare’s npm resolves multi-clause || peer ranges more strictly.

Add a root .npmrc:

legacy-peer-deps=true

Commit it, push, and the next auto-build gets past install cleanly.

Pitfalls worth remembering

  • middleware.ts, not proxy.ts — Next.js 16 rename is Node-only and silently no-ops on Workers
  • nodejs_compat — miss it and Node-dependent packages break at runtime
  • NEXT_PUBLIC_* must be in the build environment or the client bundle gets undefined
  • .npmrc with legacy-peer-deps=true — avoids Cloudflare install peer conflicts
  • Always run cf:preview before deploying — only local step that uses Cloudflare’s runtime

None of this is documented clearly in one place, which is why these bugs are easy to ship. Get the middleware file name and .npmrc right from the start.

#cloudflare#nextjs#opennext#workers#devops
Share:

Comments

0

Login to post a comment.

Sign in

Loading comments…