Serve a run of fallback shells from one route entry (#97738)
A fallback shell repeats the whole path of its source page and resolves
the leading params to concrete values. Take `/[team]/[locale]/[slug]`,
where the build prerenders three combinations of the two leading params.
It emits one entry per combination, and the entries differ only in that
leading part of the path:
```diff
- /acme/en/<slug><suffix> -> /acme/en/[slug]<matched suffix>
- /acme/de/<slug><suffix> -> /acme/de/[slug]<matched suffix>
- /globex/en/<slug><suffix> -> /globex/en/[slug]<matched suffix>
+ /(?<shellPrefix>acme/en|acme/de|globex/en)/<slug><suffix> -> /$shellPrefix/[slug]<matched suffix>
```
One entry now serves them all. Its pattern lists the leading part of
each shell path as an alternative, and its destination copies whichever
one matched.
Those alternatives are complete, and that matters. A pattern that
offered a choice per param instead, `(acme|globex)/(en|de)`, would also
match `globex/de`. The build never prerendered that combination, so a
request for it would resolve to an output that does not exist, and it
would then fall through to whichever route claims the rewritten path.
An entry serves neighbours in the manifest, and only those. It takes the
position of the first shell that it replaces, so every replaced shell
keeps its place relative to the routes around it. Any other route
between two shells ends the run, because an entry that reached across it
would move ahead of a route that a request matches first. The shells of
a run also have to agree on `fallback: false`, because an entry carries
one set of conditions.
A source page can therefore hold several runs, and a shell can belong to
none. That happens when the build resolves a different number of params
for neighbouring shells, which leaves them with different paths after
the resolved part.
This removes the multiplier that the number of prerendered combinations
applies to every route below the resolved params. We measured the same
in-progress feature branch of the v0 chat app as the previous changes in
this stack. Building it before and after this change removes 87% of the
routes that those changes left. Across the stack that branch loses 95%
of its routes.
This collapse follows `experimental.collapseAdapterRoutes`, which an
earlier change in this stack added. A build that sets it to `false`
emits one entry per shell.
**Verified with a [full deploy test
run](https://github.com/vercel/next.js/actions/runs/32577936779).**