Turbopack: fix edge route handlers when cacheHandlers is configured (#100046)
### What?
Fixes Turbopack production and dev builds of App Router route handlers
with `export const runtime = 'edge'` when `cacheHandlers` is configured.
These builds currently fail with `edge-app-route.js: Expected ';', '}'
or <eof>`. Webpack builds of the same app already work.
### Why?
The webpack loader and Turbopack both fill the
`edgeCacheHandlersRegistration` injection point in the shared
`edge-app-route.ts` template, which sits at statement position after
`const edgeCacheHandlers = {}`.
- **webpack** emits assignment statements.
- **Turbopack** (`wrap_edge_route` in
`crates/next-core/src/next_app/app_route_entry.rs`) emitted
object-literal property fragments, which don't parse at statement
position.
Existing coverage missed this because the edge fixture in
`cache-handlers-upstream-wiring` only sets the singular `cacheHandler`.
The loop that generates these entries never ran for an edge route.
### How?
- **Turbopack codegen:** Turbopack now emits one assignment per
configured kind (`edgeCacheHandlers[<kind>] = cacheHandlerN`), matching
the webpack loader. Each kind is still serialized with `serde_json`. The
template and the webpack loader are unchanged, so both bundlers keep
sharing the same template contract.
- **Regression fixture:** the new `edge-with-cache-handlers` fixture has
two distinct handler modules (`default` and `custom`), one force-dynamic
edge route and no edge page. It pins `cacheComponents: false` and
`experimental.cachedNavigations: false`, because Cache Components is not
supported on the edge runtime. Without both pins, the experimental CI
shards would turn these flags on through env defaults, and the fixture
would fail config validation or the build.
- **What the test checks:** the route reads the cache-handler registry
that `EdgeRouteModuleWrapper` fills before invoking it, and returns each
handler's marker. The test asserts both markers over HTTP, so it fails
if the build breaks or if either kind is missing or swapped.
- The suite has no gates. The fixture is a valid edge app in every
shard, so the assertions run everywhere, including deploy.
- **Limits:** the test proves registration only. It doesn't exercise
`'use cache'` hits or revalidation on edge.
### Verification
| Run | Result |
|---|---|
| New test, `test-start-turbo`, unfixed published binary | ❌ fails with
the parse error above, as expected |
| New test, `test-start-turbo`, local native build with the fix | ✅
passes |
| Existing edge test (`cacheComponents disabled, edge app router`), same
fixed build | ✅ passes |
| New test, `test-start-webpack` | ✅ passes |
| New test, `test-dev-turbo` | ✅ passes |
| New test with the experimental (cache-components) CI env,
`test-start-turbo` and `test-dev-turbo` | ✅ passes |
| Standalone repro (published canary.8 + fixed native binary), `next
build` + `next start`, `GET` the edge route | ✅ builds and returns the
expected JSON |
Not run locally: deploy-mode tests (they run in CI).
Fixes #99894
<!-- NEXT_JS_LLM -->
<!-- fleet 826df9e4-b701-4396-9634-0b96a03345e7 -->
---------
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>