Hades Studio
Menu

Deploying Next.js to Cloudflare Workers: What We Learned

Notes from moving a Next.js site to Cloudflare Workers with OpenNext: the file system, prerendered pages, forms, email and static files, with the fixes we used.

We moved this website to Cloudflare Workers using the OpenNext adapter. The site is small, and the move still surfaced five problems that the documentation did not make obvious. This article lists each one, the symptom we saw and what fixed it.

Our setup: Next.js with the App Router, every page prerendered at build time, one API route for the contact form.

1. There is no file system at runtime

Symptom. Our content lived in Markdown files that were read with fs when a page rendered. This works on a Node.js server. On Workers, the project files are not available at runtime.

Prerendered pages hid the problem at first, because they are rendered during the build. It appeared in code that runs per request, such as a layout that checked whether any case studies existed.

Fix. Move file reading to build time. A small script reads the Markdown files and writes a JSON manifest, and the application imports that manifest as a module.

// scripts/build-content.mjs (simplified)
const manifest = {
  blog: readCollection("blog"),
  work: readCollection("work"),
};
fs.writeFileSync("src/content/generated.json", JSON.stringify(manifest));

The script runs before next build through a prebuild entry in package.json.

2. Runtime Markdown compilers are a poor fit

Symptom. We used an MDX library that compiles content when the page renders. The adapter reported errors copying its dependencies during bundling. Libraries of this kind also generate code at runtime, which Workers restrict.

Fix. Our content was plain Markdown, so we converted it to HTML in the same build script and render the HTML directly. The runtime dependency disappeared and the Worker bundle got smaller.

If your content needs interactive components inside it, compile the MDX at build time instead.

3. Prerendered dynamic routes returned 404

Symptom. Static pages such as /about worked. Pages from dynamic routes with generateStaticParams, such as /blog/[slug], returned 404, with a NoFallbackError in the logs.

Cause. The adapter serves prerendered pages from an incremental cache. With no cache configured, it tried to render the page on demand, and our routes do not allow that.

Fix. Configure the static assets incremental cache, which reads prerendered pages from the Worker's static assets:

// open-next.config.ts
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import staticAssetsIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/static-assets-incremental-cache";

export default defineCloudflareConfig({
  incrementalCache: staticAssetsIncrementalCache,
});

One more detail: the cache is populated by the adapter's preview and deploy commands. Running wrangler dev directly after a build skips that step, and the 404s remain.

4. Form posts were answered from the page cache

Symptom. Our contact form submitted to the same URL as the page that contained it. With cache interception enabled, a POST to that URL returned the cached page. The form logic never ran, and no error was shown.

Fix. Two changes. We left cache interception off, and we moved the form to its own API route, which the page cache does not touch.

// src/app/api/contact/route.ts
export async function POST(request: Request) {
  const body = await request.json();
  // validate, then send
}

A separate endpoint is also easier to test, since you can call it directly.

5. Static .html files were redirected

Symptom. Search engine ownership verification asks you to host a file such as google1234.html. Workers static assets redirected that URL to the same path without the extension, and verification could fail on the redirect.

Fix. Turn off HTML handling for assets, so that files are served at their exact path:

// wrangler.jsonc
"assets": {
  "directory": ".open-next/assets",
  "binding": "ASSETS",
  "html_handling": "none"
}

Sending email without a third-party service

Cloudflare Email Routing provides a send_email binding. If the recipient is a verified destination address on your account, a Worker can send to it with no API key:

// wrangler.jsonc
"send_email": [
  { "name": "EMAIL", "destination_address": "you@example.com" }
]

This suits contact forms, where every message goes to your own inbox. It does not replace a transactional email service for messages to your customers.

A problem that solved itself

For a few minutes after attaching the custom domain, some requests failed during the TLS handshake. The success rate rose from about half to all requests within roughly fifteen minutes, with no change on our side. If you see this straight after adding a domain, wait before you start debugging.

Was it worth it?

For a content site with one form, yes. Hosting costs are close to zero, pages are served from a global network, and the domain, DNS, hosting and email routing are managed in one place.

The cost was about half a day spent on the problems above. A larger application that relies on on-demand rendering, image optimization or scheduled revalidation would need more planning, because each of those needs its own configuration on Workers.

Checklist

Before moving a Next.js site to Workers:

  • Find every use of fs and move it to build time
  • Replace libraries that compile or evaluate code at runtime
  • Configure an incremental cache if you have prerendered dynamic routes
  • Test with the adapter's preview command, which runs in the Workers runtime
  • Put forms on their own API routes
  • Test every form in the real runtime before going live

You can read more about how this site was built in our case study. If you are planning a similar move, get in touch or see our web development services.

More articles

September 29, 2026

How Much Does It Cost to Build a Mobile App?

What drives the cost of a mobile app, how platform choice changes the budget, the costs people forget, and how to lower the price without hurting the product.

  • Mobile development
  • Pricing

Have a project in mind?

Tell us what you are building. We reply within one working day with next steps.