### What?
Flips the default `experimental.instantInsights.validationLevel` from
`'manual-warning'` to `'warning'` so Cache Components apps get
instant-navigation validation across all pages by default.
### Why?
`'manual-warning'` only validates pages that explicitly export
`unstable_instant`, so apps see nothing unless they opt in. `'warning'`
is what users have been turning on manually to actually see the feature
(`v0`, `vercel-site`).
### Test coverage
- Unit: `instant-config-normalization.test.ts` pins the framework
default at `'warning'`.
- Integration: new `instant-validation-level-default/` fixture (no
`instantInsights` in config) asserts implicit dev validation fires, and
that build is unaffected.
### Collateral test fixtures
Tests whose intent is unrelated to instant validation
(router-autoscroll, owner-stack, hmr-iframe, next-image,
server-source-maps, etc.) now hit new redboxes/console warnings because
their fixtures incidentally use dynamic data. Each opts out with
`experimental: { instantInsights: { validationLevel: 'manual-warning' }
}` in `next.config`.
### Open question
With no `'off'` / `'info'` tier today, silencing Insights after this
change requires setting `validationLevel: 'manual-warning'`. Is that
acceptable? Should we inform of this anywhere?
<!-- NEXT_JS_LLM_PR -->
When `cookies()`, `headers()`, `draftMode()`, or `connection()` are
called inside `"use cache"` from third-party (ignore-listed) code, the
error stacks previously had no usable frames. The redbox showed no
source location and an empty call stack, and the build output only
showed `at ignore-listed frames`. This made it difficult to trace which
component was responsible for the invalid API usage.
This PR fixes the issue by calling React's `captureOwnerStack()` at the
`"use cache"` boundary (in `use-cache-wrapper.ts`) before entering the
cache scope, and storing the result on the cache work unit store as
`outerOwnerStack`. When an error is later thrown inside the cache scope
(e.g. from `cookies()`), `applyOwnerStack()` concatenates the inner
owner stack (from within the cache scope) with the stored outer owner
stack to reconstruct the full component tree across cache boundaries.
This also correctly handles nested `"use cache"` scopes by chaining the
outer owner stacks.
The `applyOwnerStack` function was extracted from `io-utils.tsx` into
`dynamic-rendering-utils.ts` so it can be reused by both the request API
files (`cookies.ts`, `headers.ts`, `draft-mode.ts`, `connection.ts`) and
the sync IO error handling in `io-utils.tsx`.
New test cases cover both first-party and third-party usage of all four
APIs, including a previously missing `connection()` in `"use cache"`
test case.
This PR adds error handling for nested `"use cache"` where the inner
cache has a very short lifetime (zero `revalidate` or `expire` under 5
minutes) but the outer cache doesn't have an explicit `cacheLife()`
call.
Short-lived caches become "dynamic holes" that are excluded from
prerenders. When such a cache is nested inside another `"use cache"`
without an explicit `cacheLife`, the outer cache's lifetime would
silently become short too via
[propagation](https://nextjs.org/docs/app/api-reference/functions/cacheLife#nested-caching-behavior),
which can lead to unexpected behavior. To prevent this accidental
misconfiguration, Next.js now throws an error during prerendering,
requiring developers to explicitly declare their intent by adding
`cacheLife()` to the outer cache.
The implementation tracks whether `revalidate` and `expire` were
explicitly set via `hasExplicitRevalidate` and `hasExplicitExpire` flags
on the collected cache result. Errors are wrapped with
`wrapAsInvalidDynamicUsageError` to capture proper stack traces and
prevent userland try/catch from suppressing the build error.
Documentation has been updated with a new "Prerendering behavior"
section in the `cacheLife` API reference explaining how short-lived
caches become dynamic holes, and a "Nested short-lived caches"
subsection with examples showing how to fix the error.
closes NAR-761
This PR works around a problem in the restart-on-cache-miss rendering
flow where short-lived caches wouldn't be considered dynamic if they
managed to fill microtaskily.
The problem is that:
1. If a cache fills microtaskily, it won't register as a cache miss, so
we won't restart the render
2. The logic for omitting short-lived caches only runs for cache hits
(because it expects to run in the final render), so it won't be applied
to a cache we just filled
The fix implemented here isn't ideal, but fixes the problem -- we just
make it so that all caches take at least a task to fill, and thus all
cache misses will be "noticed" and cause a restart.
This sacrifices the nice property we had before where a microtasky cache
miss wouldn't trigger a restart, but we can live with that for now for
the sake of correctness.
In production, when throwing an error in `'use cache'` at runtime, we
are currently logging the obfuscated error that React is producing when
crossing the cache-server boundary. This is not ideal for investigating
production issues so we're also logging the original error in the `'use
cache'` wrapper as a work-around. But this error does not have a digest,
which makes it non-obvious that it's the same error as the obfuscated
one.
With this PR we are fixing this by storing the original error (with a
digest) in the `reactServerErrorsByDigest` map (moved to the work
store), and retrieving it in the server environment when we log the
error. Thus the obfuscated error is not logged, and the original error
with a digest is logged instead.
In development, we keep the existing behavior of logging the transported
error, which also includes the dev-only `environmentName` property
`'Cache'`.
As part of this fix, we're consolidating the
`createFlightReactServerErrorHandler` and
`createHTMLReactServerErrorHandler` functions, which had a big overlap
and confusing names, into a single `createReactServerErrorHandler`
function.
closes NAR-536
In production, when throwing an error in `'use cache'` at runtime, we are currently logging the obfuscated error that React is producing when crossing the cache-server boundary. This is not ideal for investigating production issues so we're also logging the original error in the `'use cache'` wrapper. But this error does not have a digest, which makes it non-obvious that it's the same error as the obfuscated one.
This PR is just adding a test fixture for this case, so that we can see the changes when improving the error handling in the upstack PR.
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>
When defining a `cacheLife` with an expire time below 5 minutes or a
revalidate time of 0 we consider the `'use cache'` function dynamic
during prerendering, i.e. it will be excluded from the static shell.
Similarly, accessing runtime `params` (not defined in
`generateStaticParams`) also makes the function dynamic. Those cases
require adding a parent suspense boundary. If there's no suspense
boundary, we should show an error during prerendering at build time, as
well as in development as part of the prerender validation. The latter
is currently not working as expected when the `'use cache'` directive is
used in a page (or layout) directly. As the added snapshot tests show,
no redbox is shown in those cases. This is a known issue that we plan to
address separately.
We will also follow up with improving the error messages and docs for
those dynamic cache cases. This detail got lost in #85087.
This moves `experimental.cacheComponents` to a top level config. As part
of this, I disabled some tests in `build-output-prerender` that assert
on `cacheComponents` appearing in the experimental list. In a separate
PR, I'm going to show that Cache Components is enabled next to the
bundler info.
This also updates some docs pages to remove "experimental" language.
This PR changes the timing used in `CacheSignal.cacheReady()` to work
correctly when rendering, not just prerendering. When rendering, React
schedules new work in `setImmediate` (as opposed to using a microtask
when prerendering), and `CacheSignal.cacheReady()` needs to account for
that, because we need to wait longer to make sure that react had time to
render and thus we've started all the relevant cache reads.
Note that this PR increases the delay for both prerender _and_ render.
This is to make it easier to reason about (by not having separate
codepaths), but also to not be so tightly coupled to React pinging work
in a microtask when prerendering, in case that changes in the future.
This PR removes the deprecated sync access to Dynamic APIs.
- Removed UnsafeUnwrapped* types.
- Replaced `as unknown as UnsafeUnwrapped ` type casts to `as any` for
dev warning.
- Removed tests that expected sync access to not error.
- Removed `UntrackedExotic` functions.
- Modified tests that were accidentally doing sync access.
- Updated warnings to emphasize that it is a Promise and must be
awaited.
- Removed paragraph that states access is allowed from
`errors/sync-dynamic-apis.mdx`.
---------
Co-authored-by: Josh Story <story@hey.com>
Currently we only abort React renders that are prerendered so this
change in practice only affects prerendering. The intent is to not
report unhandledRejections that are downstream of an abort because the
signal these errors provide is very limited. If you do have unrelated
unhandled rejections they'll usually show up before aborting or in a
dynamic page render so even if they are incidentally supressed in the
prerender context you should still be able to observe them.
Arguably we should go further and never report unhandled rejections
because there are still valid patterns where you initiate work
prospectively and then abandon it by choosing a fork or by returning
early (i.e. notFound()). This change doesn't attempt to cover every
possible case.
The implementation is a bit intense. We need the ability to stop
propagating the event which is not natively supported with Node's
EventEmitter interface. Instead we patch the listener methods to
delegate the filtering to an inner listener queue. If you add/remove or
otherwise manipulate the listener list we always ensure our listener is
first and can opt to omit propagating the event. If you want to
uninstall the this patch you just need to unregister this listener
specifically which can be accessing used
`process.listeners('unhandledRejection')[0]`
This patch only exists for the node environment because we do not do any
prerendering in edge runtimes. In the future if we add aborting to
environments like edge runtime we may need an alternative solution to
this problem
Currently we error if you cannot produce a shell unless you have a
Suspense boundary above the root. This is fine for normal IO but sync IO
like Math.random() and new Date() have much more significant bad
consequences for prerendering. Instead of treating these errors as
another flavor of "must have a shell" validation we should instead treat
them like they must be guarded behind something else dynamic like `await
connection()`.
In addition to unconditionally erroring for Sync IO in Server Components
when prerendering this change also removes the Sync IO warning for
runtime prefetches. This is because at the moment there is no way to
debug these errors in dev. In the future we will add validation for
prefetches and need to add back in some ability to warn for these cases
but until then we will leave this case as a silent deopt.
In #81162 we removed the support for accessing request data
synchronously when `experimental.cacheComponents` is enabled. However,
we missed updating the `cookies()` and `headers()` promises that are
created at runtime to not be "exotic" promises, i.e. promises that also
have the cookies/headers methods defined on them. Those two cases are
now updated as well with this PR. (Params, search params, and draft mode
were already handled correctly in the original PR.)
In the next major version we'll remove the support for the legacy
synchronous access independent of the `cacheComponents` flag.
## What?
Rename `experimental.dynamicIO` to `experimental.cacheComponents` across
the Next.js codebase.
## Why?
We're going to be merging the functionality of the `ppr`, `dynamicIO`
and `useCache` experimental flags into the singular `cacheComponents`
flag to reduce complexity of the codebase and simplify adoption for
users wanting to experiment with experimental features.
## How?
- Renamed the configuration option from `experimental.dynamicIO` to
`experimental.cacheComponents`
- Added deprecation handling with automatic migration for the old option
name
- Updated all documentation, tests, and internal references
- Updated Rust code in SWC transforms and Turbopack
- Maintained backward compatibility with deprecation warnings
NAR-158