Removes the "allow-runtime" prefetch config, and turns its behavior on
implicitly wherever Partial Prefetching is enabled.
The original motivation for "allow-runtime" was to give apps more
control over server costs triggered by prefetches. Until a route
explicitly opts in, prefetches would only be served from the CDN, not
from the server. The problem, though, was it was very confusing to know
when to add or remove this configuration. The incentive for many apps
was to add it everywhere, with no clear signal for when to remove it.
Our updated thinking is that Partial Prefetching itself already provides
sufficient protection against runaway prefetching costs: per-link
prefetches only happen on Link components that explicitly opt in with
the prefetch prop.
The optimizations landed earlier in this stack also make allow-runtime
less necessary: on pages where all the content is statically renderable,
prefetches are served from the static cache and no runtime request is
ever issued; only a page that accesses non-static data is prefetched at
runtime.
The upshot of this decision is that runtime versus static becomes an
internal optimization; the same content gets prefetched regardless of
whether or how Next.js is able to optimize it.
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
While force-runtime does opt you into runtime prefetching today (i.e. it
does force) the intended semantic is shifting to convey that the Segment
itself is designed for and makes sense (i.e. cost / performance
tradeoff) to runtime prefetch. In the future segments that may be
runtime prefetched might not be for various optimization reasons. We
therefore are renaming the option from force-runtime to allow-runtime.
In the future if we change allow-runtime to sometimes not runtime
prefetch we can always ship a new force-runtime as a codemod option that
recovers the current behavior.
This wording also better demonstrates why this is a feature of the
Segment and not say an option on the link like `<Link
prefetch="force-runtime" />`.
### What?
Migrate remaining direct `next-webdriver` test callers that have a
`NextInstance` to `next.browser()`, and expose the shared `Playwright`
browser type from `e2e-utils`.
### Why?
`NextInstance.browser` should be the supported browser-opening interface
for test fixtures, with `next-webdriver` kept as the private
implementation detail.
### How?
Updated affected development, e2e, and production tests to call
`next.browser()` directly, passing `baseUrl` where tests intentionally
target a manually spawned or proxied server. Shared helpers now receive
browser callbacks from the test context, and browser types import
`Playwright` from `e2e-utils` instead of deriving from `next.browser` or
importing from private paths.
<!-- NEXT_JS_LLM_PR -->
prefetching is now controlled with the prefetch export and this option
is inert on the instant export. this removes the prefetch option as a
valid property on the instant config type.
also updates the ts plugin for this export and adds the definition for
the previously landed prefetch export
"auto" mode is the default. It does not need to be explicitly exported
"force-" modes suggest overriding framework heuristics "force-disabled"
disables prefetching for this segment "force-static" forces any
prefetching of this segment to be static "force-runtime" forces any
prefetching of this segment to do runtime prefetching
It's worth noting that when runtime prefetching we fetch the necessary
segment and all child segments in a single request. This means that a
deeper segment might specify disabled or static and still get
conditionally rendered as a runtime prefetch. This was already the
behavior of runtime prefetching and not changing in this PR just
something to call out since it may be confusing to folks trying to
understand how the implementation of prefetching actually work
Allows you to opt into runtime prefetching without coupling it to the
way you configure instant validation. We do not intend to allow shipping
runtime prefetching without opting into instant validation unless you
specifically disable the validation because the cost of the runtime
prefetching is potentially high but the way you configure validation is
likely going to be pulling in lots of test code and we want to make it
easier to discern that the validation code is not going to be bundled
into production builds so separating the config is helpful.
Another reason to separate the config is that we expect that eventually
most prefetching is configured globally and you do not need to opt
specific segments into a different prefetching strategy.
Improved client component error messages to accurately describe the
constraint: these are route segment configs that require a Server
Component module, not exports that are forbidden.
Mostly mechanical rename.
Also changes the error page to `errors/invalid-instant-configuration`.
I'm not really worried about dangling links here because this is a new
API and we don't expect anyone to be using it yet.
> This place is not a place of honor... no highly esteemed deed is
commemorated here... nothing valued is here.
> What is here was dangerous and repulsive to us. This message is a
warning about danger.
you think you know when setImmediate is supposed to run? no you don't
---
This PR introduces a patch to the Node `setImmediate` builtin. The patch
is enabled by calling
`DANGEROUSLY_runPendingImmediatesAfterCurrentTask()`. All immediates
scheduled after that point (until the end of the task) will be captured
and executed right after that task (after `process.nextTick` and
`microtasks`). This applies to immediates scheduled from immediates as
well.
This is relevant when scheduling back-to-back timeouts for staged
rendering in Cache Components:
```ts
setTimeout(() => {
// runs first
DANGEROUSLY_runPendingImmediatesAfterCurrentTask() // enable the patch
setImmediate(() => {
// runs second (normally, it'd run last!)
})
})
setTimeout(() => {
// runs third
})
```
the immediate scheduled from inside the first timeout will **always**
run before the second timeout.
A side-effect of this is that `setImmediate` will no longer be
considered IO in the Cache Components rendering model, because
immediates will always run before we advance the stage (or abort a
prerender), and thus can't result in a dynamic hole. This brings the
runtime behavior in line with React Devtools, which does not show
`setImmediate` as IO.
This patch also has some observable differences in behavior from native
`setImmediate`, mostly to do with uncaught errors:
1. sync errors in `process.nextTick` no longer interrupt
`processTicksAndRejections` (which would make us move onto the next
event loop step, and run the rest of the nextTick queue after the next
task, breaking our scheduling). They're rethrown in a microtask, which
changes the timing of `uncaughtException` a bit.
2. unhandled rejections will trigger `unhandledRejection` after _all_
fast immediates are done executing, not after each immediate. This
happens because our userspace immediate scheduling relies on nextTick,
and [rejections are only processed after everything else in
`processTicksAndRejections` is
done](https://github.com/nodejs/node/blob/d546e7fd0bc3cbb4bcc2baae6f3aa44d2e81a413/lib/internal/process/task_queues.js#L104-L105).
We hope that these divergences in behavior are niche enough to not
affect any real world code. Both are potentially fixable by managing our
own nextTick queue, more, but we'll try to avoid that complexity for
now.