This PR makes Sync IO behavior more restrictive when
`partialPrefetching` is on (either globally or for a page).
- Previously, we allowed `await cookies(); Date.now()` in in segments
that won't ever be runtime prefetched. This was achieved by separating
segments into "early" and "late" stages and only erroring in the "early"
ones.
- After this PR, it will always be an error to do sync IO anywhere other
than after `io()`/`connection()`/uncached IO (i.e. outside the Dynamic
stage).
We're doing this mainly because with `partialPrefetching`, `cookies()`
resolves in App Shells, and sync IO causes a severe deopt in any
prerender. `allow-runtime` also opts a route into `partialPrefetching`,
which means that even the segments above the runtime prefetch boundary
will need an App Shell and can't tolerate Sync IO.
This PR removes all early/late stage separation, because we no longer
need to vary the Sync IO behavior on the segment. Instead, we now pick
whether a stageController should use
- `SyncIOMode.AllowedInRuntimeOrDynamic` (legacy)
- `SyncIOMode.AllowedInDynamic` (`partialPrefetching`)
- `SyncIOMode.Untracked` if it needs to opt out
I've removed the existing tests that asserted that sync IO is allowed in
segments above the `allow-runtime` boundary. Instead, we check that sync
io either errors (if partialPrefetching is on) or is allowed (if
partialPrefetching is off)
Closes NAR-855
Stacked on #89984
When we initially implemented runtime prefetching we decided to make
sync IO that happens after runtime but before async IO an error. This is
similar to but more aggressive than the error for sync IO before runtime
data but is important because if you have sync IO before uncached IO and
we are runtime prefetching the page then we aren't going to get anything
useful from that prefetch but it might be very expensive.
The problem with the current implementation is that if you turn on
runtime prefetching for a Page segment then in practice the router will
only ever runtime prefetch that page segment but the sync IO validator
will error for sync IO before uncached IO anywhere in the route. This
makes adding runtime prefetching to leaf Segments much harder b/c you
have to address sync IO in the entire Segment stack.
This change updates the sync IO heuristic to only error in segments that
would be runtime prefetched and below. This way when you add runtime
prefetching to a segment you only have to true up the sync IO for the
part of the tree that has the changed behavior.
This PR only partially implements the intended semantics for runtime
prefetching validation of sync IO. One thing that will need to be done
in a follow up is handling sync IO in generateMetadata. In this case
while the functions are defined in layouts and pages they are
semantically always "owned" by the page itself. This means that while
the layout might not be runtime prefetchable and thus allow sync IO
after runtime data access the generateMetadata in that same layout might
not allow sync IO after runtime if there is a runtime prefetch config at
the page level or some layout higher. This will be done in a follow up
With Cache Components enabled in development, fetches without an
explicit cache config were incorrectly routed through
`createCachedPrerenderResponse` during the cache filling phase due to
the `serverComponentsHmrCache` condition. This function awaits the
entire response before returning to the caller, which prevents
infinitely streaming responses from ever being read or aborted.
This is a regression that was introduced in #84088.
These fetches should be treated as dynamic and use
`createCachedDynamicResponse` instead, which returns immediately and
caches in the background with proper error handling. The fix adds
`isCacheableRevalidate` to the condition so only explicitly cacheable
fetches use the blocking path.
Additionally, the "Failed to set fetch cache" warning is now suppressed
when the request's abort signal was triggered, since aborting is
intentional.
By sending the dynamic validation errors to the browser via WebSocket,
instead of rendering a validation outlet into the dynamic dev render
stream, we can avoid artificially delaying the spawned validation to
implicitly wait for the different chunks (static, runtime, and dynamic),
without blocking the dev render stream, and instead wait explicitly for
all chunks to accumulate.
Previously, we were using React's console replaying to get full fidelity
error stacks in the browser (including inspectable virtual server
modules). Now, we're using the same underlying mechanism by sending a
separate RSC stream through the WebSocket that contains only the errors.
In the browser, the received errors are then logged with
`console.error`, which also triggers that they're displayed in a
collapsed Redbox, as was the case before with the replaying.
---------
Co-authored-by: Janka Uryga <lolzatu2@gmail.com>
Prior to this change any "hole" in a prerender that would block the
shell was considered an error and you would be presented with a very
generic message explaining all the different ways you could have failed
this validation check.
With this change we use a new technique to validate the static shell
which can now tell the difference between waiting on uncached data or
runtime data. It also improves the heuristics around generateMetadata
and generateViewport errors.
Added new error pages for runtime sync IO and ensure we only validate
sync IO after runtime data if the page will be validating runtime
prefetches.
Restored the validation on HMR update so you can get feedback after
saving a new file.
---
We've also discovered that hanging inputs are not handled correctly.
Fixing this is non-trivial and will be done in a follow-up, so for now,
we're disabling the failing tests.
---------
Co-authored-by: Josh Story <story@hey.com>
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>