- 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
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.
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.
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>
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.
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.
### 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 -->
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.
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.
### What?
Migrate remaining direct `next-webdriver` test callers that have a
`NextInstance` to `next.browser()`, and expose the shared `Playwright`
browser type from `e2e-utils`.
### Why?
`NextInstance.browser` should be the supported browser-opening interface
for test fixtures, with `next-webdriver` kept as the private
implementation detail.
### How?
Updated affected development, e2e, and production tests to call
`next.browser()` directly, passing `baseUrl` where tests intentionally
target a manually spawned or proxied server. Shared helpers now receive
browser callbacks from the test context, and browser types import
`Playwright` from `e2e-utils` instead of deriving from `next.browser` or
importing from private paths.
<!-- NEXT_JS_LLM_PR -->
After a successful, Turbopack production build, this writes
`.next/diagnostics/route-bundle-stats.json`
containing First Load JS stats for every route.
## Why?
We used to have route bundle size statistics print after every build. It
was not very high-signal and could be misleading, often using heuristics
to give a best-effort estimate of what sizes may be at runtime. **These
kinds of metrics for route bundle sizes can be misleading, as pages can
be slow to visual/interactive completeness even if they have small first
load bytes.**
However, a minimal gauge of how much JavaScript a route ships during
initial page load can be a useful signal so long as users have
additional context and awareness of their app structure. Let's provide
just this number for now for those that could benefit.
Additionally, this does so in the form of writing a json document to the
build directory, rather than writing a table to stdout, where users have
needed to write extra tools to parse.
## Format
An array of route entries sorted descending by
`firstLoadUncompressedJsBytes`:
```json
[
{
"route": "/blog/[slug]",
"firstLoadUncompressedJsBytes": 124928,
"firstLoadChunkPaths": [
".next/static/chunks/framework.js",
".next/static/chunks/main.js",
".next/static/chunks/pages/blog/[slug].js"
]
}
]
```
`firstLoadUncompressedJsBytes`: sum of uncompressed sizes of all JS
chunks loaded on first navigation to the route (shared chunks included),
for both Pages Router and App Router
`firstLoadChunkPaths`: paths to those chunks relative to the project
root
## Test Plan
Added an integration test. Built with a create-next-app and verified the
json document is written and the numbers align with what Chrome reports
during initial load of a page.
Initial version of build-time instant validation.
During prerendering, if we have `unstable_instant` configs, we perform a
complete staged render of the page (including dynamic data). This render
mocks request data (`params`/`searchParams`/`cookies`/`headers`) using
the `samples` provided in `unstable_instant`. Then, we use the staged
chunks to run instant validation in mostly the same way that we would in
dev. Most changes in the validation flow are related to samples, which
also need to be passed into the client render to mock APIs like
`useParams`.
### Providing Mock data
We run everything in a fresh workStore+workUnitStore to (hopefully)
avoid any data from the prerender from sneaking in. This is quite
involved.
A big part of the trickiness is that if you do e.g. `(await
cookies()).get('myCookie')` but the `samples` don't contain `myCookie`,
we want the validation to error and require explicitly saying what state
`myCookie` is in.
If it's available, use
```ts
export const unstable_instant = {
// ...
samples: { cookies: [{ name: "myCookie", value: "someValue" }] }
}
```
If it's not available, use
```ts
export const unstable_instant = {
// ...
samples: { cookies: [{ name: "myCookie", value: null }] }
}
```
This explicitness makes it harder to e.g. unintentionally validate a
logged-out state.
Note that even validating a `prefetch: 'static'` page might require
samples, because we need the shared parent for the navigation to be
(mostly) complete. We might be able to relax this in the future, but
it's not straightforward.
Another issue is that a segment might access a missing sample in a
"benign" way, i.e. where it wouldn't block validation (or be inside a
suspense boundary). Ideally we wouldn't require providing a sample for
usages like that, but currently we do.
### Instrumenting APIs to detect missing samples
All request APIs are instrumented in this way.
- `params` throw when accessing a property that was not declared in
`samples`. same for the object from `useParams()`. `in` is allowed
because we know the shape of the params object statically, based on the
routing structure.
- `searchParams` throw when accessing a property that was not declared
in `samples` and `in` checks.
- objects returned from `cookies()`, `headers()`, `useSearchPrams()`
throw on `get()` and `has()`.
There's a lot of tests for this behavior in `instant-validation-build`.
One gap is that iteration/enumeration is currently not instrumented.
We're punting on it for now and will revisit in a follow-up.
### Relation to prerender
We kick off the validation after the prerender, in
`renderToHTMLOrFlightImpl`. Ideally, we should kick this off somewhere
higher up, separately from the prerender itself, but we're leaving that
for future work.
Note that if we do multiple prerenders for the same page (e.g. due to
`generateStaticParams`, we'll only run validation for the first
prerender for that route. `generateStaticParams` is not currently
integrated into the validation at all, i.e. we don't use those params to
validate, so this is safe. All params need to be provided via `samples`.
This will likely change in the future, but we're not sure how yet.
### Disabling build validation
Build-time instant validation is experimental, and has more potential
problems to deal with than dev validation. To allow incremental rollout
and testing, we allow disabling it per instant config:
```ts
export const unstable_instant = {
unstable_disableBuildValidation: true
// ...
}
```
for parity, there's also `unstable_disableBuildValidation`, but we don't
expect that to see much use.
`run-tests.js` explicitly unsets `CI` in the spawned Jest child process
and preserves the original value as `NEXT_TEST_CI`. Since
`test/lib/e2e-utils/index.ts` runs inside that child process,
`process.env.CI` is always empty, making the `jest.retryTimes(1)` call
effectively dead code in CI.
The per-test `jest.retryTimes(1)` added in #89929 is useful for CI
resilience & performance but produces confusing logs and adds
unnecessary wait time when investigating test failures locally.
We're now only enabling the retry when running in CI so that local runs
fail fast on the first attempt.
Fork the set of deployment tests so they run with both turbopack and
webpack. Now that turbopack is the default bundler we need this
coverage.
Because we are now running twice as many tests, decrease concurrency,
otherwise we hit rate limits on vercel. There were attempts to improve
rate limit recovery in the vercel CLI
(https://github.com/vercel/vercel/pull/14443 and
https://github.com/vercel/vercel/pull/14407) which helped but did not
solve the issue. Also there was an investigation into the API service
where we discovered some suspicious but ultimately correct code
(https://github.com/vercel/api/pull/55967).
The basic issue is that the vercel CLI polls `api-deployments-get`
fairly aggressively at the beginning of a deploymnet, and since we start
so many deployments in parallel we will always hit the rate limits. The
recovery logic in the CLI is good but has (reasonably) a fixed set of
retries it will attempt, inevitably some tasks get unlucky. So retrying
the test does work, but really we should slow down which is what we do
here. 😥https://github.com/vercel/next.js/actions/runs/20290161194/job/58272593085
Fixes PACK-5613
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
This aims to avoid us having tests stall for upwards of 240s when a
single assertion in the test stalls. This is very common currently. To
also avoid wasting the entire time of resetting the test suite this also
allows 1 retry for individual failed assertions. This won't always work
since some assertions rely on previous setup so this also keeps the
entire test suite retry handling as a fallback.
This change is not applied for dev tests as they check HMR and compile
lazily.
## Summary
- keep elevated Jest timeout for test setup/startup in e2e-utils
- enforce a 60s default timeout for individual `it`/`test` cases
- apply one retry per individual test via `jest.retryTimes(1)`
## Verification
- pnpm testonly test/development/gssp-notfound/index.test.ts
- pnpm testonly test/development/enoent-during-require/index.test.ts
- pnpm testonly test/e2e/custom-app-render/custom-app-render.test.ts
Tested against our deploy tests to see if this helps:
Current run
[21967823508](https://github.com/vercel/next.js/actions/runs/21967823508)
Jobs: 24 (with test-runner output: 20)
Whole-suite retry starts: 11 (retry 1: 8, retry 2+: 3)
Jobs that needed suite retries: 8
Unique test files retried: 7
Previous run
[21964085768](https://github.com/vercel/next.js/actions/runs/21964085768)
Jobs: 26 (with test-runner output: 20)
Whole-suite retry starts: 18 (retry 1: 13, retry 2+: 5)
Jobs that needed suite retries: 9
Unique test files retried: 12
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 ensures the tests have some coverage for `next dev`, and ensures proper test isolation. Previously they were just using `__dirname`, which is wrong.
What?
This PR updates the dependency "prettier" from version 3.2.5 to version
3.6.2. It also modifies other scripts by using the pnpm run prettier-fix
after updating the dependency.
Why?
This is updated to benefit from the changes and fixes introduced in the
newer versions of prettier, from versions 3.3 to 3.6.
How?
The package has been updated using pnpm install prettier@latest, and the
files other than package.json and pnpm-lock.json have been modified
using the script pnpm run prettier-fix.
This PR does only have formatting changes introduced by the updated
dependency
This PR is the same as #82719 , with fixes implemented to prevent
prettier to modifiy symlink files
### What?
This PR fixes an issue where pages with fallback route params weren't
triggering dynamic resume behavior in PPR (Partial Pre-Rendering).
### Why?
When fallback route params exist, the RSC (React Server Components) data
is inherently dynamic because the params are encoded into the flight
router state. Without this fix, pages with fallback route params would
incorrectly be treated as fully static, leading to incorrect behavior.
### How?
- Added a check in `app-render.tsx` to ensure that pages with fallback
route params (`workStore.fallbackRouteParams.size > 0`) always perform a
dynamic resume after the static prerender
- Refactored PPR tests to use deterministic sentinel markers (`<\!--
PPR_BOUNDARY_SENTINEL -->`) instead of time-based measurements for more
reliable testing
- Updated the test utilities to split responses based on the sentinel
marker rather than timing delays
This ensures that:
1. Pages with fallback route params correctly trigger dynamic behavior
2. PPR tests are more reliable and deterministic
3. The boundary between static and dynamic content is clearly marked in
test mode
Fixes #
- Follow the naming conventions of other environment variables and prefix `TEST_WASM` with `NEXT_TEST_WASM` so that there's less chance of conflicts with user code
- Remove the logic in `setup-wasm.mjs` that modifies the repository (not great for running tests locally) and instead try to match the environment-variable-override behavior used for native (non-wasm) bindings via `NEXT_TEST_WASM_DIR`.
- Hard-fail if `NEXT_TEST_WASM` is set and we fail to load wasm bindings instead of silently using fallback logic that loads native bindings instead.
- We don't need to install `wasm-pack` with `curl | sh` for tests: `pnpm` already installs it (and pins a specific version).
- The whole dance of renaming the directory from `pkg` to `pkg-nextjs` is a hack we use when publishing the package, as we're building both "web" and "nextjs" targets. We don't need to do that for tests.
- Fix handling of `undefined` values in SWC options objects.
- Remove hacks for Next v12.2
This PR fixes some bugs in our handling of
`serverActions.bodySizeLimit`. There's a couple fixes here, which can be
viewed commit by commit.
### 1. Uncaught exception error when exceeding the size limit
We were using `body.pipe(busboy)`, which does not forward errors from
the source to the sink, so the busboy stream would just hang, and we'd
log an error, but never produce a response. It seems like node is
(usually) smart enough to abort the request when this happens (because
it was triggering an uncaught exception), but we should be returning a
proper response instead. This is fixed by using
`require('node:stream').pipeline(body, busboy)`, which propagates errors
correctly.
### 2. Apply size limit to non-multipart fetch actions
We have a separate codepath for non-multipart fetch actions. This can
happen if the action arguments are simple enough that react doesn't need
to use multiple rows to serialize them -- generally, this means that
they're JSON-esque without any complex types like Promises, Maps, or
Sets.
This codepath was consuming the original request body, not the one piped
through the size limit transform. So we'd print the error (because the
transform is subscribed to the body), but still execute the action.
### 3. Tests
The likely reason that these bugs slipped through is that the tests were
only checking if an error was printed to the console, and not checking
if the server actually responded with an error. To prevent this, I've
added some assertions on the responses' status codes + checking whether
an error boundary was triggered. I've also tweaked the tests so that the
parts that submit actions can run in deploy mode.
The request interception code proved subtle/complicated enough that i
decided to pull it out into a separate helper. We have a whole bunch of
tests that intercept requests/responses using various ad-hoc tangles of
`page/browser.on('request', ...)` which i'd like to migrate to this
helper, because that'd make them easier to understand, but I'll leave
that for a follow up when there's time.
This brings back the env inlining step separate of compilation used for
the [flying shuttle
experiment](https://github.com/vercel/next.js/pull/73710) so that when
`next build --experimental-build-mode=compile` is used we don't inline
these values so that these artifacts can be cached/reused independent of
the env values. Then `next build --experimental-build-mode=generate` can
be used to inline the values and run prerendering to generate finalized
build.
waiting for metadata is only necessary when `dynamicIO` is enabled. In
the long run we will get rid of this waiting anyway when we have async
dynamic APIs so we condition this behavior to be only when dynamicIO is
on for now
This work introduces the new concept of **Partial Fallback Prerendering
(PFPR)**.
Traditionally, when a dynamic page needed to be routed to that wasn't
pregenerated, it required a render to generate even the first few bytes
of the static page itself. This resulted in slow page loads for pages
not frequently visited and a reduced Time to First Byte (TTFB) score on
Core Web Vitals (CWV).
PFPR takes advantage of the new systems of Partial Prerendering (PPR)
that allows the application to suspend at different points mid-render,
and resume it later. We mark any unknown parameter access as dynamic
access, and suspend the rendering up to the next suspense boundaries at
those points. Under ideal conditions (correctly placed `<Suspense />`
boundaries or `loading.jsx` files) this generates a static shell that
can be served to users as soon as the request hits Next.js, right out of
the static cache. This minimizes the TTFB for all requests, dynamic or
not for those pages that enable PPR. For example, the following page
would create a usable shell:
```jsx
// /app/users/[userID]/page.jsx
import { Suspense } from 'react'
function Profile({ params }) {
const { userID } = params
return <div>Hello {userID}!</div>
}
export default function ProfilePage({ params }) {
return (
<div>
<h1>User Profile</h1>
<Suspense fallback="Loading...">
<Profile params={params} />
</Suspense>
</div>
)
}
```
Due to the way that suspense works within React components, access of
params within the root page component would cause the whole page to
suspend. Thankfully, that's where the `loading.jsx` comes in handy.
Adding a `loading.jsx` at a segment will automatically wrap the
`page.jsx` with a suspense boundary, setting the contents of the root
`loading.jsx` as the fallback component to use for it. This lets you
maintain your existing style of accessing parameters at the root of the
components while also taking advantage of PFPR.
To enable this feature, you first need to enable both PPR and PFPR:
```js
module.exports = {
experimental: {
ppr: true,
pprFallbacks: true,
}
}
```
Once PFPR has stabilized with hosting providers, the experimental flag
will go away and it will become the default with the PPR flag.