Summary
On a normal-sized App Router app (~600 route segments), the OpenNext server bundle (handler.mjs) is ~52.7 MB, and an esbuild metafile of the output (sums byte-for-byte to the output) shows ~64% of it is RSC client-reference-manifest plumbing, much of it duplicated. Because Cloudflare V8-compiles the whole bundle on every cold isolate, this manifest weight is a direct cold-start latency cost on every request, and it grows ~64 KB per route added.
Environment
@opennextjs/cloudflare: 1.20.0
next: 15.5.19
- App Router, ~202 page segments + ~392 API route segments (~594 total)
Composition (esbuild metafile of handler.mjs)
| Part |
Size |
Share |
per-route *_client-reference-manifest.js (395 files, ~64 KB each) |
25.4 MB |
46% |
load-manifest.external.js shim (statically require()s all 395 manifests, embedding them a 2nd time) |
8.4 MB |
15% |
page.js |
8.0 MB |
|
api route.js |
4.1 MB |
|
| chunks |
4.6 MB |
|
| Next runtime |
1.6 MB |
|
| react-dom (prod) |
0.2 MB |
|
handler.mjs total |
~52.7 MB |
|
Manifest plumbing = ~33.8 MB / 64%.
Two specific concerns
- Each per-route client-reference-manifest carries (nearly) the whole app's client-module map. Even a pure handler like
robots.txt ships a ~64 KB manifest with ~218 ssrModuleMapping + ~229 clientModules entries. With 395 manifests that's ~25 MB of largely-overlapping data. Could these reference a single shared module map instead of each inlining the full set?
load-manifest.external.js re-embeds all 395 manifests a second time (~8.4 MB) — presumably because Workers have no filesystem to read them from at runtime. This is a straight duplication of content already present in the bundle; could the shim reference the already-bundled manifests instead of inlining a second copy?
Impact
Cloudflare compiles the full handler.mjs on each cold isolate, so the ~33.8 MB of manifest plumbing is paid as cold-start latency on every cold request, and it scales linearly with route count (~64 KB/route). Deduping the shim copy alone would cut ~8.4 MB; a shared module map could cut most of the 25 MB.
Related: #1174 (large-bundle scaling, different failure mode).
Happy to share the full metafile or put together a minimal repro.
Summary
On a normal-sized App Router app (~600 route segments), the OpenNext server bundle (
handler.mjs) is ~52.7 MB, and an esbuild metafile of the output (sums byte-for-byte to the output) shows ~64% of it is RSC client-reference-manifest plumbing, much of it duplicated. Because Cloudflare V8-compiles the whole bundle on every cold isolate, this manifest weight is a direct cold-start latency cost on every request, and it grows ~64 KB per route added.Environment
@opennextjs/cloudflare: 1.20.0next: 15.5.19Composition (esbuild metafile of
handler.mjs)*_client-reference-manifest.js(395 files, ~64 KB each)load-manifest.external.jsshim (staticallyrequire()s all 395 manifests, embedding them a 2nd time)page.jsroute.jshandler.mjstotalManifest plumbing = ~33.8 MB / 64%.
Two specific concerns
robots.txtships a ~64 KB manifest with ~218ssrModuleMapping+ ~229clientModulesentries. With 395 manifests that's ~25 MB of largely-overlapping data. Could these reference a single shared module map instead of each inlining the full set?load-manifest.external.jsre-embeds all 395 manifests a second time (~8.4 MB) — presumably because Workers have no filesystem to read them from at runtime. This is a straight duplication of content already present in the bundle; could the shim reference the already-bundled manifests instead of inlining a second copy?Impact
Cloudflare compiles the full
handler.mjson each cold isolate, so the ~33.8 MB of manifest plumbing is paid as cold-start latency on every cold request, and it scales linearly with route count (~64 KB/route). Deduping the shim copy alone would cut ~8.4 MB; a shared module map could cut most of the 25 MB.Related: #1174 (large-bundle scaling, different failure mode).
Happy to share the full metafile or put together a minimal repro.