Commit Graph

758 Commits

Author SHA1 Message Date
Josh Story 906dbd0af6 test: honor skipStart for Vercel deployments (#98776)
## Summary

Make deploy mode follow the existing setup/start lifecycle: skipStart
prepares the fixture without deploying, and next.start() performs the
deployment. Failed starts retain CLI and build logs before rejecting, so
tests can assert expected build failures in their own bodies. Default
automatic startup still fails its setup hook on an uncaught deployment
error.

Remove the expectDeploymentFailure option. Refactor
app-invalid-revalidate into fixed fixtures and use the same start/error
assertions locally and on Vercel, without a deployment exclusion.

## Verification

- All four invalid-revalidate cases passed against real Vercel preview
builds with Turbopack. Each test caught the rejection from
`next.start()` and asserted the expected diagnostic in `next.cliOutput`;
no skipped tests.
- All four cases passed locally in dev and start modes with both
Turbopack and webpack.
- Seventeen lifecycle unit tests passed, covering the real
`nextTestSetup` hooks, deferred/default startup, failed-build logs,
retries, repeated starts, custom scripts, existing deployments, and
cleanup.
- `pnpm test-deploy-turbo
test/e2e/test-utils-tests/basic/basic.test.ts`: the default automatic
deployment reached READY and loaded its build/deployment IDs, but the
HTTP assertion failed because the temporary project's protection
redirected to Vercel SSO. Project protection settings were left
unchanged.

The suite's separate, existing Cache Components manifest exclusion is
unchanged.

<!-- NEXT_JS_LLM -->
2026-09-18 13:10:08 -07:00
Janka Uryga deab0fecc3 test: unflake instant-validation (#98854)
- in `dev`, wait for validation to run before asserting on a redbox
being open (encapsulated in `getInstantInsight`), which should reduce
flakiness in webpack
- in `start`, check if the build actually succeeded before running
tests. these builds seem to occasionally fail, but the cause is
currently unknown
2026-09-18 09:18:03 +02:00
Tim Neutkens 52788bdfe1 Revert "Fix App Router locale path matching with Pages i18n" (#98715)
Reverts vercel/next.js#98095

A stale merge of #98095 caused failures on canary:
https://github.com/vercel/next.js/actions/runs/35075051322

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2026-09-16 10:14:59 +00:00
Tim Neutkens 1d5dd67200 Fix App Router locale path matching with Pages i18n (#98095)
## Summary

Fix hybrid App Router and Pages Router projects using Pages i18n so
explicit or rewritten locale segments remain available to App Router
route matching, while Pages routes continue using locale-normalized
paths.

The built-in filesystem matcher now carries each route owner through
`RouteKind`, avoids duplicate App routes being treated as Pages routes
in production, and preserves literal App path parameters through the
router server, direct route-module invocation, and static export paths.
The same ownership information is maintained by the development bundler.

Adds regression coverage for domain locales, proxy rewrites, exact and
dynamic App pages, catch-all parameters, route handlers, inferred
default locales, and Pages routes. Adapter-generated routing metadata is
intentionally handled in the follow-up layers #97366 and #97500.

Closes #86048

## Verification

- `pnpm build-all`
- `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack
test/e2e/app-dir/i18n-app-pages-domain-routing/i18n-app-pages-domain-routing.test.ts`
(9 passed)
- Prettier and ESLint checks on all changed files

<!-- NEXT_JS_LLM -->
2026-09-16 10:40:09 +02:00
Tobias Koppers f1285cec5f Use default pnpm package import method (#98703)
### What?

Use pnpm's default package import method in the two hoisted
isolated-test configurations added by #98425.

### Why?

`package-import-method` controls how regular package files are
materialized from pnpm's store: by reflink, hardlink, or copy. Those
choices do not affect `realpath`, so forcing copies is unrelated to the
Node.js symlink-resolution workaround and unnecessarily disables pnpm's
more efficient defaults.

### How?

Keep `node-linker=hoisted`, which is the setting responsible for
producing npm-style real package directories, while removing the
independent copy policy. The Node-version gate, local-tarball
validation, release-age policy, and fixture behavior remain unchanged.

### Verification

- Hoisted-only scratch install produced real package directories, no
package symlinks outside `.bin`, and a metadata-only `.pnpm`
- Production Turbopack: non-hoisted SWC helper and warm-restart task
stats passed (2/2)
- `pnpm build`
- `pnpm types`
- Prettier, ESLint, and `git diff --check`

Follow-up to #98425 and
https://github.com/vercel/next.js/pull/98425#discussion_r4020168942.

<!-- NEXT_JS_LLM -->


<!-- fleet 81cd457d-6956-4cf9-b6f6-9ebf9d95f285 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 23:15:46 +00:00
Tobias Koppers 357b2b3c4c Use pnpm for isolated test installs (#98425)
### What?

Migrate the five local isolated test installs that explicitly used npm
to pnpm. The Nx fixture now has pnpm workspace metadata, while
filesystem-layout-sensitive fixtures use pnpm's hoisted linker with
copied package files.

### Why?

The npm-based Nx install bypassed the repository's centralized
supply-chain protections and could select a package immediately after
publication, including temporarily incomplete multi-package releases.
Using pnpm makes isolated installs inherit the repository's
`minimumReleaseAge`, exclusions, and exotic-subdependency policy.

The other npm installs depended on npm-style real package directories.
On Node versions affected by nodejs/node#65113, hoisted/copy mode
preserves that layout without leaving these fixtures outside the shared
pnpm security configuration; fixed Node releases use normal pnpm
linking.

### How?

- Use normal pnpm workspace resolution for the Nx fixture.
- Use `node-linker=hoisted` and `package-import-method=copy` for
filesystem tests only on affected Node releases; Node 24.21+ and 26.8+
use normal linking. Node 20 CI keeps the workaround because no fixed
Node 20 release exists.
- Validate local `@next/env` tarballs through the lockfile when hoisted
installs do not expose pnpm's virtual-store path marker.
- Keep the deployment-environment npm install unchanged.

### Verification

- `pnpm build-all`
- `pnpm types`
- A 9-version throwaway assertion verified the affected/fixed Node
release matrix
- `pnpm test-dev-turbo test/e2e/app-dir/nx-handling/nx-handling.test.ts
test/e2e/handle-non-hoisted-swc-helpers/index.test.ts
test/e2e/filesystem-cache/filesystem-cache.test.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts
test/e2e/filesystem-cache/evict-after-snapshot.test.ts` — all 25 tests
passed after installing the sandbox's missing Playwright browser
- Production Turbopack: Nx, non-hoisted SWC helper, build-cache-default,
and warm restart passed (9/9)
- `filesystem-cache.test.ts` production baseline: 15/17 passed; the same
two cache-growth bounds fail under both the unchanged npm fixture and
the pnpm fixture at nearly identical percentages, so they are
pre-existing sandbox-specific failures
- Generated-layout inspection: no package symlinks outside expected
`.bin` command shims; package files are copied; `node_modules/.pnpm` is
metadata-only

<!-- NEXT_JS_LLM -->


<!-- fleet 81cd457d-6956-4cf9-b6f6-9ebf9d95f285 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 20:19:48 +00:00
next-js-bot[bot] 83cb4fb70f Upgrade React from 6c0e1047-20260908 to 019019be-20260911 (#98576) 2026-09-11 17:58:49 +00:00
Sebastian "Sebbie" Silbermann 1329212686 [ci] Cleanup orphaned next-stats-action (#98348)
Became orphaned in https://github.com/vercel/next.js/pull/97792. Another
script and the test harness needed some utils from `next-stats-action`
so I moved that into `test/lib/link-packed-packages`.
2026-09-10 13:50:36 +02:00
Sebastian "Sebbie" Silbermann 6ba71046b0 [test] Move the harness off node-fetch (#98195)
Node.js ships with a built-in `fetch` now so `node-fetch` is no longer
necessary. Mostly motivated by tracing Node.js deprecation warnings
which originated from `node-fetch` by calling the deprecated
`url.parse`.

Call sites keep working through a compatibility type on `fetchViaHTTP`
that translates node-fetch-only options: Instead of `agent` we pass to
`http(s)` directly, `timeout` becomes `AbortSignal.timeout`, and Node.js
readable streams are accepted as bodies with `duplex: 'half'` set
automatically.

The `abort-controller` polyfill is dropped since its signal type
predates the current AbortSignal and undici would not honor it.
`node-fetch` stays installed because `scripts/generate-release-log.mjs`,
`scripts/reset-project.mjs`, and `scripts/update-google-fonts.js` still
import it (follow-up material). Fixture apps will be migrated
separately.
2026-09-10 13:50:35 +02:00
Josh Story 4cb547213b test: support deploy exclusions with force gates (#98149)
## Summary

- add static gate conditions for the deployment CI variants used by
existing test suites
- support `@force-gate` on parameterized `describe.each` suites
- document and test the force-gate infrastructure needed by the
migration

This establishes the gate support used by the deployment-exclusion
migration PRs above it.

## Verification

- `pnpm typescript`
- `pnpm jest test/unit/gate/pragma-transform.test.ts
test/unit/gate/runtime.test.ts --runInBand`

<!-- NEXT_JS_LLM -->
2026-09-08 19:41:08 -07:00
Hendrik Liebau c11c0942f2 [test] Run deploy tests against local JavaScript changes by default (#98353)
Deploy tests now use the checkout's built JavaScript packages when
`NEXT_TEST_VERSION` is unset. The harness reuses the isolated-test
packer, includes the tarballs in the deployment source, and installs
them through relative `file:` references.

The previous published-build workflow remains available through
`NEXT_TEST_VERSION`. For example, `NEXT_TEST_VERSION=canary` skips local
packing and tests the published canary. Exact versions and CI preview
tarball URLs remain supported. Existing CI deploy jobs already set
`NEXT_TEST_VERSION`, so their package selection does not change. The
harness skips local package preparation when it reuses an existing
deployment or invokes a custom deploy script, preserving those
workflows.

Staged monorepo peer dependencies use matching local versions so npm
accepts prerelease builds without conflicting direct-package overrides.
Fixture overrides retain their nested rules, including qualified
identity overrides with or without an explicit `.` entry. Configuration
updates preserve linked source files.

Deployments of local JavaScript packages use published SWC binaries
matching the local Next.js version. They do not include locally built
native binaries because those may target a different platform.

**Verification**

```
VERCEL_TURBOPACK_TEST_TEAM=vtest314-next-turbo-e2e-tests \
  pnpm test-deploy-turbo test/e2e/app-dir/third-parties/basic.test.ts
```

```
VERCEL_TEST_TEAM=vtest314-next-turbo-e2e-tests \
  pnpm test-deploy-webpack test/e2e/app-dir/third-parties/basic.test.ts
```

Both deploy modes passed. Additional temporary checks, removed
afterward, verified that:

- Deployments install the local JavaScript packages and use matching
local versions for monorepo peers.
- Qualified npm overrides preserve a nested `sharp@0.34.5` pin, both
with and without an explicit `.` entry. The check resolved `sharp` from
Next's own dependency context.
- Archive preparation leaves checkout manifests and tarballs unchanged.
Repacked archives preserve other package files, permissions, links, and
external peer declarations.
2026-09-08 18:49:13 +02:00
Sebastian "Sebbie" Silbermann 662b0355eb [test] Resolve workspace:* packages to the version under test (#98330)
Test fixtures that depend on monorepo packages (for example
`@next/third-parties` and `@next/mdx`) declared them at the `canary`
dist-tag, which installs the published npm canary instead of the build
under test. In deploy tests this also broke the remote install entirely:
the published canary's `peerDependencies` ranges (for example
`^16.0.0-beta.0`) reject the prerelease preview versions that
`NEXT_TEST_VERSION` installs for `next` (for example
`16.4.0-preview-<sha>-<date>`), so `npm install` failed with ERESOLVE.

Test dependencies can now be declared as `workspace:*`, which resolves
to the build from the current checkout in every test mode:

- dev/start: the locally packed tarball from `pack-for-isolated-tests`,
with a descriptive error when the package has no pack task or is not
part of the repository.
- deploy: the preview tarball of the tested commit when
`NEXT_TEST_VERSION` points at a preview build (preview tarballs already
rewrite their monorepo peer dependencies to the same preview URLs, so
peer resolution succeeds), falling back to the version in the worktree
otherwise. Private packages without published preview tarballs fail with
a descriptive error.

All existing tests that used `canary` for monorepo packages are migrated
to `workspace:*`.
2026-09-08 18:17:29 +02:00
Janka Uryga f4449e8fa5 Instant validation for caches excluded from prerenders (#98342)
Follow-up to #98339. Turns out we were not testing how caches excluded
from static/runtime prerenders and app shells behave in Instant
Validation, and it was somewhat broken. This PR adds test coverage and
fixes some bugs I found along the way.

### Fixes

- `use-cache-wrapper` was incorrectly gating cache delays in "request"
stores on `NODE_ENV === "development"`, which does not include
build-time instant validation. The correct check is now implemented in
`isValidationRender`
- `use-cache-wrapper` has divergent behavior for caches with `stale <
MIN_SHELL_STALE` across app shells and PPR/static shells, which needs to
be tracked so that we know that the same render can't be used for both
Instant and Static Shell validation. When we see a cache entry like
that, we now call `trackIncompatibleShellContent()`
- A render that had a cache miss could still report that it's compatible
with both SSV and IV, because we only do the above for a cache hit. As a
result so we'd incorrectly reuse `LAZY_FULL_RENDER` for both. Cache
misses now result in a `trackIncompatibleShellContent()` call to avoid
this

### Tests

We now have tests for:
- `stale < MIN_SHELL_STALE` - excluded from app shells, but included in
static and runtime prefetches
- `stale < MIN_PREFETCHABLE_STALE` - excluded from all prerenders
- `expire < MIN_PRERENDERABLE_EXPIRE` - excluded from static prerenders,
but allowed in runtime prerenders

Due to bugs mentioned above, some of the added tests were failing before
the fixes (mostly the ones that expect an error -- passing a "no
validation errors" test is easy, just don't create any dynamic holes)

In build, these tests incorrectly reported no errors when they should've
failed IV:

- `invalid - unguarded non-prefetchable cache (with short stale)` (both
PPF and non-PPF)
- `non-app shell validation > invalid - unguarded non-prerenderable
cache with short expire` in build:
- `app shell validation > invalid - unguarded cache with a
shorter-than-shell staleTime` in build:

This is because we were missing cache delays in build-time instant
validation (now fixed with `isValidationRender`), so the caches weren't
dynamic holes at all.

The `stale < MIN_SHELL_STALE` tests (`app shell validation > invalid -
unguarded cache with a shorter-than-shell staleTime`) were also failing
in dev:
- **{initial load, client navigation} with cold caches**: Should be an
IV error, but is an SSV error. The initial render had cache misses, so
we did a warm-cache full rerender with runtime shells, which resolved
`await nonShellCache()` in the Runtime stage. But the initial render
**did not track incompatible data**, so we incorrectly re-used it for
SSV and IV. The cache was resolved in `Runtime` so SSV saw a runtime
hole and `await nonShellCache()` errored in SSV with `Next.js
encountered runtime data during prerendering.`
- **client navigation with warm caches**: same as above, except there
weren't cache misses, so incorrectly reused the *original* runtime-shell
render for SSV and IV
- **initial load with warm caches**: No redbox when IV should've
errored. The main render was an initial load and did not use runtime
shells, so it resolved `await nonShellCache()` in `PrefetchStatic`. We
had no cache misses and **did not track incompatible data**, so we
incorrectly re-used the main non-runtime-shell render for SSV and IV.
The cache resolved in `PrefetchStatic` and it wasn't a hole in
`ShellRuntime` so no error was reported.
2026-09-08 16:22:18 +02:00
Sebastian "Sebbie" Silbermann 5b8c866c32 [test] Skip hooks of suites skipped by a lazy @force-gate (#98169)
A `describe` gated by a lazy `// @force-gate` (a condition read off the
fixture's resolved config, like `cacheComponents`) cannot be skipped at
collection time, so the gate runtime force-passes its tests at runtime
and `nextTestSetup` skips the fixture build. Hooks registered inside
such a `describe` still ran, because only the `it`/`test` globals were
wrapped. A hook that touches the `next` instance (for example a
`beforeEach` that reads `next.cliOutput`) then failed with `next
instance is not initialized yet` and failed the supposedly skipped
suite.

The gate runtime now wraps the
`beforeAll`/`afterAll`/`beforeEach`/`afterEach` globals as well and
skips a hook registered under a lazy `@force-gate` when that gate
evaluates false against the fixture's resolved config. Only a false lazy
force-gate skips a hook: an inverted `@gate` still runs its tests and
therefore needs its hooks, and a static `@force-gate` already skips the
whole `describe` at collection time. The hooks `nextTestSetup` registers
itself are marked with the new `ungatedHook` helper so that the skip
decision and the fixture cleanup still run.

The `cacheComponents disabled, edge app router` describe in
`test/e2e/cache-handlers-upstream-wiring` is converted from the
`process.env.__NEXT_CACHE_COMPONENTS` `describe.skip` ternary to `//
@force-gate !cacheComponents`, replacing a fake-green skip and
exercising the fixed path. The `!deploy` conversion of the remaining
describes is left to the branch that introduces the `deploy` condition.

Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
2026-09-02 19:45:27 +02:00
Tobias Koppers d42ea11842 [test] Drain build output before start (#97947)
### What?

Make the production test harness wait for a build process's piped stdio
to close before `NextStartInstance.start()` continues to launch the
server or reports a build failure.

### Why?

A child process's `exit` event can fire before trailing stdout and
stderr have been delivered to the parent. This can make start-mode tests
intermittently miss diagnostics emitted near the end of a production
build, even though the build itself reported them correctly.

The standalone `build()` helper already uses the later `close` lifecycle
event to avoid this race. Applying the same rule to the build phase
inside `start()` removes the equivalent latent output-drain hazard and
makes child-process handling consistent across the harness.

### How?

Resolve the build phase on the child process's `close` event. Unlike
`exit`, `close` is emitted after piped stdio closes, so all trailing
build output has reached the harness before it starts the server or
rejects the build. Exit-code and signal handling remain unchanged.

### Verification

- `pnpm types`
- `pnpm test-start-webpack
test/production/production-build-dir/production-build-dir.test.ts`
- `pnpm test-start-webpack
test/production/app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts`
- Repeated webpack stress campaign for the sync-IO suite: 5/5 runs,
including 20/20 `--debug-prerender` route cases
- Prettier and ESLint on `test/lib/next-modes/next-start.ts`

<!-- NEXT_JS_LLM -->


<!-- fleet 821362ed-9f96-4e53-b56e-49bf00e878b9 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-08-27 16:18:02 +02:00
Andrew Clark 94327e8ade Port React's @gate test directive to the e2e harness (#96228)
The test suite has accumulated a bunch of patterns for disabling tests
that are known to fail under some configuration: `it.skip`, `if
(isNextDev) { test('skipped in dev mode', () => {}); return }`, whole
describes toggled off by checking `process.env.__NEXT_CACHE_COMPONENTS`.
These all have the same flaw: nothing tells you when the thing you
skipped starts working. The test stays disabled forever, and the
workaround it was guarding rots along with it.

React solves this with the `@gate` pragma, and this PR ports it to the
Next.js e2e harness:

```ts
// Blocked on the optimization that marks a route as fully static when
// no dynamic params are referenced in Server Components.
// @gate !cacheComponents
it('navigates to a page with a lazily-generated static param', async () => {
  // body unchanged
})
```

The test still runs. If the condition is false and the test fails, the
failure is absorbed and the suite stays green. If it _passes_, the suite
fails: the gate is stale, delete it. So instead of a skip that hides a
fixed bug indefinitely, you get a CI failure the day the fix lands.

When the condition is static, the inversion is Jest's own `test.failing`
under the hood. A lazy condition isn't known until the fixture's
resolved config is read inside the body, so those tests invert at
runtime instead.

`// @force-gate <condition>` skips for real — for tests that can't even
be attempted (prefetching is disabled in dev, deploy has no local build
output, the fixture won't build under the condition), and for tests of a
new API, where the disabled state can only throw and running it proves
nothing:

```ts
// Prefetching is disabled in dev, so this suite has nothing to test.
// @force-gate prefetching
describe('segment cache prefetch scheduling', () => {
  // ...
})
```

There's no staleness check in that case, so this is a judgment call:
prefer `@gate` when the off state fails for a meaningful reason — the
flag changes behavior that already exists — and `@force-gate` when the
body can only throw because the API doesn't exist. A static condition
(mode, bundler) resolves at collection time into a normal Jest skip. A
lazy condition resolves at runtime, and when a lazy force-gate on a
describe is false, we skip the fixture build entirely — that's what
makes it usable for suites whose fixtures are build-incompatible with
the condition. (One caveat: Jest has no way to skip a test that's
already running, so these report as passing with a warning in the log,
not as skipped.)

Conditions live in a hand-written registry. I considered deriving the
lazy ones from the config schema automatically, but a gate is a claim
about which dimension of the test matrix explains a failure, and I'd
rather each of those claims be spelled out with a description.
Referencing an undeclared name fails the suite at collection time, so a
typo can't silently disable a gate.

The important design decision for lazy conditions is that they read the
fixture's _resolved_ config, never `process.env`. The env var isn't the
truth: `__NEXT_CACHE_COMPONENTS=true` only applies when the fixture
doesn't set `cacheComponents` itself, and config resolution implies
flags the fixture never mentions (`cacheComponents: true` alone turns on
`experimental.ppr`). Resolution happens in a child process, because
in-process `loadConfig` would leak the fixture's `.env` files into the
Jest worker. Suites with no lazy gate never pay for any of this.

The condition expression is parsed using a small grammar (also ported
from the React repo). An expression that doesn't parse fails the suite:

```ts
// @gate mode === 'start' && !cacheComponents
// @gate !(turbopack || rspack)
```

There's also a runtime version, mirroring React's `gate(flags =>
flags.enableFoo)`, for tests that run under both states but assert
differently (and for `it.each`, where the pragma can't attach):

```ts
import { gate } from 'next-test-utils'

it('renders the fallback', async () => {
  if (await gate((conditions) => conditions.cacheComponents)) {
    // PPR: the fallback is part of the static shell
  } else {
    // fully dynamic: the fallback streams in
  }
})
```

It also accepts the pragma expression language as a string: `await
gate('cacheComponents && !dev')`.

Docs are in `test/lib/gate/README.md`; `test/unit/gate/` covers the
transform, the expression language, and the runtime.
2026-08-26 10:22:11 -04:00
Tobias Koppers 2059206d96 [test] Fix flaky build CLI output capture (#97900)
### What?

Stabilize `NextStartInstance.build()` CLI output capture so failed
production builds return all trailing stdout/stderr diagnostics.

The existing `sync-io-blocks-root` behavioral assertions remain
unchanged, including the route-specific `Date.now()` diagnostic,
timeout/retry guard, and exit-code check.

### Why?

The `--debug-prerender` cases intermittently lost the detailed
`Date.now()` diagnostic from `result.cliOutput` in CI even though builds
exited normally. Across the reported CI run and two retries, the missing
route set varied from 4 to 2 to 3 routes, consistent with an
output-drain race rather than a rendering or resource failure.

On latest canary with Node 20, `NEXT_TEST_MODE=start`, and
`IS_WEBPACK_TEST=1`, 5 baseline campaigns completed 24 builds (20
debug-prerender) without reproducing the intermittent failure locally. A
controlled Node 20 reproduction established the lifecycle race directly:
the child `exit` event observed no trailing output, while `close`
observed the trailing diagnostic.

### How?

Wait for the child process `close` event before snapshotting
`cliOutput`. Unlike `exit`, `close` is emitted after the piped stdout
and stderr streams close, so diagnostics emitted near process shutdown
are fully drained before the build result is returned.

This is intentionally limited to the affected start-mode build harness
and is independent of the mmap persistence PR stack (#97873, #97888,
#97889).

### Verification

- Baseline on Node 20.20.2 / start mode / Webpack: 5 campaigns, 24
builds total, 20 debug-prerender; 0 failures and no affected local
routes
- Deterministic child-process lifecycle reproduction: `exit` captured
empty output; `close` captured the trailing diagnostic
- Post-fix focused test: 5/5 runs passed, covering 20/20 debug-prerender
cases
- Post-fix complete test: 3/3 runs passed, covering 24/24 cases (12
debug-prerender and 12 normal production)
- `pnpm build-all`
- Prettier and ESLint for `test/lib/next-modes/next-start.ts`
- `pnpm types`

<!-- NEXT_JS_LLM -->


<!-- fleet a8a423ef-2679-4cb0-ba51-92592630165e -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-08-26 08:15:50 +02:00
Jiwon Choi d7aa66c345 Remove generated error codes (#97687)
### Why?

Should come up with better solution that does not block PRs with git
conflict

x-ref:
https://vercel.slack.com/archives/C02CDC2ALJH/p1785263902728189?thread_ts=1785263687.502649&cid=C02CDC2ALJH

### How?

- Delete `errors.json`, the error-code SWC plugin, generated WASM, merge
driver, and validation/build tooling.
- Stop attaching error codes to server-rendering digests, redboxes, and
telemetry; native `Error.code` and `Error.name` remain available where
applicable.
- Remove the development-overlay error feedback UI, middleware, and
telemetry event that depended on stable codes.
- Update fixtures, snapshots, and guidance for code-free errors and
numeric-only digests.

<!-- NEXT_JS_LLM -->
2026-08-21 22:45:12 +02:00
Sebastian "Sebbie" Silbermann 1f933ce927 [test] Replace the turbopack-reports sqlite3 dependency with a local addon fixture (#97541)
The suite installed `sqlite3` only to get a package that locates its
compiled binary through `require('bindings')(...)`, which is the shape
turbopack issue 5913 was about. It now carries its own `native-addon`
and `bindings` packages instead, both installed as relative `file:`
dependencies, with node-gyp compiling the addon during install.

The addon is compiled rather than stubbed because the assertion reads a
value off the loaded binary, so a real `process.dlopen()` has to happen.
Compiling at install time also keeps the binary matched to whichever
Node ABI is running, which a checked-in binary could not do, since a
non-context-aware addon cannot use Node-API and is therefore ABI-locked.
The page now renders the addon's constant, so a module that resolved to
nothing fails the test instead of passing quietly.

Both packages are `file:` rather than `link:` dependencies. A linked
package resolves to a path inside the app, which the bundler then treats
as app code and tries to bundle, and `bindings` contains a `require` it
cannot resolve statically. `serverExternalPackages` is needed because
the app router bundles `node_modules` by default, and `sqlite3` only
avoided that by being on the built-in list in
`server-external-packages.jsonc`.

The fixture packages are registered in `modulePathIgnorePatterns`,
following the entries already there. Jest is configured with
`throwOnModuleCollision`, so a package name appearing twice outside
`node_modules` aborts the whole test run, and the layers above add more
copies of `bindings`.
2026-08-20 11:57:03 +02:00
Andrew Clark 12b802b259 Convert per-segment prefetches to NavigationFlightResponse format (#96877)
This is the last of the changes to convert all RSC requests to a single
unified response type, NavigationFlightResponse.

The old format was a positional array whose ordering had to be kept in
sync by hand between the server and the client. The new format is the
same root-anchored tree used by every other response, so the dedicated
client write path for per-segment responses is deleted and these
responses go through the shared one.

A few functions are renamed as their roles change:
convertServerPatchToFullTree becomes createNavigationSeed (it now also
accepts trees with no base to overlay), fetchSegmentsOnCacheMiss becomes
fetchSegmentPrefetchesUsingStaticRequest (the counterpart of
fetchSegmentPrefetchesUsingRuntimeRequest), and writeSeedDataIntoCache
becomes writeTreeDataIntoCache (seed data no longer exists as a
concept).

During the unification, a few inconsistencies and oversights were caught
and fixed: response data with no matching pending entry is written as a
detached entry instead of dropped, and a response's byte size is spread
across the entries it actually fulfilled rather than the old chain-based
count. The fallback retry loop also marks itself pending before the
first cache write instead of after, closing a window where the scheduler
could spawn a duplicate revalidation.

The second commit unifies cache keying for prefetched segments. There
were two implementations of the logic that decides which cache key a
prefetched segment is written to, one for per-segment responses and one
for responses produced by a live render. Now there's one: if the server
reported which params the segment depends on, key the entry using those;
otherwise fall back to the keying implied by how the payload was
fetched. A segment is only treated as param-independent when the server
says so. Two precedence fixes were needed to merge them, and a few other
inconsistencies found during the unification are fixed along the way;
see the commit message for details.
2026-08-13 18:18:23 -04:00
Andrew Clark 0c5881a374 Unify how server responses are written into the client cache (#96876)
Every flow that writes a server response into the client cache now goes
through a single function, writeServerResponseIntoCache.

This is preparation for subsequent steps in this stack that migrate even
more callers to this unified path.

While we're modifying it, fetchSegmentPrefetchesUsingDynamicRequest is
renamed to fetchSegmentPrefetchesUsingRuntimeRequest: these responses
are produced by a runtime request, and "dynamic" refers to non-cached
data, which prefetch responses never contain.

One subtle fix that fell out of the unification: a runtime prefetch
response that carries no transport data used to make the flow bail
before writing anything; now the shared function rejects the pending
entries it was given, the same as a build id mismatch.
2026-08-13 18:18:23 -04:00
Syed Sohail Hussain 4abf9cabde test: fix 'ouput' typo in parseIdsFromCliOutput (#97266)
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

We encourage you to use AI to assist you in researching, creating, and
reviewing changes. However, you must review and deeply understand the
contributions you are making. For this reason, **pull request
descriptions from external contributors must be written by a human**.

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Signed commits

- This repository requires verified commit signatures on protected
branches.
- If this pull request is blocked for unsigned commits, re-sign the
commits and force-push the branch.
- A `Signed-off-by` line in the commit message is not enough.

## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Closes NEXT-
Fixes #

-->
2026-08-12 16:11:41 -07:00
Hendrik Liebau 0d5d2fb142 [test] Redirect the deployment host in-process for proxy deploy tests (#97260) 2026-08-12 20:57:57 +00:00
Sebastian "Sebbie" Silbermann 10725074a4 [ci] Use OIDC tokens to read private preview builds (#96919)
Replaces the shared, static `PREVIEW_BUILDS_READ_TOKEN` secret with OIDC
tokens for reading auth-protected preview builds from vercel-packages
(see https://github.com/vercel/vercel-packages/pull/96):

- CI jobs mint a GH oidc token for the
`https://vercel-packages.vercel.app` audience
- deploys have a `.npmrc` written that uses the Vercel OIDC token

Tarball polling now also fails fast on 401/403 (retrying can't change
the authorization outcome) and failure messages include response headers
so request IDs are available for debugging.



Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 18:21:05 +02:00
Hendrik Liebau a5000124d7 Forward a build-container pin to deploy-test deployments (#97191) 2026-08-12 10:57:39 +02:00
Marcos Hernanz aeddff1556 Fix spelling in two comments (#97136)
### What?

Fix two spelling mistakes in comments.

### Why?

Readability.

### How?

Updated comment text only.

Attribution: This change was originally authored by @adityagiri3600 in
#90594.

Co-authored-by: Aditya Giri <74224708+adityagiri3600@users.noreply.github.com>
2026-08-10 16:32:30 -07:00
Sebastian "Sebbie" Silbermann 17f6f135e2 [fragment-scroll] Rename ScrollAndMaybeFocusHandler to ScrollHandler (#96828)
As of https://github.com/vercel/next.js/pull/95602, the old
scroll-and-focus handler doesn't exist anymore
As of https://github.com/vercel/next.js/pull/96113, the new handler no
longer handles focus.

This updates the relevant variable names to reflect that we no longer
handle focus.
2026-08-10 13:26:51 +02:00
Sebastian "Sebbie" Silbermann 2c04735944 [fragment-scroll] Remove config.experimental.appNewScrollHandler (#95602) 2026-08-06 09:53:09 +00:00
Tim Neutkens e37ddd19f5 Fix deploy test TypeScript exclusions (#96545)
## Summary

Set `NEXT_PRIVATE_LOCAL_DEV=1` for directory fixtures in deploy mode,
matching the next-start setup. This keeps copied test runner files
excluded from TypeScript checks when deployment tests use
`tsconfig.test.json`.

## Verification

- `pnpm exec tsc --project test/lib/tsconfig.json --noEmit`
- Not run: deployment tests (requires Vercel deployment credentials)

<!-- NEXT_JS_LLM -->
2026-08-03 19:35:17 +00:00
Tim Neutkens cbf0cef687 Enable TypeScript CLI by default (#96497)
## Summary

Enable the project-local TypeScript CLI checker by default during `next
build`, while preserving `experimental.useTypeScriptCli: false` as an
opt-out to the TypeScript compiler API. Update the TypeScript 7
guidance, diagnostics, tests, and documentation to match the new
default.

## Verification

- `pnpm exec jest --runTestsByPath test/unit/isolated/config.test.ts`
- Pre-commit lint-staged checks passed (Prettier and ESLint)
- Not run: `pnpm test-start-turbo
test/production/app-dir/typescript-cli/typescript-cli.test.ts` (isolated
fixture dependency installation was blocked by unavailable npm registry
access)
- Not run: `pnpm --filter=next types` (repository has pre-existing
unrelated TypeScript errors)

<!-- NEXT_JS_LLM -->
2026-08-03 18:10:51 +02:00
dan be1424f3dc Make retry stop when the time it was given is up (#96354)
How long do you expect this to wait at worst (before failing)?

```js
await retry(async () => {
  expect(await browser.elementById('never-appears').text()).toBe('x')
}, 15_000)
```

After this PR, the answer is 15 seconds. (Well, technically, 15 seconds
+ whatever last attempt is, so 20.)

But before this PR, the answer was 3 minutes.

## What?

There's a bug (IMO) in `retry`. Instead of waiting for the time you give
it, it counts tries.

For example, `retry(fn, 15_000)` reads as "keep trying for 15 seconds".
However, what it actually was doing is divide 15000 by the 500ms
interval and try 30 times, sleeping 500ms in between every try.

This is a problem because **it wasn't measuring the stall time of each
try itself.**

When a try is cheap, it doesn't matter. But a try that looks for an
element in the browser isn't cheap: if the element isn't there, the
selector waits 5 seconds before giving up.

In the example above, each try sits in the selector for its full 5
seconds, and there are 31 of these intervals, plus the 15 seconds of
sleeping in between. This ends up waiting 170103ms, i.e. three minutes
for a wait that says 15 seconds.

Jest kills the test at its own timeout long before that, and the actual
error gets thrown away. What's left is `Exceeded timeout of 120000 ms`,
which doesn't say which assertion never came true. In the one I measured
it was `page.waitForSelector: Timeout 5000ms exceeded`. My agent lost an
hour trying to make sense of this twice now.

## Fix

We count time now. After the fix, the retry watches the clock and stops
when its time is up. It still always makes at least one try. The
interval no longer has to divide the duration, because nothing counts
steps any more. If the last try is slow, we're gonna wait for that last
try to end (so the timeout is still not *strictly* respected) so that we
can show the error in the correct test.

Cheap waits behave exactly as before. In my local run, a try that had to
repeat cost 251ms at the median, 459ms at the worst. So the only calls
this changes are ones that were already failing, and those now fail with
their own error instead of a test timeout. One flaky dev test that used
to die at 120 seconds with nothing to show now fails in 19.5 seconds
saying `Failed to retry within 15000ms (waited 16022ms)`, next to nine
tests that take 2.2 seconds each.

I added some regression tests. Since it's a test helper, a unit test
seemed fine.

## What this means for existing tests

The catch is that a call whose tries are slow now gets less time than it
used to, and this helper has 2110 call sites, so CI is the real check.
It found one file: the hash scroll tests in `navigation.test.ts` wait
with the default 3 seconds, and each try there costs 150-300ms on a
runner, so they were really getting about 5. They need more than 3, so
they failed on two dev shards. I gave them 10.

If this is green, we can merge it in isolation from the rest of the
stack.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:42:56 +01:00
Andrew Clark dfa7f4f713 Attempt static prefetch before resorting to runtime (#96095)
If we're reasonably confident that a segment can be prefetched
statically without omitting data that would have been included during a
runtime prefetch (e.g. cookies), the client should attempt prefetch the
segment statically instead of going straight to a runtime request.

The decision for whether to do this is based on the
ShouldAttemptStaticPrefetch added in previous steps.

If it turns out the static response is not sufficient, then it will fall
back to a runtime request.

This makes prefetching cheaper for pages that are fully statically
renderable. We can make the optimization better in the future with more
reliable per-segment computation of the ShouldAttemptStaticPrefetch, but
the current approach should at least work for fully static pages, which
is what's most important.

This optimization applies during both the Shell phase and the
Speculative phase of the prefetching algorithm.
2026-07-28 11:51:29 -04:00
Andrew Clark bdbbb1e887 Block prefetch task until sufficient response is received (#96017)
This is a refactor of the prefetching algorithm to block the prefetch
task from completing until the response is received.

Currently, a prefetch task is fire-and-forget: once a prefetch's
requests are spawned, the scheduler marks the originating task as
complete regardless of what the server ends up returning. However,
certain planned optimizations require us to verify the response from the
server to determine whether additional work is necessary. For example,
we may attempt to do a static prefetch instead of a runtime prefetch if
we optimistically assume that the static prefetch does not vary on
runtime data. We need to verify that assumption is correct before
marking the task as complete.

Now, whenever a pass over the task spawns a segment request, or
encounters an entry whose response hasn't arrived yet, the task
registers itself on the entry and exits as Blocked instead of Done. When
the entry resolves, the blocked task is pinged and the pass re-runs
against the received data. Only a pass that observes every response it
cares about advances the task to the next phase or completes it.

Rejected entries are handled per segment: a rejection counts as an
observed response, so the pass skips that segment and keeps prefetching
the rest. The task never registers on a rejected entry, since nothing
ever pings one. Retries are governed by the existing per-entry logic.

Also fixes a cache bug this exposed: when a revalidation response is
keyed at a more generic vary path than the stale partial entry that
prompted it (because the segment turned out not to vary on some param),
the stale entry shadowed the result on every lookup — the upgraded data
was unreachable and the revalidation was wasted. With blocking, that
dead end became an infinite loop: each fulfillment re-pinged the blocked
task, which re-read the same stale entry and spawned another
revalidation. Upserting (or re-keying) a segment now deletes settled
entries at more specific vary paths that the incoming entry supersedes.

The router-act test helper's "at least one request initiated" watchdog
is adjusted to account for the new blocking behavior: countable requests
can now be legitimately gated on an in-flight App Shell response (which
the watchdog intentionally doesn't count). On expiry it now proceeds to
response processing if the act scope intercepted a shell request, and
keeps waiting while shell requests are in flight elsewhere on the page,
instead of timing out.
2026-07-27 19:03:16 -04:00
dan e2dce63f3f [test] Unflake more tests (#96081)
- `detectContentType` test was too aggressive about asserting completion
in 1ms, let's give it several tries.
- We weren't correctly setting up pnpm overrides in tests, which breaks
things whenever canary comes out.
2026-07-27 20:54:10 +01:00
Hendrik Liebau 954eabec68 [refactor] Prepare dev validation for running on a worker thread (#96151)
This is a behavior-preserving refactor of the dev-mode Cache Components
validation, ahead of the change that moves it onto a worker thread.
In-process behavior is unchanged.

It narrows the context the validation functions require.
`runValidationInDev` and its helpers (`validateStaticShell`,
`warmupClientModulesForStagedValidation`, `validateStagedShell`,
`validateInstantConfigs`) now take a `ValidationRenderContext` (a `Pick`
of `AppRenderContext` plus the two `renderOpts` fields and the
debug-channel flag they actually read) instead of the full render
context, with `toValidationRenderContext` mapping the in-process and
build-time callers. The worker cannot reconstruct a full
`AppRenderContext` from a serialized snapshot, so narrowing the contract
to what validation genuinely consumes is what lets the same code run
there.

It also separates computing the validation errors from delivering them.
`runValidationInDev` now returns the errors instead of sending them to
the dev overlay inline, and the caller `runDevValidationInBackground`
delivers them via `logMessagesAndSendErrorsToBrowser`. The test-mode
lifecycle markers and delay move into `runWithDevValidationLogging`,
which brackets both the render and the delivery. When validation runs on
the worker, the worker computes the errors but the main thread still
delivers them (delivery needs the response object), so splitting the two
halves is a prerequisite.

Finally, it extracts the test-only validation lifecycle markers into a
shared `dev-validation-events` module. The `<VALIDATION_MESSAGE>`
wrapping and the event shape had been hand-rolled at each emission site
in `app-render.tsx` and duplicated again in the test harness; they now
live in one place as `formatValidationEvent` and an exported
`ValidationEvent` union that the in-process emitters call and the test
utilities import rather than redeclare. The worker emits the same
markers through the same helper once it is added, so a single definition
keeps every producer and the test parser in sync. Build validation's
marker `requestId`, previously the numeric `Date.now()`, is stringified
to fit the shared `string` type; it appears only in the logged marker
payload, which nothing matches on, so this too is unobservable.
2026-07-25 07:21:02 +02:00
Benjamin Woodruff e630d81822 [ci] Enforce minimumReleaseAge in e2e tests that install external dependencies (#95628)
We also mitigate the security impact of e2e tests by running everything
in VMs, and by not reading caches in release jobs, but we should still
be using `minimumReleaseAge` in e2e tests.

Ideally we'd also use lockfiles here too, and I'll work on that next,
but that is likely a lot more complicated because we probably need some
way to merge lockfiles, since we can't pin the next.js and react
versions in every test, and we need a reasonable/efficient way to
bulk-update all the lockfiles for tests that need it.

Depends on https://github.com/vercel/next.js/pull/95668 for a bunch of
windows path representation fixes that this ends up exposing in CI,
because adding workspace files causes pnpm to change the absolute path
representation used in the Windows junction points that it creates.
2026-07-21 14:54:01 -07:00
Vercel Release Bot cee9824aa5 Upgrade React from 172742b4-20260716 to 81e442ea-20260721 (#96016)
Co-authored-by: next-js-bot[bot] <279046576+next-js-bot[bot]@users.noreply.github.com>
2026-07-21 17:25:13 +00:00
Tim Neutkens 81d0e8519d Fix instant validation blocking navigations (#95939)
### What?

- Run development instant validation only after the navigation response
has finished.
- Let a newer request supersede stale validation work and yield between
validation render attempts.
- Forward the cancellation signal into React validation prerenders so
asynchronous validation work can stop promptly.
- Add focused scheduler/response tests and assert that development
validation starts after the response finishes.

### Why?

Instant validation was detached from the response promise, but its
additional React renders still ran on the same Node.js event loop. A
subsequent navigation could therefore wait hundreds of milliseconds for
stale diagnostic work from the previous request.

Instant validation still runs when the server is idle. When another
navigation arrives, foreground request work now takes priority and the
obsolete validation is discarded.

### How?

Each Cache Components development request receives a validation
generation signal. Starting a newer request aborts the previous
generation. Validation waits for the Node response `finish` event,
yields through the event-loop poll phase between attempts, and combines
the generation signal with React's existing prerender abort signals.

Tests:

- `pnpm --filter=next build`
- `pnpm --filter=next types`
- `pnpm exec jest --runTestsByPath
packages/next/src/server/app-render/dev-validation-scheduler.test.ts
packages/next/src/server/app-render/wait-for-response.test.ts`
- `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo
test/e2e/app-dir/instant-validation/suspense-boundaries.test.ts -t
"valid - static prefetch - suspense around runtime and dynamic"`

<!-- NEXT_JS_LLM_PR -->
2026-07-21 09:53:43 +02:00
Benjamin Woodruff 390eff3b86 [ci] Share a single browser instance across all test suites in a single job (#95589)
The idea here is that we waste a bunch of CPU setting up each test suite
by spawning an entirely new Chrome process, but playwright can share the
same browser across many tests. Tests are still isolated at the browser
level, just not at the OS process level.

Hoping to see some performance improvement in e2e tests. Compare to:
https://github.com/vercel/next.js/pull/95617

Somewhat unscientific (single sample) results:

```
Comparing the head-commit build-and-test runs — PR #95589 run [28981702219 ](https://github.com/vercel/next.js/actions/runs/28981702219)vs PR #95617 run [28981686692](https://github.com/vercel/next.js/actions/runs/28981686692). Both ran 101 jobs and both succeeded.

  ┌─────────────────────────┬────────────────┬───────────────────────┬───────────────────┐
  │         Metric          │ #95617 control │ #95589 shared browser │       Delta       │
  ├─────────────────────────┼────────────────┼───────────────────────┼───────────────────┤
  │ Raw wall-time sum       │ 616.2 min      │ 571.2 min             │ −45.0 min (−7.3%) │
  ├─────────────────────────┼────────────────┼───────────────────────┼───────────────────┤
  │ Billable (ceil per job) │ 658 min        │ 617 min               │ −41 min (−6.2%)   │
  └─────────────────────────┴────────────────┴───────────────────────┴───────────────────┘

  The shared-browser experiment is cheaper, and the savings land almost exactly where you'd expect — the browser-driven test suites — while non-browser jobs (rust, lint, unit, windows) are flat within noise:

  ┌─────────────────────────────────┬─────────┬────────┬───────┐
  │          Job category           │ control │ shared │ delta │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ test prod                       │   130.7 │  110.5 │ −20.2 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ cache components dev            │    51.5 │   42.9 │  −8.7 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ turbopack dev                   │    75.1 │   69.5 │  −5.5 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ turbopack production            │    86.3 │   81.1 │  −5.2 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ test dev                        │   110.4 │  105.5 │  −4.9 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ cache components prod           │    48.7 │   49.0 │  +0.3 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ firefox and safari              │     5.3 │    7.3 │  +2.0 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ flake-detection jobs (combined) │     ~15 │    ~21 │   +~6 │
  └─────────────────────────────────┴─────────┴────────┴───────┘

  Takeaways:
  - Net savings of ~41 billable minutes per run (~6%), concentrated in the Chromium-driven test prod/dev, turbopack, and cache components suites — consistent with reusing one browser process instead of spawning per-suite.
  - The small regressions are in firefox and safari and the "new/changed tests for flakes" jobs (+~8 min combined). Worth a glance, though they may just be run-to-run variance.

  Caveat: this is a single run per PR, so there's real variance run-to-run. That said, the fact that the deltas track the browser-heavy jobs specifically — and not the Rust/lint/unit jobs — is a good signal the effect is genuine rather than noise. If you want a firmer number, re-running each PR 2–3× and averaging would tighten it up.
```
2026-07-21 01:36:10 +00:00
Niklas Mischkulnig 756d6dc9ea Move immutable static assets config option out of experimental (#95351)
Keep reading `config.experimental.supportsImmutableAssets` for now to
not break existing adapters until they are migrated.

Closes NAR-868
2026-07-20 15:35:14 +02:00
Sebastian "Sebbie" Silbermann 3ae5630d0a [ci] Allow running all deploy tests with builds from a private registry (#95784) 2026-07-16 11:33:21 +02:00
Niklas Mischkulnig 1a7e6bda0f chore: Remove stale build warning (#95813)
Added in https://github.com/vercel/next.js/pull/88001
But it doesn't actually work. It thinks the build is always stale
2026-07-15 18:40:46 +00:00
Benjamin Woodruff 337a40c672 [ci] Pin typescript version in tests (#95619)
Typescript 7 came out, which is breaking a lot of our e2e tests.
2026-07-08 14:12:48 -07:00
Niklas Mischkulnig da06078b9b test: Fix immutable static asset deployment tests for real (#95600)
https://github.com/vercel/next.js/pull/95550 wasn't enough:

-
https://github.com/vercel/next.js/actions/runs/28907225463/job/85769093204
-
https://github.com/vercel/next.js/actions/runs/28907225463/job/85769093252

Because `VERCEL_IMMUTABLE_STATIC_FILES_ENABLED` might be set, but it
still only works when using adapters, so it broke in the non-adapter
deploy test jobs.

Also prepare for when we switch the default of `NEXT_ENABLE_ADAPTER` by
setting it explicitly to `0` for when we want to test the old CLI
builder.
2026-07-08 14:16:35 +02:00
Hendrik Liebau 60ddf0966a Cancel a superseded Server Components HMR refresh's server-side work (#95486)
When rapid edits overlap Server Components HMR refreshes in `next dev`,
only the newest refresh can commit. The client already aborts a
superseded refresh's fetch, which closes its response. We use that
response-close to stop the server work the superseded refresh started,
so the dev server no longer runs a render (and, under Cache Components,
a validation) whose result is discarded.

When the `serverComponentsHmrCancellation` flag is enabled and the
request is an HMR refresh over a Node response, we derive an abort
signal from `signalFromNodeResponse(ctx.res.originalResponse)` and
thread it into the Flight render. This covers both dev RSC render paths:
the Cache Components staged render in
`generateDynamicFlightRenderResultWithStagesInDev` and the
non-Cache-Components render in `generateDynamicFlightRenderResult`.

The render is aborted through `renderToNodeFlightStream`, which calls
`abort()` on the pipeable that `renderToPipeableStream` returns when the
signal fires. React's Node Flight API has no `signal` option, unlike the
Web `renderToReadableStream`, aborting the returned pipeable is the
intended way to stop the render. This also removes the incorrect
`signal` field from the `renderToPipeableStream` type declaration and
derives `FlightRenderOptions` from `renderToReadableStream`'s options,
so the render wrappers are no longer typed as `any`.

Under Cache Components the superseded refresh additionally skips its
detached validation, which only runs on the staged render, so it never
validates a discarded tree, and any error thrown while its background
renders tear down is swallowed. The aborted Flight renders surface as
abort errors that `createReactServerErrorHandler` already ignores, so
nothing reaches the error overlay or the CLI.

The `hmr-rsc-cancellation` suite exercises both render paths, with Cache
Components controlled by the `__NEXT_CACHE_COMPONENTS` test shard rather
than the fixture config. A child component logs when React renders it,
and the test asserts the superseded refresh never logs, since its render
is aborted before reaching the child, while the committed refresh does.
The detached-validation assertions run only when Cache Components is
enabled.
2026-07-07 21:53:00 +02:00
Niklas Mischkulnig 8b5df172e4 test: Fix immutable static asset deployment tests (#95550)
1. The env var ended up having a different name than I foresaw months
ago. Now `next.assetToken` is correctly `undefined` if the deployment
used immutable static assets.
2. `expect(searchParams.get(...)).toBe(undefined)` was failing because
`.get()` returns null for unknown keys

Failures:
-
https://github.com/vercel/next.js/actions/runs/28831699621/job/85515363446
-
https://github.com/vercel/next.js/actions/runs/28831699621/job/85515363310
2026-07-07 16:53:46 +02:00
Hendrik Liebau cd0aee96d7 Re-query deploy-test build logs until id markers appear (#95243)
Deploy tests recover the `BUILD_ID`, `DEPLOYMENT_ID` and
`NEXT_SUPPORTS_IMMUTABLE_ASSETS` values by scraping them out of the
deployment's build logs, where the test fixture's `post-build` script
prints them as the final lines of the build. We fetch those logs with
`vercel inspect --logs` after `vercel deploy` returns, but a deployment
can report `Ready` before its full build-log tail has propagated to the
log query API, so a single call sometimes returns a truncated prefix
that stops before the markers. When that happens `parseIdsFromCliOuput`
throws "Failed to get buildId from logs" and fails the whole suite
during setup, which is the intermittent failure we have been seeing
across the deploy jobs.

This change wraps the log fetch in `fetchBuildLogsUntilComplete`, which
re-queries `vercel inspect --logs` until the last-printed marker
(`NEXT_SUPPORTS_IMMUTABLE_ASSETS`) appears before parsing, bounded to
twenty attempts three seconds apart. Gating on the final marker rather
than the first ensures a partial read cannot slip past the loop into the
parser. Streaming the logs from `vercel deploy --logs` as part of the
deploy itself was considered, but it has the same race: the CLI stops
streaming once the deployment reports `Ready`, before the `post-build`
tail flushes, and a single stream cannot re-query for the missing lines.
2026-07-01 21:08:58 +02:00
Janka Uryga c131314bcf [PP] Validate Shell prefetches (except gSP) (#95151)
Implements instant validation for `partialPrefetching`. In this mode,
`<Link>` prefetches an App Shell, which cannot access link data, and we
need to warn for that.

The changes in `instant-validation.tsx` are relatively simple: for an
App Shell, we simply use `ShellRuntime` for all the new segments. We
might also force them into `Runtime` for the purposes of discriminating
dynamic holes. If a hole is present in `ShellRuntime` but disappears in
`Runtime`, then we know it's caused by **link data** (as opposed to
runtime or dynamic data). I've added some new error messages for this
case.

Note that the implementation here is incomplete: it uses the chunks from
the dev render, which resolves static params in the `Static` stage. We
use `ShellRuntime` for validating the App Shell, so as a result, static
params are incorrectly included in it and don't trigger link data
errors. This will be implemented in a follow-up.

Note: It seems like we have some pre-existing bug in build validation
where `fallbackParams` aren't populated, so params resolve statically
when they shouldn't. I've marked two tests with `// TODO(app-shells):
missing fallback params in build validation` so we can follow up and fix
those.
2026-07-01 02:36:41 +00:00
Sebastian "Sebbie" Silbermann dcb662c7fb [ci] Add support for auth based preview build registries (#95204)
Follow-up to https://github.com/vercel/next.js/pull/93464 which didn't
consider auth based preview build registries.
2026-06-30 20:59:15 +02:00
Hendrik Liebau 6771a60dfc [test] Recover from a leftover build process on test retry in build() (#95304)
PR #94797 taught `NextStartInstance.start()` to recover from a leftover
`next build` process left behind when a previous test attempt was
interrupted mid-build by the per-test timeout, but `build()` kept
throwing `can not run export while server is running` unconditionally
whenever a child process was still tracked. Tests that only call
`next.build()` (and never `start()`) therefore still failed their
`jest.retryTimes` retry with that misleading error, masking the original
timeout.

This extracts the recovery logic into a shared
`stopLeftoverBuildOrThrow` helper and routes both `start()` and
`build()` through it. When a leftover `next build` process from an
interrupted attempt is found, it is stopped so the retry can continue
from a clean state; a genuinely running server still throws, preserving
the existing guard. Sharing one helper keeps the two call sites in sync.
2026-06-30 14:00:47 +02:00