I run Galaxy Movies, a Next.js Pages Router app, on Cloudflare Workers via @opennextjs/cloudflare. When I migrated off Vercel, every smoke test passed: pages returned 200, the HTML looked right, the APIs responded. Then Playwright ran against the built bundle and three bugs fell out, all invisible to curl. If you deploy Next.js through any adapter that re-bundles your code, these are worth knowing about.
1. Never build a <script> from fn.toString()
A common theme-init trick is rendering an inline script whose source is a function's .toString() output so that you can write the script as real code instead of a quoted string.
OpenNext builds the worker with esbuild's keepNames enabled, which rewrites function definitions into __name(fn, "fnName") calls to preserve .name through minification. That helper exists inside the worker bundle. Once you stringify the function and ship it as script source, the browser gets a call to __name, which doesn't exist there, and the page crashes before React mounts.
I fixed it by deleting next-themes entirely: the app is dark-only, so _document now stamps class="dark" and color-scheme statically and no runtime script is needed at all.
The general rule: after a bundler touches your code, .toString() output isn't your code anymore. If you need an inline script, write it as a string literal.
2. Locale-dependent text hydration-mismatches
SSR inside workerd always runs in UTC. Any toLocaleDateString, toLocaleString, or new Date()-derived label renders a different string on the server vs. in the browser for a viewer in another timezone near midnight. That's React hydration error #418, a silent text mismatch.
Every locale-sensitive format now goes through a shared helper pinned to timeZone: 'UTC' so server and client agree. The alternative is rendering that text client-only, but a #418 buried in one corner of the page is a miserable bug to hunt down. Pinning beats suppressing.
3. notFound doesn't use your custom 404
On dynamic routes, getServerSideProps returning notFound: true renders Next's built-in 404 under the OpenNext adapter, not pages/404.tsx. The same code renders your custom page in next dev and on Vercel. If your e2e asserts your branded 404, it only fails on the built bundle. Ours now assert status code plus generic not-found copy.
The real lesson
All three bugs share a shape: the server output was correct, and the failure lived in the client bundle or the adapter gap. Status codes can't see that. The setup that catches it now is boring but effective: build the production bundle, boot it under wrangler dev, and run the Playwright suite against it with a fixture that fails the test on any uncaught pageerror. That job runs daily in CI.
Repo if you want the details: https://github.com/NickFasulo/galaxy-movies.
The wrangler-booted smoke script is npm run smoke:workerd, and the e2e variant is npm run test:e2e:workerd.
Top comments (0)