server-reference-manifest grows to 170MB+ and scales O(server-references × pages) with output: 'standalone' (Next 16, Cache Components)
invalid link
### Summary
After `next build` with `output: 'standalone'`, the **`server-reference-manifest`** dominates the build output, scaling **`O(server-references × reachable-pages)`** and reaching **170MB+** for a single manifest — plus it is duplicated into `.next/standalone`, plus a per-route copy is emitted for every route. Together these manifests account for roughly **1GB of a 2.5GB standalone image**.
### Environment
- **Next.js**: `16.2.9` (Turbopack)
- `output: 'standalone'`
- `cacheComponents: true` (Cache Components / `'use cache'`)
- Large App Router app: ~1,650 routes, heavy use of Server Actions (`'use server'`) and `'use cache'`
### Measured artifact sizes (single production build)
| artifact | size |
|---|---|
| `.next/server/server-reference-manifest.js` | **182 MB** |
| `.next/server/server-reference-manifest.json` | **170 MB** |
| duplicate copy in `.next/standalone/.../server/server-reference-manifest.{js,json}` | **+352 MB** |
| per-route `server-reference-manifest.json` (1,703 files) | **~314 MB** |
≈ **1 GB of a 2.5 GB standalone image** is just these manifests. A healthy manifest should be KB-sized.
### Root cause — it scales `O(references × reachable-pages)`
Each server-reference — **every `'use server'` action and every `'use cache'` function** (`$$RSC_SERVER_CACHE_*`) — gets a `workers` map containing **one object per page route that can reach it**, and the full `filename` / `exportedName` strings are repeated verbatim in every object:
```jsonc
"<refId>": { "workers": {
"app/(dashboard)/.../a/page": { "moduleId": 160630, "async": true, "exportedName": "x", "filename": "apps/web/src/.../x.ts" },
"app/(dashboard)/.../b/page": { "moduleId": 432536, "async": true, "exportedName": "x", "filename": "apps/web/src/.../x.ts" },
// ... repeated for ~1,600 routes that can reach this one reference
}}
```
Measured on this app:
- **1,847 distinct references → 589,349 total `workers` entries** (~290 bytes each ≈ 170 MB).
- A **single** `'use cache'` function is registered on **1,638 pages** (≈ every route in the app).
- The split is ~**51% `'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" easily — e.g. anything pulled in through a shared RPC/router client, a global provider, or a layout — at which point its entry count jumps to ~`N_routes`.
### Why this appears to be a manifest-design issue, not just "a big app"
1. **Redundant encoding.** The same `filename` and `exportedName` strings are stored thousands of times instead of being interned. A string table + indices, or inverting the structure to `reference → [pageIndex]`, would cut the size roughly an order of magnitude with no information loss.
2. **Shipped twice.** The multi-hundred-MB global manifest is copied verbatim into `.next/standalone` in addition to `.next/server`.
3. **Super-linear growth.** Because it is `references × pages`, it explodes precisely on the large apps that `output: 'standalone'` is meant for, and it grows further as the `unstable_cache → 'use cache'` migration completes.
### Reproduction
Any App Router app built with `output: 'standalone'` + `cacheComponents: true` that has a large number of Server Actions and/or `'use cache'` functions reachable from a large number of routes (e.g. via a shared server RPC/router client or global providers). Inspect `.next/server/server-reference-manifest.json` and count `workers` entries:
```bash
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
- **Normalize the manifest**: intern `filename`/`exportedName` into a lookup table, or invert to `reference → [pageId]` so each page route and filename is stored once.
- **Do not duplicate** the global manifest into `.next/standalone`.
- **Per-route lazy loading**: rely on the already-emitted per-route manifests instead of also shipping one global `O(refs × pages)` blob.
### Related
- vercel/next.js#86320 — `.next` folder size growth in Next.js 16 (different cause; segment artifacts, not the reference manifest).
关闭于 2026-06-27 1 条评论