[PPF] `ensureStatic = "navigation"` (#99171)
Closes NAR-930
Initial implementation of `ensureStatic = "navigation"`
### Behavior
This version of `ensureStatic = "navigation"` makes all prerenders
blocking. All params must be specified via `generateStaticParams`,
similar to `output: "export"`. This is meant to ensure that we don't
serve a fallback + a resume for a param that wasn't prerendered, which
would violate the expectation that `ensureStatic = "navigation"` means
no per-request compute, which is what we're trying to provide.
Note that Client-only IO (like `use(io())` or `useSearchParams()`) is
allowed, because it doesn't require a resume render -- we serve HTML
with fallbacks and render the missing content in the browser. (this is
existing behavior and not changed here)
Earlier versions of this PR attempted to allow fallback shells if they
managed to produce a complete RSC result, e.g. on a route with params
that are only used via `useParams()`, where could use the same browser
bailout trick as other client IO. This proved complex to implement
correctly, so we'll revisit that in the future
### Prerender output changes
We skip producing all fallback shells except the most generic one in
`buildAppStaticPaths`. Initially, i wanted to remove all of them, but it
seems like the deploy output is incorrect if we don't have segment data
from a fallback prerender. We also error for incomplete/missing
`generateStaticParams`, which makes it impossible to create a
purely-fallback param.
### Validation
This PR extends the Static Shell Validation logic to be more restrictive
when `ensureStatic = "navigation"`. We can't allow _any_ usages of
dynamic data, even when wrapped with suspense. The new logic is
expressed in `trackDynamicAccessInStaticRoute` +
`getDisallowedReasonsInStaticRoute` which we use both in dev and prod.
we check whether the server prerender was partial (which is already an
error) and then run static shell validation so we can report where the
hole was. This has some drawbacks:
- if we have both server and client holes we might misreport the client
ones as dynamic data. I have some ideas for how to address this, but for
now this is the best we can do. (see `mixed server and client holes` in
`ensure-static-navigation.test.ts` for examples)
- if the client got a dynamic data promise but didn't `use()` it during
the prerender (maybe because it was just seeding a cache for use in the
browser), we won't have a dynamic hole to point to. in that case we'll
error with a generic error message instead (see
`errorPattern.body.withoutLocation` in
`ensure-static-navigation.test.ts`).
i've added new error messages to match the constraints of `ensureStatic
= "navigation"`. we no longer recommend `instant = false` or `suspense`
for dynamic data, because that's not a valid fix. there's a new
suggestion: "make your \`params\` static via `generateStaticParams\`".
(note that the messages don't have the "pretty" errors with suggestion
cards yet, just the string message. i will update the dev error logic in
a follow-up. docs are needed as well)
i've also done a bit of cleanup: `trackDynamicHoleInStaticShell` and
`trackDynamicHoleInRuntimeShell` are now one function that takes a
`kind` parameter, because they only differed on the error message
variant they reported (runtime vs dynamic).