You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In production on Workers, roughly 9 out of 100 page loads log React error #418 (hydration mismatch) and React discards the server-rendered tree and re-renders on the client. The same build served locally with next start gives 0 out of 100.
Same Chrome, same Supabase backend, same account, same routes, same build output — the only variable is the runtime serving it.
The rate is stable across builds: an independent run three days earlier, on a different commit, gave 8/100 in production.
What we ruled out, each with the measurement that killed it:
Route-specific or client-component size. 25 loads on each of 4 routes. Failures spread across routes and the distribution moves between runs (run A: 2/25, 3/25, 0/25, 3/25 — run B on a later build: 2/25, 0/25, 0/25, 7/25). A route that is a pure Server Component fails; the route with the largest client island gave 0/25 in both runs. No correlation with route.
Non-deterministic server HTML. Repeated fetches of the same route return byte-identical documents (a single distinct content-length across 10 loads).
Intl / locale divergence between the Workers runtime and the browser. One consistently failing route has zero Intl/currency formatting in its tree.
Deploy-window artifact (HTML from one version, JS chunks from another). Reproduced with the Worker untouched for hours.
Cloudflare HTML rewriting (Rocket Loader, email obfuscation). No cdn-cgi markers in the document.
Our own version-skew banner (a client component that compares a build stamp). It initialises to the same state on both sides and only mutates inside useEffect, so it cannot diverge during hydration — and it did not exist when the first measurement was taken.
cacheComponents. Not enabled; our next.config.ts sets only reactStrictMode and one env value.
The one concrete difference we can measure is delivery. The Worker coalesces the stream — same route, same build, measured today:
Payload sizes match within 0.2%, which rules out "local renders less content" as the explanation for the different failure rates.
Note the inversion that kills the intuitive theory: in an earlier run the longest delivery window of all (local, ~1,050 ms first-to-last byte) never failed, while a production route arriving in ~38 ms did. So "hydration started before the RSC payload finished" does not fit the data. The coalescing is a visible symptom of something the runtime does differently, not a proven cause.
After a failing load, the first DOM difference against the server HTML is a Radix-generated id (radix-_R_… server-side vs radix-_r_0_ client-style) — that is, the tree had already been regenerated on the client. It is the consequence of the mismatch, not its origin. next dev, where React prints the named diff, does not reproduce.
Steps to reproduce
We do not have a minimal public repro yet — the failure is probabilistic (~9%) and environment-specific, so a single load usually looks fine. What reliably reproduces it on our app:
Deploy a Next.js 15 App Router app as a Worker via @opennextjs/cloudflare.
Load an authenticated page 25+ times in headless Chrome, collecting console errors on each load.
Run next start on the same build output and repeat, same browser, same account.
Two things that cost us time and are worth stating for anyone trying to confirm this:
12 loads per route is not enough. At a ~9% rate, a dozen loads routinely produces false "this route is clean" conclusions — it did for us, and it produced a route-correlation "finding" that a larger sample later demolished.
Compare against the local pair of the same build. Comparing against a different build makes the result meaningless.
We are happy to share the measurement scripts (headless Chrome loop, an RSC-delivery profiler, and a server-HTML vs post-hydration DOM differ), and can run instrumented builds if that helps narrow it down.
Expected behavior
The same build should hydrate as consistently on Workers as it does under next start — that is, no hydration mismatch on loads where the server HTML is byte-identical and the client bundle is the same.
@opennextjs/cloudflare version
1.19.11
Wrangler version
4.101.0
next info output
Operating System:
Platform: win32
Arch: x64
Version: Windows 11 Pro
Available memory (MB): 32159
Available CPU cores: 16
Binaries:
Node: 24.15.0
npm: 11.12.1
Yarn: N/A
pnpm: N/A
Relevant Packages:
next: 15.5.19
eslint-config-next: 15.5.19
react: 19.0.0
react-dom: 19.0.0
typescript: 5.9.3
Next.js Config:
output: N/A
Additional context
Impact for us is low and we are not blocked: no data loss, no broken interaction — the cost is a full client re-render and, at worst, a brief flash, on a minority of loads. We are reporting it because it is reproducible at a stable rate across builds and is specific to the Workers runtime, which makes it look like something you would want to know about rather than something we can fix on our side.
Happy to provide raw numbers, the scripts, or to test a patched build.
Is this a known issue, and is there instrumentation you would like enabled to catch the mismatch at its source?
Describe the bug
In production on Workers, roughly 9 out of 100 page loads log React error #418 (hydration mismatch) and React discards the server-rendered tree and re-renders on the client. The same build served locally with
next startgives 0 out of 100.Same Chrome, same Supabase backend, same account, same routes, same build output — the only variable is the runtime serving it.
@opennextjs/cloudflare)next start, identical buildThe rate is stable across builds: an independent run three days earlier, on a different commit, gave 8/100 in production.
What we ruled out, each with the measurement that killed it:
Intl/ locale divergence between the Workers runtime and the browser. One consistently failing route has zeroIntl/currency formatting in its tree.cdn-cgimarkers in the document.useEffect, so it cannot diverge during hydration — and it did not exist when the first measurement was taken.cacheComponents. Not enabled; ournext.config.tssets onlyreactStrictModeand oneenvvalue.The one concrete difference we can measure is delivery. The Worker coalesces the stream — same route, same build, measured today:
next start)self.__next_f.push,brgzipbrgzipPayload sizes match within 0.2%, which rules out "local renders less content" as the explanation for the different failure rates.
Note the inversion that kills the intuitive theory: in an earlier run the longest delivery window of all (local, ~1,050 ms first-to-last byte) never failed, while a production route arriving in ~38 ms did. So "hydration started before the RSC payload finished" does not fit the data. The coalescing is a visible symptom of something the runtime does differently, not a proven cause.
After a failing load, the first DOM difference against the server HTML is a Radix-generated id (
radix-_R_…server-side vsradix-_r_0_client-style) — that is, the tree had already been regenerated on the client. It is the consequence of the mismatch, not its origin.next dev, where React prints the named diff, does not reproduce.Steps to reproduce
We do not have a minimal public repro yet — the failure is probabilistic (~9%) and environment-specific, so a single load usually looks fine. What reliably reproduces it on our app:
@opennextjs/cloudflare.consoleerrors on each load.next starton the same build output and repeat, same browser, same account.Two things that cost us time and are worth stating for anyone trying to confirm this:
We are happy to share the measurement scripts (headless Chrome loop, an RSC-delivery profiler, and a server-HTML vs post-hydration DOM differ), and can run instrumented builds if that helps narrow it down.
Expected behavior
The same build should hydrate as consistently on Workers as it does under
next start— that is, no hydration mismatch on loads where the server HTML is byte-identical and the client bundle is the same.@opennextjs/cloudflareversion1.19.11
Wrangler version
4.101.0
next infooutputAdditional context
Impact for us is low and we are not blocked: no data loss, no broken interaction — the cost is a full client re-render and, at worst, a brief flash, on a minority of loads. We are reporting it because it is reproducible at a stable rate across builds and is specific to the Workers runtime, which makes it look like something you would want to know about rather than something we can fix on our side.
Happy to provide raw numbers, the scripts, or to test a patched build.
Is this a known issue, and is there instrumentation you would like enabled to catch the mismatch at its source?