Repository navigation
A single server reference is duplicated across all 1,600+ pages — server-reference-manifest scales O(refs × pages), reaching 170MB+ with output: 'standalone' (Next 16, Cache Components)
#95241
Replies: 1 comment 2 replies
|
Update: opened a fix PR — #95242. Root cause located in the Turbopack emitter ( The PR drops the two redundant per-worker fields so worker entries are just Would appreciate a review / CI run — and if any manifest snapshot fixtures hard-code the per-worker fields, happy to update them. |
Uh oh!
There was an error while loading. Please reload this page.
This is performance/design feedback from a large production App Router app (not a minimal repro — the codebase is proprietary, but every number below is from a real
next build). The ask: theserver-reference-manifestscalesO(server-references × pages)and reaches 170MB+, which seems like a manifest-encoding problem worth revisiting.Environment
16.2.9(Turbopack)output: 'standalone'cacheComponents: true(Cache Components /'use cache')'use server') and'use cache'Measured artifact sizes (single production build)
.next/server/server-reference-manifest.js.next/server/server-reference-manifest.json.next/standalone/.../server/server-reference-manifest.{js,json}server-reference-manifest.json(1,703 files)≈ 1 GB of a 2.5 GB standalone image is just these manifests.
Root cause — it scales
O(references × reachable-pages)Each server-reference — every
'use server'action and every'use cache'function ($$RSC_SERVER_CACHE_*) — gets aworkersmap with one object per page route that can reach it, and the fullfilename/exportedNamestrings are repeated verbatim in every object:Measured on this app:
workersentries (~290 bytes each ≈ 170 MB).'use cache'function is registered on 1,638 pages (≈ every route in the app).'use server'/ ~49%'use cache'— so adopting Cache Components makes this larger, not smaller, as more reads move to'use cache'.A reference becomes reachable from "every page" very easily — anything pulled in through a shared RPC/router client, a global provider, or a root layout — at which point its entry count jumps to ~
N_routes.Why this looks like a manifest-design issue, not just "a big app"
filenameandexportedNamestrings are stored thousands of times instead of being interned. A string table + indices, or inverting the structure toreference → [pageIndex], would cut the size roughly an order of magnitude with no information loss..next/standalonein addition to.next/server.references × pages, it explodes precisely on the large apps thatoutput: 'standalone'is meant for, and grows further as theunstable_cache → 'use cache'migration completes.How to measure this on any large app
No minimal repro needed — any App Router app on
output: 'standalone'+cacheComponents: truewith many Server Actions /'use cache'functions reachable from many routes will show it. Countworkersentries:python3 -c "import json;m=json.load(open('.next/server/server-reference-manifest.json'))['node'];print('refs',len(m),'entries',sum(len(v['workers']) for v in m.values()))"Suggested directions
filename/exportedNameinto a lookup table, or invert toreference → [pageId]so each route and filename is stored once..next/standalone.O(refs × pages)blob.Related
.nextfolder size growth in Next.js 16 (different cause: segment artifacts, not the reference manifest).Is the
O(refs × pages)encoding intended, or is there appetite for normalizing / de-duplicating this manifest? Happy to share more measurements.All reactions