fix(turbopack): dedupe filename/exportedName in server-reference-manifest worker entries (#95242)
### What
Turbopack's `server-reference-manifest` emitter stores `filename` and
`exportedName` **twice**: once hoisted to the action-entry level
(correct), **and** redundantly inside *every* per-page
`ActionManifestWorkerEntry`. This PR removes the per-worker copies so
the worker map only carries `{ moduleId, async }` — matching the webpack
backend, which already emits them at the entry level only.
The emitter even comments the intent (`// Hoist the filename and
exported_name to the entry level`) and does the hoist — the per-worker
fields are vestigial.
```rust
// crates/next-api/src/server_actions.rs
entry.workers.insert(&key, ActionManifestWorkerEntry {
module_id: loader_id.clone(),
is_async: …,
exported_name, // ← removed (already on entry)
filename: filename.as_ref(), // ← removed (already on entry)
});
entry.exported_name = exported_name; // hoist (kept)
entry.filename = filename.as_ref(); // hoist (kept)
```
### Why it matters
`filename` and `exportedName` are invariant per action, but the worker
map has **one entry per page that can reach the action**, so these
strings get repeated `O(actions × pages)` times. On a large App Router
app (`output: 'standalone'`, `cacheComponents: true`) this is the
dominant cost of the manifest:
- ~1,650 routes, **1,847 references → 589,349 `workers` entries**
- `server-reference-manifest.json` = **170 MB** / `.js` = **182 MB**
(plus the duplicate copy under `.next/standalone`, plus ~314 MB of
per-route manifests)
- A single `'use cache'`/action reachable from a shared client is
registered on **1,638 pages**, each repeating the full `filename` string
Dropping the two redundant per-worker fields removes the largest
repeated strings from every worker entry, shrinking the manifest
substantially with no information loss.
### Why it's safe (consumer-compatible)
- The webpack backend already emits worker entries as `{ moduleId, async
}` only, with `filename`/`exportedName` at the entry level — so this
aligns Turbopack with webpack, not introduces a new shape.
- The only first-party reader,
`packages/next/src/server/mcp/tools/get-server-action-by-id.ts`, reads
`entry.filename` / `entry.exportedName` (entry level) and types
`workers` as `Record<string, any>` — it never reads the per-worker
copies.
### Changes
- `crates/next-core/src/next_manifests/mod.rs` — drop `exported_name` +
`filename` from `ActionManifestWorkerEntry` (keep `module_id`,
`is_async`).
- `crates/next-api/src/server_actions.rs` — stop assigning them in the
worker insert (entry-level hoist unchanged).
### Notes
- Verified by reading the emitter, the manifest types, and the consumer;
I don't have a local Turbopack build, so CI is the source of truth here
— if any manifest **snapshot fixtures** include per-worker
`filename`/`exportedName`, they'll need regenerating.
- Context / measurements: vercel/next.js#95241.