The `use-cache-dev` suite only covers dev-mode HMR behavior of `use
cache`, so it belongs under `test/development/app-dir` alongside the
other `cache-components-dev-*` suites rather than in `test/e2e`. Because
`test/development` runs only in dev mode and is never deployed, the
production-mode placeholder guard, the `skipDeployment` option, and the
`if (skipped)` early return are all dropped. The three test manifests
that referenced the old path are updated.
---
<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
This is relevant if the cache is in a broken state or the keys or
entries are not suitable (e.g. an entry that's to large to be stored).
Revealed several oddities:
1. cache handler have no no sources content (not too bad since that
means the codeframe points into the problem `use cache` function which
is usually the cause)
2. A throwing `set` is swallowed in dev
3. A throwing `get` is logged as a caught error and an unhandled
rejection. This is spammy.
Extracted from https://github.com/vercel/next.js/pull/95468.
We did not have error handling around async module initialization. As a
result, in `output: 'export'` mode, an error during async initialization
were reported as unhandled rejections. Now it properly fails the build.
See https://github.com/vercel/next.js/pull/95553 for an explanation of
why we want to split these up. TL;DR it makes CI faster and saves us
some money.
In CI we have to run tests at the suite/file granularity, so if a test
suite is too long it limits parallelism and if it has to be retried, we
have to retry the full suite.
The React Server Components transform validates route segment configs
(rejecting `runtime`, `dynamic`, `revalidate`, and friends under Cache
Components) only for `page`, `layout`, and `route` entries, so metadata
convention files slipped past the check. Because these files compile to
route handlers and accept the same configs, this change extends the
match to `icon`, `apple-icon`, `opengraph-image`, `twitter-image`,
`sitemap`, `robots`, and `manifest`. Exporting `runtime = 'edge'` (or
any other unsupported config) from one of them is now a compile-time
error that matches the behavior for regular routes, instead of surfacing
later as a confusing bundling failure. The `metadata-font` suite, which
intentionally builds edge-runtime image routes, joins the other edge
metadata suites that are excluded from the Cache Components test run.
`experimental.useExperimentalReact` names the React *build* it pulls in
rather than the feature it's there for. Rename it to `blockingSSR` — the
actual capability (React's blocking SSR, which emits `<link
rel="expect">` to hold first paint until the streamed shell is coherent)
— mirroring React's own feature flag so it's clear why the experimental
channel got enabled when grepping.
<!-- NEXT_JS_LLM_PR -->
Opting into React's experimental channel — which emits `<link
rel="expect">` to hold first paint until the streamed shell is coherent,
avoiding flicker from partially-streamed HTML — currently requires
enabling an unrelated feature like `experimental.taint` as a side
effect, which is confusing. This adds
`experimental.useExperimentalReact` as a direct opt-in.
It feeds the existing `needsExperimentalReact` aggregation and the
matching Turbopack `react_channel` switch, selecting the
`react@experimental` build the same way `taint`, `transitionIndicator`,
and `gestureTransition` do. It's opt-in only: an explicit `false` can't
disable the channel when one of those still requires it (the taint APIs
only exist in the experimental build), so `assignDefaults` warns on that
contradiction. Covered by a webpack e2e test mirroring the existing
`taint` channel test.
<!-- NEXT_JS_LLM_PR -->
The fixture's `next.config.js` had `output: 'standalone'` hardcoded,
which is meaningless on Vercel deployments (the standalone directory is
ignored) but became a build-time crash after #93684 made the Turbopack
NFT generator skip `next-server.js.nft.json` whenever an adapter is
configured. With `output: 'standalone'` still active,
`writeStandaloneDirectory` then fails with `ENOENT` when it tries to
read that file
([x-ref](https://github.com/vercel/next.js/actions/runs/26006483168/job/76441905221)).
This PR splits the `og-api` E2E suite into a default variant and a
`standalone.test.ts` wrapper that enables `output: 'standalone'` via the
`TEST_OUTPUT_STANDALONE` env var, and excludes the standalone wrapper
from the deploy test manifest.
The default variant still runs in `deploy` mode and preserves coverage
of `next/og` from Pages Router API routes, App Router route handlers
(edge and node), and middleware — paths not covered by the metadata-*
fixtures. The standalone variant continues to exercise the trace-copy
assertion in `next start` mode.
---------
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
### What?
Converts every test under `test/integration/` to an isolated test
running through `nextTestSetup` (under `test/e2e/`, `test/production/`,
`test/development/`, or `test/unit/`), then deletes `test/integration/`
along with the legacy CI orchestration that was specific to it.
- `test/integration/` removed entirely (~327 test suites)
- New isolated suites added across the existing folders:
- `test/e2e/` — 175
- `test/production/` — 130
- `test/development/` — 43
- `test/unit/` — 1
- `.github/workflows/build_and_test.yml` and `run-tests.js` no longer
have any `integration` branches
- `nextTestSetup` gained a `baseUrl` option on `next.browser()` so a
small number of tests that drive their own proxy/static-export server
can keep using `next.browser(...)` instead of importing `next-webdriver`
directly
### Why?
`test/integration/` predated `nextTestSetup` and ran tests directly
against the source checkout via custom helpers (`launchApp`,
`nextBuild`, `nextStart`, `runNextCommand`, `webdriver`, `fetchViaHTTP`,
…). Each suite hand-rolled its own dev/start/build orchestration,
fixture mutation, and process management.
The isolated test model used by the rest of the repo gives each suite an
isolated working directory containing a packed `next.tgz` install, a
uniform `next.start()` / `next.build()` / `next.fetch()` /
`next.browser()` API, and the same lifecycle for dev, start, and deploy
modes — so a single set of assertions covers all three. Deploy-mode
skips and per-feature gates are expressed declaratively
(`skipDeployment`, `disableAutoSkewProtection`, `if (skipped) return`)
instead of branching on `process.env`.
Removing `test/integration/` lets us:
- Delete the bespoke orchestration code in the CI workflow and
`run-tests.js`
- Run every converted suite consistently in dev, start, and deploy modes
(where applicable)
- Reproduce every test locally with the same `pnpm
test-{dev,start}-{turbo,webpack}` commands; no separate `integration`
path
- Open the door to running `test/production` against deployments in the
future (the converted suites already declare `skipDeployment` so they
can be flipped on)
### How?
Mechanical conversion per suite, with targeted clean-ups:
1. **Per-suite conversion.** Each
`test/integration/<name>/test/index.test.{js,ts}` was rewritten into a
single `<name>.test.ts` under the right folder based on what the
original exercised:
- `launchApp` / dev-only assertions → `test/development/`
- `nextBuild` + `nextStart` / start-only assertions → `test/production/`
- Both → `test/e2e/`
- The one pure jsdom render check (`link-without-router`) → `test/unit/`
2. **API mapping.** Custom helpers were replaced by `nextTestSetup`
equivalents: `launchApp` → `next.start()`, `nextBuild` → `next.build()`,
`runNextCommand` → `next.runCommand`, `fetchViaHTTP` → `next.fetch`,
`webdriver(...)` → `next.browser(...)`. Fixture mutations switched from
raw `fs.writeFile`/`fs.rename` to `next.patchFile` (with the 3-arg
`runWithTempContent` callback when the change has a defined scope) and
`next.deleteFile`.
3. **Deploy-mode handling.** Suites that can't run in deploy mode (use
`patchFile` / `next.build()` / depend on local CLI output) declare
`skipDeployment: true` and early-return on the `skipped` boolean. Suites
where Vercel's edge mutates URLs (`&dpl=`, immutable assets) declare
`disableAutoSkewProtection: true`.
4. **`next.browser({ baseUrl })`.** A handful of tests
(`prerender-export`, `cdn-cache-busting`, `preload-viewport`, both
`react-virtualized` suites) need to drive a separate server (a
static-export server or an `http-proxy` instance) rather than the
Next.js process. Instead of importing `next-webdriver` directly, those
tests now pass `{ baseUrl: <port|url> }` to `next.browser()`. For the
proxy cases, the proxy was moved into `server.js` inside the fixture and
`http-proxy` declared via the `dependencies` option of `nextTestSetup`,
so the test runs with a fully isolated dependency graph.
5. **CI clean-up.** With `test/integration` gone, the `test
integration*` jobs and `integration-tests-manifest`-related logic in
`.github/workflows/build_and_test.yml` were removed, and `run-tests.js`
no longer has the `integration` test-folder branch.
6. **Validation.** The PR was iterated against multiple full CI runs;
the remaining failures on the latest run are pre-existing flakes
(segment-cache 60s `act` timeouts in turbopack-prod) or transient
infrastructure issues unrelated to the conversion.
Removes the unstable prefix from `unstable_io`. Since this was only
shipped in canaries with the unstable prefix we are going to just remove
it without retaining the unstable prefix simultaneously.
Note: re-apply of #92817
### What?
Deprecates the Edge Runtime and the `preferredRegion` route segment
config that only applied to edge runtime with build-time warnings,
TypeScript IDE hints, error documentation pages, and documentation
updates.
### Why?
Edge Runtime in Next.js is being deprecated in favor of the Node.js
runtime (the default). The `preferredRegion` route segment config is
tightly coupled to Edge Runtime and is also being deprecated.
### How?
**Edge Runtime deprecation:**
- Added `warnAboutEdgeRuntime()` helper using `Log.warnOnce` in
`packages/next/src/build/warn-about-edge-runtime.ts`
- Warning fires in `get-page-static-info.ts` (Webpack dev/build +
Turbopack build) and `turbopack-utils.ts` (Turbopack dev) when `runtime
= 'edge'` is detected
- Marked `'edge'` as `@deprecated` in the TypeScript language service
plugin (`rules/config.ts`)
- Created `errors/edge-runtime-deprecated.mdx` with migration guidance
- Updated documentation across 11 files to add deprecation notices or
simplify/remove outdated Edge Runtime references
**`preferredRegion` deprecation:**
- Added `warnAboutPreferredRegion()` helper in the same file
- Warning fires in `get-page-static-info.ts` for both App Router
(`config.preferredRegion`) and Pages Router (`config.config?.regions`)
- Marked `preferredRegion` as `@deprecated` in the TypeScript language
service plugin and the generated types (`next-types-plugin`)
- Created `errors/preferred-region-deprecated.mdx` with migration
guidance (remove the export)
- Updated documentation across 4 files to add deprecation notices
**Tests:**
- Added `test/e2e/edge-runtime-deprecated/` with fixture and assertions
for the edge runtime warning (dev + production)
- Added `test/production/preferred-region-deprecated/` with fixture and
assertions for the preferredRegion warning (production only -- Turbopack
dev does not surface `preferredRegion` from the Rust layer to JS)
<!-- NEXT_JS_LLM_PR -->
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
A prerendered route's `expire` — set via `cacheLife({ expire })` inside
`'use cache'` or via the `expireTime` config fallback — lands in the
prerender manifest as `initialExpireSeconds` / `fallbackExpire`
(#76207), but the runtime never read it: `IncrementalCache.get` only
considered `revalidate`. So past expire, Next.js served stale with a
background refresh instead of the blocking regeneration the `cacheLife`
`expire` docs describe.
The fix is three coordinated changes. The render-time
`responseGenerator` in `app-page.ts`, `app-route.ts`, and
`pages-handler.ts` now applies the `expireTime` fallback as soon as it
has the render's `cacheControl`, so every downstream consumer (the cache
stored via `IncrementalCache.set`, the response `Cache-Control` header,
the entry returned to `handleResponse`) sees a finalized `cacheControl`
with a populated `expire` — mirroring the build-time fallback.
`IncrementalCache.get` then returns `isStale = -1` when `lastModified +
expire * 1000 < now`, and `response-cache.handleGet` skips its early
`resolve(previousEntry)` for `isStale === -1` so the blocking
revalidation inside `responseGenerator` (which already picks
`BLOCKING_STATIC_RENDER` on that signal) can return its fresh output to
the user. Previously the early resolve committed the stale value to the
response first, so even though `responseGenerator` still ran a fresh
render its output only warmed the cache for the next request. As a side
effect this also closes the same early-resolve hole on the existing
tag-expired `isStale = -1` path.
On Vercel, ISR cache decisions live at the Proxy and the Proxy currently
ignores `staleExpiration` (using a hard-coded one-year value instead).
It is also expected, once it starts honoring `staleExpiration`, to pick
up updated values from the `stale-while-revalidate` response header.
Until that lands this change is only observable on `next start` —
deploy-mode behavior is tracked independently of Next.js.
Two test suites cover the new behavior.
`test/production/app-dir/use-cache-expire` uses `cacheComponents` +
`cacheLife({ expire: 300 })` with a custom cache handler that shifts
`lastModified` via an `x-test-cache-age-offset-ms` header, exercising
the fully-static shell, the partially-static route shell for a known
param, and the partially-static fallback shell for unknown params.
`test/e2e/app-dir/expire-time` covers classic ISR (`revalidate = 1`,
`expireTime: 2`) with a real three-second wait and is `it.failing` on
deploy, so it will flip the moment the Proxy honors the expire value.
fixes#78269
Adds a new export from next/cache that acts as a metasyntactic IO
boundary. During prerendering with cache components it returns a hanging
promise to prevent execution of code that follows it. In all other
contexts it resolves as a fulfilled thenable that React can unwrap
synchronously. This is gated behind experimental.unstableIO and not
ready for general use.
Re-apply #89861.
Somehow the tests passed in the PR but failed after merging. Will dig
into it.
Edit: Dug into it, there was a missing env var. Fixed and ready to merge
now.
PR doesn't add the Node.js streams code path. Implementation starts in
#90500
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.
Flakey:
```
❌ test/integration/app-dir-export/test/start.test.ts output:
HEADLESS=true NEXT_TELEMETRY_DISABLED=1 CI= NEXT_TEST_CI=true IS_RETRY=undefined TRACE_PLAYWRIGHT=true CIRCLECI= GITHUB_ACTIONS= CONTINUOUS_INTEGRATION= RUN_ID= BUILD_NUMBER= JEST_JUNIT_OUTPUT_NAME=test_integration_app-dir-export_test_start.test.ts JEST_SUITE_NAME=default:12/13:integration:test/integration/app-dir-export/test/start.test.ts /root/actions-runner/_work/next.js/next.js/node_modules/.bin/jest '--ci' '--runInBand' '--forceExit' '--verbose' '--json' '--outputFile=test/integration/app-dir-export/test/start.test.ts.results.json' 'test/integration/app-dir-export/test/start.test.ts'
[08:48:33.323Z] Running command "next build /root/actions-runner/_work/next.js/next.js/test/integration/app-dir-export"
▲ Next.js 16.1.0-canary.1
- Local: http://[::1]:36371
- Network: http://[::]:36371
✓ Starting...
Error: "next start" does not work with "output: export" configuration. Use "npx serve@latest out" instead.
at <unknown> (dist/server/next.js:230:53)
at async NextServer.prepare (dist/server/next.js:177:24)
at async initializeImpl (dist/server/lib/render-server.js:132:5)
at async initialize (dist/server/lib/router-server.js:538:22)
at async Server.<anonymous> (dist/server/lib/start-server.js:381:36)
[08:49:12.558Z] Running command "next build /root/actions-runner/_work/next.js/next.js/test/integration/app-dir-export"
FAIL webpack test/integration/app-dir-export/test/start.test.ts (100.119 s)
app dir - with output export (next start)
production mode
✓ should error during next start with output export (39235 ms)
✕ should warn during next start with output standalone (60029 ms)
● app dir - with output export (next start) › production mode › should warn during next start with output standalone
thrown: "Exceeded timeout of 60000 ms for a test.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."
46 |
47 | // TODO: Move this test to test/production to run in isolation.
> 48 | ;(process.env.TURBOPACK_BUILD ? it.skip : it)(
| ^
49 | 'should warn during next start with output standalone',
50 | async () => {
51 | nextConfig.replace(`output: 'export'`, `output: 'standalone'`)
at integration/app-dir-export/test/start.test.ts:48:50
at integration/app-dir-export/test/start.test.ts:21:56
at Object.describe (integration/app-dir-export/test/start.test.ts:20:1)
```
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.
### What?
This PR introduces comprehensive type-aware parameter tracking and
fallback handling for dynamic routes in PPR scenarios, with enhanced
client-side parameter resolution capabilities.
### Why?
When using client segment cache with dynamic routes, Next.js faced
several critical issues that prevented reliable client-side navigation:
**1. Parameter Resolution Inconsistencies**: The previous system had
inconsistent behavior when handling parallel route parameters, causing
unpredictable parameter resolution across different route segments. This
led to client-side navigation failures when encountering dynamic route
segments that weren't statically generated.
**2. Limited Parameter Type Awareness**: The routing system lacked
semantic understanding of different parameter types (dynamic, catchall,
optional catchall), making it impossible to implement advanced parameter
handling features or provide proper fallback behavior for different
parameter scenarios.
**3. Incomplete Fallback Parameter Collection**: The original
`getFallbackRouteParams()` used simple string-based tracking that
couldn't capture the full complexity of parameter relationships,
especially in parallel route scenarios where parameters might be defined
at different levels of the route tree.
**4. Client-Side Parameter Extraction Gaps**: The `getDynamicParam`
function had limitations in handling fallback route parameters and
lacked proper encoding/decoding mechanisms, leading to potential
client-side errors during route resolution.
**5. Configuration Validation Missing**: There was no validation to
ensure `clientParamParsing` was only enabled when `clientSegmentCache`
was also enabled, leading to potential misconfigurations in production.
These issues became critical blockers for implementing reliable
client-side parameter parsing in PPR scenarios, particularly for the
Vercel platform integration
(https://github.com/vercel/vercel/pull/13740).
### How?
- Introduces `DynamicParamTypes` and `OpaqueFallbackRouteParams` for
structured, type-aware parameter tracking
- Refactors `getFallbackRouteParams()` to use route modules for
comprehensive parameter collection across the routing tree
- Adds `resolveParallelRouteParams` function to eliminate parallel route
parameter inconsistencies
- Enhances `getDynamicParam` with proper fallback parameter handling,
encoding, and comprehensive catchall support
- Implements configuration validation in `assignDefaultsAndValidate()`
to prevent misconfigurations
- Adds clientParamParsing boolean to the routes manifest configuration
(needed for https://github.com/vercel/vercel/pull/13740)
- Updates build pipeline to properly handle enhanced fallback parameters
across static generation, export, and rendering phases
NAR-305
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
When a page component (or a parallel route slot) is cached, but other
parts of the component tree are dynamic, we generate a partially static
shell at buildtime that's resumed at runtime. During the resume, we need
to ensure that the cache key is the same as during prerendering. This is
currently not the case because we automatically include the (unused)
`searchParams` prop in the cache key. Its value is a hanging promise
during prerendering, and a resolving promise at runtime, leading to a
cache miss when looking up the cached page data in the Resume Data Cache
(RDC). Subsequently, hydration errors might occur when the recomputed
result is different than the prerendered result.
Since search params are not allowed to be read in public cache scopes
anyways, we can pass erroring search params into the cached page,
instead of using a hanging promise, and omit them from the generated
cache key. We already used this technique when `cacheComponents` is not
enabled.
closes NAR-87
## 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
When reading a cache entry, we usually check its tags against the recently revalidated tags, and dismiss it, if it has any of those tags. However, this is only needed during dynamic requests, specifically during the rendering that follows a revalidating server action.
During the prerender validation in dev mode, we should not discard the cache entries based on the pending revalidation. The concurrently running dynamic rendering will handle discarding and recreating those cache entries. During build-time prerendering, there will never be any pending revalidated tags.
This fixes a bug where a different value was rendered when revalidating with a server action and then reloading the page afterwards (or triggering another unrelated revalidating server action).
After having revalidated a prerendered route handler, on the subsequent request we must discard the prerendered data from the file system cache, as we already do for pages and fetch caches as well.
> [!NOTE]
> This PR is best reviewed with hidden whitespace changes.
We're splitting the test app routes between two root layouts, one for (partially) static pages that does not wrap its children in a Suspense boundary, and one for dynamic pages that adds a Suspense boundary above `body`, which opts those pages into fully dynamic rendering.
Two tests are expected to fail for now, because they revealed bugs related to revalidation, which will be fixed in follow-up PRs:
- https://github.com/vercel/next.js/pull/81611
- https://github.com/vercel/next.js/pull/81621
closes NAR-171
We want to respect the `__NEXT_EXPERIMENTAL_PPR` and `__NEXT_EXPERIMENTAL_CACHE_COMPONENTS` environment variables in the test apps, so that we can run them locally with these features enabled without needing to modify the config files.
So we're removing the requirement that `__NEXT_TEST_MODE` needs to be set at the same time. Usually you don't want to set this variable locally because it also changes other behavior in a few places.
This really has no downside, because it was already possible for users to set both variables and affect the flags, and now this can be done with a single variable. It allows us to keep the noise out of the test app's next config files.
We're also moving the assignments from the default config into `enforceExperimentalFeatures` to get the nice output when printing the experimental features in the CLI.
### What?
Introduces a testing infrastructure to validate Next.js behavior with
the experimental Cache Components feature (`dynamicIO`) enabled. This PR
adds dedicated CI jobs and configuration to run a subset of existing
tests with Cache Components activated.
This has 271 more disabled test suites than the Legacy PPR test suite
manifest. The expectation is that we will be resolving the test failures
in followup PRs.
### Why?
Cache Components (dynamicIO) represents a significant architectural
change in how Next.js handles component caching and rendering. To ensure
a smooth transition when this becomes the default behavior, we need to:
1. **Validate compatibility** - Ensure existing applications continue to
work correctly when Cache Components is enabled
2. **Identify edge cases** - Catch potential issues early by running
tests in this new mode
3. **Build confidence** - Provide a safety net for the eventual
migration to Cache Components as the default
The manifest-based approach allows us to incrementally expand test
coverage as we fix compatibility issues, rather than attempting to run
all tests at once which would be overwhelming and block progress.
### How?
- **Test manifest** (`test/cache-components-tests-manifest.json`):
Defines which tests to run and tracks known failures that need
addressing
- **CI workflow changes**: Adds three new job groups that mirror
existing test jobs but with Cache Components enabled:
- `test-cache-components-integration`
- `test-cache-components-dev` (parallelized across 6 groups)
- `test-cache-components-prod` (parallelized across 7 groups)
- **Conditional feature activation**: When
`__NEXT_EXPERIMENTAL_CACHE_COMPONENTS=true` is set during testing,
`dynamicIO` is automatically enabled unless explicitly disabled
- **Test adjustments**: Updates error message expectations in tests to
handle the different environment label ("Prerender" vs "Server") when
Cache Components is active
The infrastructure is designed to be temporary and will be removed once
Cache Components becomes the default behavior.
NAR-172