mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
v16.4.0-canary.21
9714 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db66a5094d |
Create next.config.ts when generating apps with pnpm new-test (#98364)
Updates the template used by `pnpm new-test` for app-dir tests to create a `next.config.ts` instead of `next.config.js`. |
||
|
|
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. |
||
|
|
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:*`. |
||
|
|
b0b7a429b9 |
[PPF] Fix missing runtime-follow up if hints are stale (#98278)
if a page didn't use runtime data during build but started doing so after the revalidation (i.e. the hints say its statically-prefetchable but now it's not), we're supposed to be doing a follow-up runtime request based on the `needsRuntimeRequest` promise (introduced in #96095), but this was not actually happening. the bug goes as follows: 1. if we get an entry from a static prefetch, but `needsRuntimeRequest` is true, we save it as `PPR` (or `StaticShell` if it's a shell rewound from the static response). otherwise, we'd have saved it as `PPRRuntime` (or `RuntimeShell` for shells) to indicate that it satisfies those requests. 2. `wouldRuntimeRequestProvideMore` compares the runtime strategy for the current static walk to the entry's strategy to check if it should do a runtime request. this boils down to a comparison of `RuntimeShell > PPR` for a shell request 3. however, in the `FetchStrategy` enum, `RuntimeShell = 2` and `PPR = 3`, so `RuntimeShell > PPR` is false, which makes `wouldRuntimeRequestProvideMore` return `false`. so, if we have a `PPR` entry, **we'll never do a follow-up runtime shell request**. This is fixed by swapping the order of the two values in the enum. Now, if we end up with a `PPR` response where `needsRuntimeRequest` is true due to runtime data, that indicates that we *should've done a runtime request in the first place*, we just didn't know we should do so because the prefetch hint was stale, so the new ordering achieves the correct behavior, i.e. `pingSegmentBundle` decides that it needs a runtime shell request, which makes it match the intended PPF prefetching behavior. The enum ordering is a bit of a lie, because there is no single correct ordering -- a `PPR` entry *might actually provide more content* than a `ShellRuntime` entry if the route uses static params. In practice this is resolved by the vary path (shell responses are keyed with fallback params, and a PPR entry with static params would have the actual param values, and the param-ful entry would end up being preferred) so the strategy order wouldn't matter. Now that `RuntimeShell` beats `PPR`, i had to disable the strategy upgrading (`StaticShell -> RuntimeShell` and `PPR -> PPRRuntime`) on routes that don't use Partial Prefetching -- otherwise, a rewound`ShellRuntime` entry from a different param was winning over `PPR` in `pingSegmentBundle`, so it was preventing issuing new static requests even if we needed to do them. However, without PPF, the runtime tiers are not meaningful anyway, so this should be fine. ### Other bugs This fix also revealed a bug where `FetchStrategy.Full` requests were writing a shell as `RuntimeShell` even though the byte offset only marks a *static* shell. I've refactored the logic for picking the strategies to fix this and make it more explicit. ### Edge cases there is one (somewhat involved) edge case to this that i know of. if the post-revalidation static response has: 1. a shell that didn't access runtime data (i.e. `needsRuntimeRequest = false` in the shell stage) 2. a prefetch that did access runtime data (i.e. `needsRuntimeRequest = true` in the static stage) then we'll: 1. write the shell as `ShellRuntime` (because `needsRuntimeRequest = false`, and a runtime shell would not in fact provide more data) 2. write the prefetch as `PPR` (and not `PPRRuntime`, because `needsRuntimeRequest = true`, and a runtime prefetch *would* provide more data) with the new ordering, this means we'll end up preferring the `ShellRuntime` entry over the `PPR` entry even though the shell is a strict subset of it and would be missing static params and content gated behind `prefetch()` or `navigation()`. (note that this only happens if they share the same vary path) this can be observed as follows: 1. when a `<Link prefetch={true}>` to such a page is revealed, we won't request a runtime shell (due to already having a `ShellRuntime` entry), but we will request a runtime prefetch (because `PPR` is not enough). so far this is correct 2. if the navigation happens **before** this runtime prefetch completes, we will display what we already have. but we'll prefer the rewound `ShellRuntime` shell entry over than the `PPR` entry it originated from because of the enum ordering. so we may end up not showing content despite having it in the cache i'm not sure how to resolve this yet, but i think the current `recordedFetchStrategy` trick can't handle this, and we need a more involved mechanism that can detect when the `PPR` entry is actually better. i don't consider this a blocker for this fix though |
||
|
|
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. |
||
|
|
e974969ddd |
[PPF] Model caches with stale < MIN_SHELL_STALE as prefetch-only data (#98339)
Caches that have `stale < MIN_SHELL_STALE` were using `makeStageHangingPromise` in prerenders, which (in theory) was meant to track a runtime data access during a static prerender, which in turn would make the page use runtime shells. This is semantically incorrect, because both static and runtime shells would exclude the cache, and both static and runtime prefetches would include it, so it does not actually signal runtimeness, only prefetchness. The only thing we should be doing for these caches is excluding the content, same as `unstable_prefetch()`. This PR replaces `makeStageHangingPromise` with `makePrefetchHangingPromise`, which is not tracked as runtime data at all. We also use it for `unstable_prefetch()` to signal the correspondence. Note that in practice, the usage of `makeStageHangingPromise` in `use-cache-wrapper` was *technically* fine, because its runtime data tracking was effectively dead code. We only returned it if `stagedRendering.finalStage < stage`, but for static prerenders `stage = staticLinkDataStage = PrefetchStatic` and `finalStage = Static`, so the condition was never actually true, and we never actually tracked it as a runtime data access. However semantically it should still not be tracked as runtime data. (The condition *can* be true in a runtime prerender that only prerenders the shell. We don't do any tracking in runtime prerenders, so it was also a no-op). (this was extracted from #98129, where we separate runtime data access tracking into session data and URL data. i couldn't figure which one `makeStageHangingPromise` should be tracked as, but then i realized it shouldn't be tracked at all) |
||
|
|
9d9e1d5b00 |
Fix turbopacks webpack loader setup to support options being optional (#98270)
If you look at the types for webpacks loaders, you can see that options is optional. https://github.com/webpack/webpack/blob/c17742f631eaeaf1ad880a70b105eedd96dd21b9/declarations/LoaderContext.d.ts#L83 The implementation between the two is slightly different so webpacks impementation doens't jsut set it to {}. But with the way turbopack works, this is the equivalent. I saw a bunch of failures all related to this in real loaders, so figure it was an easy quick fix. (also extensionOptions is optional in webpack and needed to be here for the the types to all checkout https://github.com/webpack/webpack/blob/c17742f631eaeaf1ad880a70b105eedd96dd21b9/declarations/WebpackOptions.d.ts#L2175) |
||
|
|
7167297192 |
test: stabilize server action HMR update (#98322)
### What? Stabilize the development-mode server action HMR test when an unrelated Fast Refresh reload occurs while the patched action is being applied. ### Why? The test retried by dispatching another click before the previous asynchronous server action had settled. Under CI load, those requests overlapped with HMR and an unrelated full-page Fast Refresh reload. The reload reset the client counter, so the correctly updated action returned exactly `1000`, which the test rejected because it assumed the pre-reload state was preserved. ### How? Wait for network idle after each action invocation so retries do not observe an in-flight response, allow the valid post-reload value of `1000`, and use a retry window that accommodates CI HMR latency. The assertion still distinguishes the updated `+1000` action from the original `+1` implementation without requiring client state to survive an unrelated reload. ### Verification - `pnpm test-dev-turbo test/e2e/app-dir/actions/app-action-node-middleware.test.ts -t "should support updating the action"` (10 consecutive passes) - `pnpm test-dev-webpack test/e2e/app-dir/actions/app-action-node-middleware.test.ts -t "should support updating the action"` (4 consecutive passes) - Prettier and ESLint on `test/e2e/app-dir/actions/app-action.test.ts` <!-- NEXT_JS_LLM --> <!-- fleet 5793f439-a569-44be-ad19-d456ce9b641c --> 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> |
||
|
|
e9c16094f2 |
Fix flaky successive HMR changes test (#97610)
### What? Correct the fixture marker used to construct the second invalid source state in the successive App Router HMR test. ### Why? The test was replacing a marker that does not exist in its MDX fixture. Because string replacement silently returned the original content, supposedly successive patches sometimes wrote byte-identical source while the test harness waited for an HMR completion callback. A no-op write does not have to produce an HMR update, so the callback could remain pending until the harness reported a misleading timeout. CI logs showed Fast Refresh itself completing quickly, which ruled out an insufficient timeout budget and made extending the timeout the wrong fix. ### How? Match the replacement to the fixture's actual marker. This restores four genuinely distinct source states in every error/recovery cycle, preserving the intended overlay coverage without changing Turbopack, the shared HMR helper, or its timeout. ### Verification - `pnpm test-dev-turbo test/development/acceptance-app/app-hmr-changes.test.ts` - Repeated the focused test three times under 14 CPU-contention workers - Prettier and ESLint on the changed test file <!-- NEXT_JS_LLM --> --------- 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> |
||
|
|
0ac302d3d8 |
Turbopack: fix source field in CSS sourcemaps (#88225)
Closes #88294 Currently, CSS source maps in Turbopack always use index `0` for all source file references. This can lead to misleading mappings and makes debugging CSS generated from multiple sources more difficult. |
||
|
|
00b29d6654 |
fix: track dynamic accesses in final runtime prerenders (#98255)
The fix implemented in #95665 (`params/searchParams` not hanging correctly in Runtime App Shells during prospective prerenders) was incomplete. We started using `makeHanging[Search]Params` in the prospective prerender (if it was an app shell and wouldn't resolve them), but not during the final prerender. This meant that, when encoding the cache key, we would only fire `dynamicAccessAbortSignal` in the prospective prerender: https://github.com/vercel/next.js/blob/090f1b72753b2d0094235d27d46ffa0da5fc7d35/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2160-L2174 ...but the final prerender wasn't using `makeHanging[Search]Params`, so it would not hit this early exit. The hanging input abort signal would still correctly abort the key encoding (because it accounts for `stageController.finalStage`) so the key would still be partial, as expected. We would continue on, and attempt to look the key up in the Resume Data Cache. But the prospective prerender exited early and never put anything under that key, so we would incorrectly end up here, and log: https://github.com/vercel/next.js/blob/090f1b72753b2d0094235d27d46ffa0da5fc7d35/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2730-L2734 This is fixed by consistently using `makeHanging[Search]Params` if the runtime prerender won't reach the stage where they resolve, i.e. if it's an App Shell. This means we'll trigger `dynamicAccessAbortSignal` while encoding the cache key in *both* renders, so we'll do the early exit consistently and avoid the log. (This feels a bit unsatisfying, because ideally this would all just work based on stages, which is what I idealistically thought when writing the code originally. But unfortunately our usage of `dynamicAccessAbortSignal` for `params/searchParams` requires these special cases to work consistently. Maybe this can be improved in the future.) ### Tests This scenario is covered by the existing `prefetch-runtime` tests -- i discovered the bug by running them and seeing the `Unexpected cache miss after cache warming phase` warning. We now assert that the warning is not logged. The assertion is negative and would also pass if the error message changed, so i've exported the message as a constant to ensure `use-cache-wrapper` and the tests stay in sync. |
||
|
|
cc27d89b5d |
Turn on the adapter route collapses by default (#98218)
`experimental.collapseAdapterRoutes` now defaults to `true`, so a build serves several dynamic routes from one entry in the route table that it passes to an adapter. The default started off in #97774 so that the behavior could reach projects one at a time. We have tested this successfully internally, so the default moves back. The option stays in place, so a build that hits a regression can set it to `false` and get the table that a build emitted before the collapses. The four fixtures under `test/production/app-dir/adapter-dynamic-routes` no longer set the option, because the default now gives them the collapsed table that their snapshots record. The snapshots themselves do not change, since the new default produces what the explicit option produced. A full deploy test run with the flag on by default was already done as part of #97738. |
||
|
|
090f1b7275 |
fix: suppress missing package manager errors in next info (#97946)
## What - capture stdout and stderr for both direct and shell-fallback package-manager version probes - keep successful version output unchanged and return `N/A` when a probe fails - add deterministic cross-platform regression coverage with a failing Yarn shim Closes #97932 ## Why PR #71134 added an `execSync` fallback so package-manager command shims can be resolved on Windows. When `stdio` is omitted, Node mirrors stderr from failed synchronous child processes to the parent, so an unavailable Yarn or pnpm can print raw shell errors before `next info` reports `N/A`. Passing `{ stdio: "pipe" }` to both probes preserves captured stdout and the Windows fallback while keeping expected probe failures silent. The change stays scoped to package-manager version detection; shared registry discovery is unchanged. ## Testing - `pnpm --filter next typescript` - `pnpm --filter next build` - `pnpm test-types --pretty false` - ESLint and Prettier on both changed files - `pnpm test-start test/e2e/cli/cli.test.ts` (66 passed, 1 skipped) - built-CLI reproduction with npm available and Yarn/pnpm absent: both report `N/A` with no Yarn/pnpm command-not-found output - autoreview with GPT-5.6 Sol xhigh and Claude Opus 5 xhigh: zero findings --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> |
||
|
|
e82978ca63 |
test: remove stale deploy manifest exclusions (#98051)
## Summary Remove two inert entries from the deploy-test manifest: - the entry for `test/e2e/app-dir/options-request/options-request.test.ts`, which no longer exists - the `test/production/standalone-mode/**/*` glob, which cannot match the manifest's e2e-only include rule This leaves the hard exclusion list limited to live, selectable test suites so follow-up deploy-testing work starts from an accurate inventory. ## Verification - Parsed `test/deploy-tests-manifest.json` and confirmed all 26 remaining exclusions resolve to files selected by the manifest's include rule. <!-- NEXT_JS_LLM --> |
||
|
|
65c832c8b8 |
Return client errors for unrecognized Server Actions (#98123)
## Summary - return `400 Bad Request` when a Server Action reference ID does not match the expected format - return `409 Conflict` when a well-formed action ID is unavailable in the current deployment, including deployment skew - apply the same classification to IDs from the `Next-Action` header and multipart MPA forms, including direct and bound actions in Node and Edge runtimes - return generic text bodies matching the client error category, without exposing action-ID details - derive marked forwarded-action responses from the original action ID so an unexpected or mixed-version worker response cannot change the client error semantics Fetch actions previously had dedicated 404 handling, while equivalent MPA form submissions fell through to the generic error path and returned 500. This removes that transport-dependent behavior and distinguishes malformed or scanning traffic from legitimate stale clients without treating either case as a missing page or an internal server failure. ## Verification - `pnpm test-dev-turbo test/e2e/app-dir/actions-unrecognized/actions-unrecognized.test.ts` - `pnpm test-dev-turbo test/e2e/app-dir/actions/app-action.test.ts -t 'forward.*action'` - `pnpm test-start-turbo test/e2e/app-dir/actions-unrecognized/actions-unrecognized.test.ts` - `pnpm test-dev-webpack test/e2e/app-dir/actions-unrecognized/actions-unrecognized.test.ts` - `pnpm test-dev-turbo test/e2e/app-dir/no-server-actions/no-server-actions.test.ts` <!-- NEXT_JS_LLM --> |
||
|
|
fb4c498852 |
Upgrade web-vitals to v6 and report soft navigations (#97232)
## Summary Upgrades the vendored `web-vitals` dependency to v6.0.1 and enables soft-navigation reporting for CLS, INP, and LCP. This lets `useReportWebVitals` report metrics against the navigation where they occurred, including `navigationURL` for route attribution. The upgrade also removes the obsolete FID integration and compatibility patch, regenerates the standard and attribution bundles, and updates the public metric types and documentation to match the current upstream API. Related to #97161. ## Verification - Added production-mode coverage for initial and soft-navigation Web Vitals reporting with both Turbopack and Webpack. <!-- NEXT_JS_LLM --> |
||
|
|
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> |
||
|
|
538f3f68e9 |
Bump swc to v77 (#97407)
There have been breaking changes to the AST: - `BlockStmtOrExpr` is now `ArrowFunctionBody` - function bodies are `FunctionBody` instead of `BlockStmt` - JSX text literals are `Wtf8Atom`, instead of `Atom` |
||
|
|
bcfa15d0ed |
Remove the worker thread for turbopack builds (#96862)
Avoid spawning a worker thread to run turbopack The typical arguments for worker threads are that they isolate the JS heap and enable parallelism, but turbopack does both these things by default (native heap is isolated, the js deps are minor and have a lot of overlap with existing next server deps). There is also no substantial parallelism opportunity from worker threads that aren't immediately delivered by turbopacks execution model ## Benchmarks Measured with a paired A/B harness that alternates `head` and `base` builds so thermal drift and background load cancel out, discarding a warmup build per variant and clearing `.next` before each run. Both variants are the same commit except for this change. 35 paired iterations on a default `create-next-app` (App Router, TS), Node 20.11.1, Apple silicon. | Metric | this PR | canary | Diff | 95% CI | Wins | | --- | --- | --- | --- | --- | --- | | `next-build` total | 2478 ms | 2595 ms | **-117 ms (4.5%)** | [50, 183] | 30/35 | | `run-turbopack` | 1228 ms | 1335 ms | -107 ms (8.0%) | [62, 152] | 31/35 | | overhead (total - turbopack) | 1250 ms | 1260 ms | -10 ms (0.8%) | [-27, 46] | 20/35 | | wall clock | 2775 ms | 2888 ms | -112 ms (3.9%) | [45, 180] | 30/35 | | peak RSS | 1175 MiB | 1195 MiB | **-19 MiB (1.6%)** | [3.4, 35.3] | 22/35 | Significant by both a paired t-test and a Wilcoxon signed-rank test (p = 0.0001 for total build time, p = 0.025 for RSS). The Wilcoxon is included because a couple of slow builds per run would otherwise dominate the variance. Nearly all of the saving lands inside the `run-turbopack` span rather than around it, which is expected: that span wraps the worker spawn, the IPC to marshal the build across, and the teardown, so deleting the worker removes work from inside it. The win is proportionally smaller on larger apps, where compilation dominates. On `bench/basic-app` (~400 source modules, ~19s builds) the same ~115 ms is well inside the noise -- 12 paired iterations there showed no significant difference on any metric. So this is a real but small improvement that is only user-visible on small builds: | Metric | this PR | canary | Diff | 95% CI | | --- | --- | --- | --- | --- | | `next-build` total | 19313 ms | 19436 ms | -123 ms (0.6%) | [-256, 502] | | overhead (total - turbopack) | 1424 ms | 1415 ms | +9 ms (0.6% slower) | [-52, 70] | Caveats: single machine and two fixture shapes. Peak RSS is the build process's own footprint (`/usr/bin/time -l`), which includes the worker thread's isolate but not the separately spawned static-generation workers. A CI runner with fewer cores would plausibly show a larger effect. |
||
|
|
e9180eae2a |
errors: shorten "use cache" messages and unify them into one factory (#94300)
### What? Centralizes related <code>"use cache"</code> scope errors and rewrites them as shorter, actionable messages with consistent <code>Learn more:</code> links. It also updates the reachable revalidation errors for rendering and <code>generateStaticParams</code>. ### Why? The previous messages were long, inconsistent, and sometimes missing documentation links. Some new cache-specific revalidation messages were also hidden by an earlier render-phase error. The new messages name the constraint, give the immediate fix, and link to the relevant documentation. ### How? Adds centralized error factories and dedicated error pages for request data, cache configuration, private-cache composition, and revalidation. When an active App Router route is available, the messages include it. The error pages use consistent terminology and complete examples. The <code>unstable_cache()</code> reference now documents the request-dependent operations that these errors reject. ### Before and after Route-based examples use <code>/products</code>. Bracketed values are alternatives in matching order. For example, <code>[E1482, E1486, E1489]</code> maps to <code>[headers(), cookies(), request.url]</code>. Each runtime error contains one alternative, but the table groups messages with the same template. #### Messages that include the route | Cases | Before | After | | --- | --- | --- | | E1480 · <code>searchParams</code> in <code>"use cache"</code> | Route /products used <code>searchParams</code> inside "use cache". Accessing dynamic request data inside a cache scope is not supported. If you need some search params inside a cached function await <code>searchParams</code> outside of the cached function and pass only the required search params as arguments to the cached function. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache | Route "/products": <code>searchParams</code> can't be read inside <code>"use cache"</code>. Await it outside the cached function and pass what you need as an argument.<br><br>Learn more: https://nextjs.org/docs/messages/next-request-in-use-cache | | [E1482, E1486, E1489] · [<code>headers()</code>, <code>cookies()</code>, <code>request.url</code>] in <code>"use cache"</code> | Route /products used [<code>headers()</code>, <code>cookies()</code>, <code>request.url</code>] inside "use cache". Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use [<code>headers()</code>, <code>cookies()</code>, <code>request.url</code>] outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache | Route "/products": [<code>headers()</code>, <code>cookies()</code>, <code>request.url</code>] can't be read inside <code>"use cache"</code>. Read it outside the cached function and pass what you need as an argument.<br><br>Learn more: https://nextjs.org/docs/messages/next-request-in-use-cache | | [E1481, E1485, E1492] · [<code>cookies()</code>, <code>request.url</code>, <code>headers()</code>] in <code>unstable_cache()</code> | Route /products used [<code>cookies()</code>, <code>request.url</code>, <code>headers()</code>] inside a function cached with <code>unstable_cache()</code>. Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use [<code>cookies()</code>, <code>request.url</code>, <code>headers()</code>] outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache | Route "/products": [<code>cookies()</code>, <code>request.url</code>, <code>headers()</code>] can't be read inside <code>unstable_cache()</code>. Read it outside the cached function and pass what you need as an argument.<br><br>Learn more: https://nextjs.org/docs/app/api-reference/functions/unstable_cache | | [E1484, E1491] · <code>draftMode().enable()</code> in [<code>unstable_cache()</code>, <code>"use cache"</code>] | Route /products used "draftMode().enable()" inside [a function cached with <code>unstable_cache()</code>, "use cache"]. The enabled status of <code>draftMode()</code> can be read in caches but you must not enable or disable <code>draftMode()</code> inside a cache. See more info here: [https://nextjs.org/docs/app/api-reference/functions/unstable_cache, https://nextjs.org/docs/messages/next-request-in-use-cache] | Route "/products": <code>draftMode().enable()</code> can't be called inside [<code>unstable_cache()</code>, <code>"use cache"</code>]. Draft mode can be read inside a cached function, but enabling or disabling it must happen outside.<br><br>Learn more: [https://nextjs.org/docs/app/api-reference/functions/unstable_cache, https://nextjs.org/docs/messages/next-request-in-use-cache] | | [E1488, E1499] · <code>connection()</code> in [<code>unstable_cache()</code>, <code>"use cache"</code>] | Route /products used <code>connection()</code> inside [a function cached with <code>unstable_cache()</code>, "use cache"]. The <code>connection()</code> function is used to indicate the subsequent code must only run when there is an actual request, but caches must be able to be produced before a request, so this function is not allowed in this scope. See more info here: [https://nextjs.org/docs/app/api-reference/functions/unstable_cache, https://nextjs.org/docs/messages/next-request-in-use-cache] | Route "/products": <code>connection()</code> can't be called inside [<code>unstable_cache()</code>, <code>"use cache"</code>] because cached functions may run during prerendering, without an incoming request. Call it outside the cached function.<br><br>Learn more: [https://nextjs.org/docs/app/api-reference/functions/unstable_cache, https://nextjs.org/docs/messages/next-request-in-use-cache] | | E1494 · <code>connection()</code> in <code>"use cache: private"</code> | Route /products used <code>connection()</code> inside "use cache: private". The <code>connection()</code> function is used to indicate the subsequent code must only run when there is an actual navigation request, but caches must be able to be produced before a navigation request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache | Route "/products": <code>connection()</code> can't be called inside <code>"use cache: private"</code> because private cached functions may run during prefetching, without a navigation request. Call it outside the cached function.<br><br>Learn more: https://nextjs.org/docs/app/api-reference/directives/use-cache-private | | [E1483, E1495] · <code>revalidateTag("products")</code> in [<code>"use cache"</code>, <code>unstable_cache()</code>] | Route /products used "revalidateTag products" inside [a "use cache", a function cached with "unstable_cache(...)"] which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering | Route "/products": <code>revalidateTag("products")</code> can't be called during render, inside a cached function, or inside <code>generateStaticParams</code>. Call it from a Server Action or Route Handler instead.<br><br>Learn more: https://nextjs.org/docs/messages/revalidate-in-use-cache | | During render | Route /products used "revalidateTag products" during render which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering | Route "/products": <code>revalidateTag("products")</code> can't be called during render, inside a cached function, or inside <code>generateStaticParams</code>. Call it from a Server Action or Route Handler instead.<br><br>Learn more: https://nextjs.org/docs/messages/revalidate-in-use-cache | | Inside <code>generateStaticParams</code> | Route /products used "revalidateTag products" inside <code>generateStaticParams</code> which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering | Route "/products": <code>revalidateTag("products")</code> can't be called inside <code>generateStaticParams</code>. Call it from a Server Action or Route Handler instead.<br><br>Learn more: https://nextjs.org/docs/messages/revalidate-in-use-cache | #### Other messages | Cases | Before | After | | --- | --- | --- | | [E1477, E1490] · Nested cache with [short <code>expire</code>, <code>revalidate: 0</code>] | A "use cache" with [short <code>expire</code> (under 5 minutes), zero <code>revalidate</code>] is nested inside another "use cache" that has no explicit <code>cacheLife</code>, which is not allowed during prerendering. Add <code>cacheLife()</code> to the outer "use cache" to choose whether it should be prerendered [with longer <code>expire</code>, with non-zero <code>revalidate</code>] or remain dynamic [with short <code>expire</code>, with zero <code>revalidate</code>]. Read more: https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife | Route <code>"/products"</code>: A nested <code>"use cache"</code> with [a short <code>expire</code> (under 5 minutes), <code>revalidate: 0</code>] is inside an outer <code>"use cache"</code> that has no <code>cacheLife()</code>. Add <code>cacheLife()</code> to the outer one to choose whether to prerender it [with a longer <code>expire</code>, with a non-zero <code>revalidate</code>] or keep it dynamic [with a short <code>expire</code>, with <code>revalidate: 0</code>].<br><br>Learn more: https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife | | E1478 · External promise | Filling a "use cache" entry appears to be stuck on shared state from the outer render scope. The same function completed when run in isolation, which usually means a module-scoped value (for example a top-level Map used to dedupe fetches) is joining a promise created outside the cache. "use cache" already dedupes calls with the same arguments within a request and across requests on the same server instance, so the surrounding dedupe layer is both unnecessary and the likely cause. Remove it and rely on "use cache" alone for deduping.<br><br><em>No documentation link.</em> | Route <code>"/products"</code>: A <code>"use cache"</code> function is awaiting a promise created outside it. The same call completed when run in isolation, so a module-scoped value (often a top-level <code>Map</code> used to dedupe fetches) is most likely blocking it. <code>"use cache"</code> already dedupes calls with the same arguments. Remove the surrounding dedupe layer.<br><br>Learn more: https://nextjs.org/docs/messages/next-request-in-use-cache | | [E1479, E1498] · [<code>cacheTag()</code>, <code>cacheLife()</code>] outside a cached function | [<code>cacheTag()</code>, <code>cacheLife()</code>] can only be called inside a "use cache" function.<br><br><em>No documentation link.</em> | Route <code>"/products"</code>: [<code>cacheTag()</code>, <code>cacheLife()</code>] can only be called inside a <code>"use cache"</code> or <code>"use cache: private"</code> function.<br><br>Learn more: [https://nextjs.org/docs/messages/cache-tag-outside-use-cache, https://nextjs.org/docs/messages/cache-life-outside-use-cache]<br><br>Outside an App Router route, the same message is shown without the route prefix. | | E1487 · Prerender timeout | Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside "use cache".<br><br><em>No documentation link.</em> | Route <code>"/products"</code>: A <code>"use cache"</code> function took too long during prerendering. The most common cause is passing unresolved request-specific arguments, such as <code>params</code> or <code>searchParams</code>, into the cached function. Resolve the data before calling the function and pass only the values you need.<br><br>Learn more: https://nextjs.org/docs/messages/next-request-in-use-cache | | E1493 · Private cache inside a public cache | "use cache: private" must not be used within "use cache". It can only be nested inside of another "use cache: private".<br><br><em>No documentation link.</em> | Route <code>"/products"</code>: <code>"use cache: private"</code> can't be nested inside <code>"use cache"</code> because a shared cached function can't depend on private request data. Nest it only inside another <code>"use cache: private"</code>.<br><br>Learn more: https://nextjs.org/docs/messages/use-cache-private-composition | | E1496 · Private cache without a request | "use cache: private" cannot be used outside of a request context.<br><br><em>No documentation link.</em> | Route <code>"/products"</code>: <code>"use cache: private"</code> needs an active request, so it can't be used during <code>generateStaticParams</code> or other build-time contexts. Move it to a request-time component or function.<br><br>Learn more: https://nextjs.org/docs/messages/use-cache-private-composition | | E1497 · Private cache inside <code>unstable_cache()</code> | "use cache: private" must not be used within <code>unstable_cache()</code>.<br><br><em>No documentation link.</em> | Route <code>"/products"</code>: <code>"use cache: private"</code> can't be used inside <code>unstable_cache()</code> because <code>unstable_cache()</code> uses a shared cache that can't contain private request data. Call the private cached function outside <code>unstable_cache()</code>.<br><br>Learn more: https://nextjs.org/docs/messages/use-cache-private-composition | ### Runtime verification - Exercised the 13 route-based message IDs through minimal dev-runtime reproductions. Twelve reached the new factories from userland, with byte-identical output across three requests each. - The `request.url` error for `"use cache"` Route Handlers is currently limited to the prerender path. A dynamic request captured by a cached closure can bypass that tracking; this is existing framework behavior to follow up separately. - Passing the `searchParams` promise into a nested cached function can still surface the earlier synchronous dynamic-API error before this factory. The new message is verified when the cache scope reads its own `searchParams` value. - A follow-up preview verification triggered 17 of the 19 rewritten messages from userland with byte-identical output. The timeout-driven external-promise and prerender-timeout messages were verified in source and through their focused test coverage. ### Verification - <code>CI=1 pnpm build-all</code> - <code>pnpm --filter=next types</code> - <code>pnpm --filter=next build</code> - <code>HEADLESS=true pnpm test-dev-turbo test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts</code> - <code>HEADLESS=true pnpm test-start-turbo test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts</code> - <code>NEXT_SKIP_ISOLATE=1 HEADLESS=true pnpm test-dev-webpack test/e2e/app-dir/cache-components-errors/use-cache.test.ts -t 'cacheLife with (expire < 5 minutes|revalidate: 0)'</code> (6 tests and 6 snapshots passed) - <code>pnpm test-dev test/e2e/app-dir/cache-components-errors/use-cache.test.ts --projects jest.config.*</code> (42 tests and 42 snapshots passed across Turbopack and webpack) - <code>pnpm test-dev test/e2e/app-dir/use-cache-hanging/use-cache-hanging.test.ts --projects jest.config.*</code> (10 tests and 8 snapshots passed across Turbopack and webpack) - <code>pnpm test-dev test/e2e/app-dir/use-cache-configured-timeout/use-cache-configured-timeout.test.ts --projects jest.config.*</code> (4 tests and 2 snapshots passed across Turbopack and webpack) - Prettier, ESLint, and Alex on the changed source, tests, and error pages <!-- NEXT_JS_LLM --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
adba1e3f2a |
[PPF] Pass in original searchParams for private-cache pages (#98041)
The handing for page components marked with `use cache: private` was
assuming that searchParams can never hang in `prerender-runtime`, so it
passed the serialized `innerSearchParams` to the page, which we expected
to just be a resolved promise. This *used to* be correct when runtime
prerenders always had search params available, because the serialized
search params were always equivalent to `outerSearchParams`.
However, for `partialPrefetching` we started using `prerender-runtime`
for runtime shells, where search params are not available, and if a
private cache awaits them, it needs to abort filling and become dynamic,
same as when it awaits params. So we need to trigger
`dynamicAccessAbortController` when `searchParams` is awaited:
```tsx
export default async function Page({ searchParams }) {
'use cache: private'
// this should trigger `dynamicAccessAbortController.abort()`, so we need the original instrumented
// searchParams object instead of the serialized one
await searchParams
}
```
This is fixed by using the outer searchParams object in the
`isPageSegmentFunction` codepath of `use-cache-wrapper` -- we're
preserving the instrumented promise, so the cache prerender will abort
as expected.
(before this fix, a shell prefetch of such a page would get a
deserialized hanging promise for `searchParams` and hang until it times
out)
2 of the 3 newly added tests ("params in a public cache" and "params in
a private cache") were already passing, because `params` was already
being preserved, but i'm adding them here to prevent regressions. The
"search params in a private cache" one was failing due to the cache
timing out, and is now passing. I'm not covering "search params in
public cache" because that's an error and is already tested elsewhere.
|
||
|
|
ec107c16dd |
lazy server hmr (#96566)
## Summary Make Turbopack server HMR demand-driven. Server updates are now compiled and applied when the next relevant request writes an endpoint, instead of eagerly evaluating changed server modules after every file change. This replaces the aggregate server HMR subscription with an on-demand update API, while preserving incremental updates and falling back to full cache eviction when a restart is required. Test Plan: added an e2e test <!-- NEXT_JS_LLM --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
724641c6a2 |
Fix Turbopack HMR recovery after dropped connections (#97966)
### What? Track the latest applied Turbopack HMR 'hash' in the browser runtime and include it when subscriptions are restored after reconnecting. Reload the page when the client's hash differs from the server's current hash. Add an App Router development test that interrupts HMR traffic, misses an update, restores the connection, and verifies the page reloads into the current revision. ### Why? A browser that temporarily loses its HMR connection can miss updates and remain out of sync after reconnecting. ### How? Attach the current HMR hash to Turbopack connection and update messages. The runtime records the last hash it processed and sends it with each subscription. The development server compares that value against its current hash and requests a full reload on mismatch. <!-- NEXT_JS_LLM --> |
||
|
|
8330e4c4cd |
test: Improve test cache handler implementations (#98098)
- Some of them were not forwarding `getExpiration` or `softTags` to `defaultCacheHandler` - use-cache-cross-deployment was ignoring softTags and expiration. (This patch was written by Sol) --------- Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de> |
||
|
|
d4c838a7d4 |
chore: remove unused code from image optimizer (#98060)
This PR removes some unused code from the image optimizer. - Remove redundant MIME validation - Remove unreachable fallback error These used to be necessary until PR https://github.com/vercel/next.js/pull/82118 removed the fallback. Before 82118: ```js upstreamType = detectContentType(upstreamBuffer) || imageUpstream.contentType?.toLowerCase().trim() ``` After 82118: ```js upstreamType = detectContentType(upstreamBuffer) ``` Now there is never a case where upstreamType is invalid since its already been validated, there is no more fallback. |
||
|
|
a37a86acf0 |
tests: Improve use-cache-cross-deployment tests (#98049)
Followup to #95233 - unskip one client component test since that is actually expected behavior already, we just need to test the observable behavior instead - Fix instant insights warnings - Don't use console.log since that errors when trying to print server references |
||
|
|
a41e98e725 |
Fix optimistic routing for encoded dynamic params (#97948)
## Summary Fixes #97842 by canonicalizing encoded pathname parts before using them as dynamic segment cache keys in optimistic route predictions. This keeps predicted route trees consistent with server Flight data and prevents query-only navigation from remounting pages when regular or catch-all params contain encoded characters. Add coverage that verifies bfcache IDs and form state are preserved for `@` in a regular dynamic param and for `@` plus `%2F` in a catch-all param. ## Verification - Existing encoded-slash route-cache E2E suite in Turbopack and webpack production modes - Existing optimistic-routing E2E suite in Turbopack and webpack production modes - `pnpm test-types` - `pnpm types` - `pnpm prettier-check` - `pnpm lint-ast-grep` - `pnpm lint-language` - Changed-file Prettier and ESLint checks via the pre-commit hook Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> |
||
|
|
4d36801fdb |
Fix intercepted route params after Proxy rewrites (#97953)
## What? Preserve the final internal rewrite pathname in the RSC response when an earlier Proxy rewrite has already populated the rewritten-path header. This also adds an end-to-end regression assertion for a dynamic interception route behind a locale-injecting middleware rewrite. ## Why? The route resolver set the final marker-bearing pathname directly on the response, but router-server later reapplied the accumulated Proxy response headers. That restored the earlier markerless pathname. The client then parsed an intercepted dynamic param against that stale path and removed three real characters from the value. ## How? Mirror the final internal rewrite pathname into the accumulated response-header map. When router-server applies that map after route resolution, the last rewrite remains authoritative. Fixes #97939 ## Tests - Production interception regression suite: 4/4 passed - Development interception regression suite: 4/4 passed - Related production rewrite/interception suites: 43/43 passed - Next package build, targeted ESLint, Prettier, and git diff checks passed |
||
|
|
d8f95c12de |
[PPF] Fix navigation() in prospective runtime prerenders (#98000)
### Long story short Fixes an analogous bug to #95665, but affecting `prefetch()/navigation()`. Those two were accidentally always resolving in the **prospective** runtime prerender, so we were warming content that isn't needed for the final runtime prerender. We now guard them properly. Also, I replaced `PrerenderStoreModernRuntime.isSessionShell` with `finalStage` (the same stage we're gonna use for the final prerender), so we can include/exclude `navigation` properly -- the boolean only distinguished shell/prefetch, not navigation. ### Long story long We use runtime prerenders for three things: runtime shells, runtime prefetches, and embedding a prefetch in navigations. These are aborted after `ShellRuntime`, `Runtime`, and `NavigationRuntime` respectively (accessible in `stageController.finalStage`). If we're doing the **final** prerender for a shell, promises that would resolve in e.g. `NavigationRuntime` would never resolve, because we abort the prerender before we get there. However, the **prospective** runtime prerender has no `StagedRenderingController` and needs to model this separately (so that the same APIs hang in both). This was [previously done via `prerenderStore.isSessionShell`](https://github.com/vercel/next.js/pull/95665) which we checked in URL data apis i.e. `params/searchParams` and the metadata`pathname` to do this. `isSessionShell: true` meant they should hang because it's a shell, and `isSessionShell: false` meant they should resolve. However, I forgot about this case when implementing `navigation`/`prefetch`, and made them resolve unconditionally in the prospective prerender. This means we'd potentially warm content that would not actually be reached in the final prerender. In other words, ```ts await navigation() return <Expensive /> ``` would needlessly render `<Expensive />` during the prospective render but not in the final one. This kinda defeats `navigation`'s main purpose, i.e. avoiding expensive work. This PR fixes the bug by replacing `isSessionShell: boolean` with `finalStage: AdvanceableRenderStage`, which is set to the same value we put in `stageController.finalStage`. This lets us keep the logic of what hangs and what resolves relatively close in both prerenders. --- On a meta level, the fact that the same bug happened twice tells me that having a `StageRenderingController` that is sometimes `null` is error-prone, and we should figure out a better solution, but i'm gonna keep this fix targeted and figure out a more holistic solution in a follow-up |
||
|
|
357e514cf7 |
Revert "test: re-enable sync IO root coverage" (#98050)
Reverts vercel/next.js#97996 The fix does not seem to help with the flakiness. |
||
|
|
e3790aa911 |
More granular cache keys for use-cache entries (#95233)
Caveats: - I had to skip `react[-dom][/*]` and `private-next-rsc-server-reference` and `private-next-rsc-cache-wrapper` imports in the code hash and env-var tracking. Because those all end up pulling in app-page-turbo.runtime.prod.js which reads many env vars and would cause constant deopting. But this should still be correct. The code of these imports is included via the Next.js version, and no env vars should change the semantics of any of those imports. - Static env var reads are collected, but too dynamic accesses are silently ignored and don't lead to deopts (same goes for env var reads in native NAPI addons). This means that this cache reuse is not guaranteed to be 100% guaranteed to never lead to stale caches. - Code hashing and env var collection happens on a per-module basis. So if you put all use-cache functions into a single file and/or together with react components, then you will see extraneous invalidations. This will be fixed by either generally enabling module splitting for all of Turbopack, or by adding a special transform that does it for use-cache functions. Followups: - There are some env var static analysis gaps that will be immediate followups before broader testing. These are the various TODOs added in https://github.com/vercel/next.js/pull/95310 - Client components invalidation is very coarse grained right now (statically imports any client component anywhere -> deopt completely). Followup for the future. But the current setup is correct. This would just improve effectiveness further - Do this in dev as well. Currently there is no NFT at all in dev (for performance reasons) - Module splitting for more granular tracking - Include entropy when serializing server reference arguments for cache key Todo: - [x] Use implementation code hash (includes inlined env vars): from #94234 - [x] Include non-inlined runtime env vars: from #95310 - [x] Include client reference manifest (very coarse for now) - [x] Include Next.js version (for wire format, etc) - [x] This is now done for all use-cache entries now. Not just for `use cache: remote`. Is that the intended behavior? Yes - [ ] ~~if `NEXT_DEPLOYMENT_ID` is in the env vars. just deopt and don't care about stringifing and hashing the env vars~~ - [x] Is cache key size a problem? Currently you can get this: (values are always hashed) `CustomCacheHandler::get ["80e6f6560092f0078775e7e787c1c10ecf6dea0bc4",[],["d984fbaa996274737f3b59345a300a20","16.4.0-canary.5","__NEXT_NO_MIDDLEWARE_URL_NORMALIZE=undefined","NEXT_OTEL_PERFORMANCE_PREFIX=undefined","__NEXT_PRIVATE_ORIGIN=a04f4b9d6a8f42724740a480e9a2bc67c053dc04377be831c0e2c407a1422004","NEXT_PRIVATE_RESPONSE_CACHE_TTL=undefined","NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE=undefined","__NEXT_CACHE_COMPONENTS=b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b","__NEXT_ROUTER_BASEPATH=undefined","__NEXT_MANUAL_CLIENT_BASE_PATH=undefined","__NEXT_INSTRUMENTATION_CLIENT_ROUTER_TRANSITION_EVENTS=undefined","__NEXT_APP_NAV_FAIL_HANDLING=undefined","__NEXT_GESTURE_TRANSITION=undefined","__NEXT_USE_OFFLINE=undefined","NEXT_DEBUG_BUILD=undefined","__NEXT_VERBOSE_LOGGING=undefined...]] [["_N_T_/layout","_N_T_/page","_N_T_/","_N_T_/index"]]` |
||
|
|
5e390edb7f |
test: re-enable sync IO root coverage (#97996)
## Summary Re-enable the `sync-io-blocks-root` production suite now that #97900 waits for child stdio to close before capturing build CLI output. This reverts the temporary skip from #97986 so CI flake detection exercises the route-specific sync I/O diagnostics again. ## Verification - `HEADLESS=true IS_WEBPACK_TEST=1 pnpm test-start-webpack test/production/app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts` <!-- NEXT_JS_LLM --> |
||
|
|
1cb20a1d14 |
[image-optimizer] Refactor into lightweight transform module (#97988)
This PR is strictly a refactor, no logic should change. The idea is that we eventually want to run the image optimizer transform step in a child process. This PR refactors the code to make a new `transform.ts` that is lightweight and should reduce some of the overhead of loading a large module graph when spawning a new process. |
||
|
|
17a901a74f |
Guard filesystem reads against unresolved symlinks (#97902)
### What? Adds debug-only OS realpath validation to successful `DiskFileSystem` file and directory reads. When a successfully canonicalized path differs from the supplied path, the read returns a normal task error naming both paths. Fixes pattern/glob traversal and NFT tracing so physical filesystem access uses resolved paths while logical paths remain available for user-visible specifiers and complete symlink-chain recovery. ### Why? Reading through an unresolved symlink parent gives the same filesystem object multiple path identities. That can make Turbo Tasks dependency tracking and invalidation inconsistent and can produce invalid deployment ZIPs when NFT output contains files below unresolved links. The checks return errors rather than asserting because paths can disagree temporarily under eventual consistency. Propagating a task error avoids panicking a worker thread while still exposing invalid callers during development. ### How? The validation lives directly in `DiskFileSystem::read` and `DiskFileSystem::raw_read_dir`. It calls the OS canonicalization API inline instead of the Turbo Tasks realpath task, keeping the diagnostic out of the task dependency graph. The guard runs only after the OS read succeeds, so missing/non-directory probes preserve their existing behavior. `read_matches` resolves each physical directory immediately before enumeration while retaining logical `PatternMatch` paths. `read_glob` and `track_glob` now resolve their initial directory before enumeration. Symlinks discovered later through wildcard segments are also traversed through resolved targets. `ReadGlobResult` deliberately retains logical paths rooted at the supplied base, allowing consumers to call `realpath_with_links` and recover the complete symlink chain. Consumers follow that contract explicitly: - NFT includes expand each logical match with `realpath_with_links`, emit resolved files and every traversed symlink, skip resolved directory targets, and deterministically deduplicate/sort output. - `import.meta.glob` uses recursive logical keys as the source of user-visible requests, while module resolution follows and tracks symlinks. - the hash-glob example resolves returned logical paths before reading. Webpack-loader context dependencies are covered for both `path/to/symlink/inner/path/*` and `path/to/*/inner/path/*`. The loader fixture performs its directory read with Node `fs`, reports the directory using `addContextDependency`, and Turbopack tracks the resolved target. ### Verification - `cargo fmt -p turbo-tasks-fs -p turbopack-ecmascript -p next-api -- --check` - `cargo clippy -p turbo-tasks-fs -p turbopack-ecmascript -p next-api --all-targets` - `cargo test -p turbo-tasks-fs` (128 passed) - `cargo test -p next-api` (7 passed) - `cargo check -p turbo-tasks-fs --examples` - `import.meta.glob` symlink execution fixture (1 passed) - Nine targeted node-file-trace CI cases with `release-with-assertions` (9 passed) - `pnpm build-all` - `webpack-loader-fs` Turbopack dev e2e (1 passed) - `build-trace-extra-entries-turbo` Turbopack production e2e (1 passed) - twoslash Turbopack production, normal mode (4 passed) - twoslash Turbopack production, cache-components mode (4 passed) - `bench/heavy-npm-deps` Turbopack development smoke test (HTTP 200) ### Notes The disk guard is cross-platform, while its symlink-parent regression test is Unix-only, matching neighbouring symlink tests. On Windows, OS canonicalization can also normalize casing and 8.3 short names; a debug read using a non-canonical spelling will therefore return the same diagnostic error. <!-- NEXT_JS_LLM --> <!-- fleet 1d32e12c-f4ec-4f22-862a-c85f0005805c --> --------- 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> |
||
|
|
12bf495e0e |
Expand Turbopack dev cleanup (#97833)
## Summary Builds on #97591. Webpack currently removes most of `.next` on devserver startup. We can remove more as well. This keeps the age-based removal introduced in #97591 and expands it to `traces`, logs, etc. However, for any other directory that is not `cache` or `lock`, completely remove it on startup. This includes types, etc. Apply age-based deletion to Turbopack-owned output that can be restored from cache or recomputed, including route entries, chunks, assets, service workers, middleware, and instrumentation. Completely remove other dev output so logs, traces, generated types, diagnostics, and future framework-owned output cannot be left as mixed generations. Use exact-path exclusion sets in `recursiveDeleteSyncWithAsyncRetries`. The complete pass retains `cache`, `lock`, and Turbopack-owned paths; the age-based pass then sweeps those Turbopack paths. Both passes finish before project creation. ## Verification - `pnpm test-unit test/unit/recursive-delete.test.ts` - `pnpm --filter=next types` --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
b882af0899 |
Add Cache Components option to create-next-app (#97695)
When initializing a new project with create-next-app, the interactive prompt will now ask the user if they want to enable Cache Components. For now the option defaults to off. We'll flip the default back on once there's a flow that guides users to the right recommendation based on the kind of site they're building. The option can still be enabled interactively, or with the `--cache-components` flag. If the user chooses to enable Cache Components, the project will also be initialized with Partial Prefetching enabled. There's no separate prompt for Partial Prefetching; the only reason they are separate configs is because Cache Components shipped in 16.0 before Partial Prefetching existed. This is not a breaking change because it does not change any behavior in Next.js itself, only the create-next-app CLI. It has no impact on any existing Next.js projects. |
||
|
|
1f52cb42ad |
Turbopack: shorten CSS module class names (#97944)
The class names were unnecessarily long. Use the same as the lightningcss default: `[hash]_[local]` which is `<hash of the full file path>_<original class name or identifier>` Keep the previous longer mechanism to aid in debugging in dev Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> |
||
|
|
6d6228c0c6 |
Prune incomplete parallel route matchers (#97108)
## Summary
This adds an experimental `strictRouteMatching` flag that leaves a
matcher out when its finalized loader tree contains a synthesized
default that will always call `notFound()` for a slot actually declared
by the owning layout. Adding a `default.tsx` keeps the matcher, and
structural router-state branches that are not real slots of that layout
do not make it incomplete. The flag defaults to `false` so matcher
pruning can roll out independently from the preceding loader-tree
correction.
The main goal is to make `children` and named slots behave the same way.
If a URL cannot construct a complete route tree on its own, it should be
treated as an unmatched URL instead of sometimes showing a slot 404 and
sometimes producing a missing page or default error.
## Semantics
For example:
```text
app/split-matcher/
├── layout.tsx
├── foo/page.tsx
├── bar/page.tsx
└── @slot/[...parts]/page.tsx
```
With strict route matching disabled, Next.js emits matchers for
`/split-matcher/foo`, `/split-matcher/bar`, and the broad
`/split-matcher/[...parts]`. The broad matcher can match any URL, but
its `children` branch has neither a matching page nor a default, so it
can only construct a permanent 404 tree. With strict route matching
enabled, that broad matcher is omitted while `/foo` and `/bar` remain
because each combines a real `children` page with the named-slot
catchall. Adding `app/split-matcher/default.tsx` deliberately makes the
broad matcher complete and keeps it.
The same rule applies without a catchall:
```text
app/disagreeing-slots/
├── layout.tsx
├── @first/foo/page.tsx
└── @second/bar/page.tsx
```
`/foo` is incomplete because `@second` has no matching page or default,
and `/bar` is incomplete for the corresponding reason in `@first`, so
neither matcher is emitted. A later PR in this stack reports the
now-unreachable page files as a project misconfiguration.
A declared `children` route is treated like any named slot:
```text
app/declared-children/
├── layout.tsx
├── page.tsx
└── @panel/
├── default.tsx
└── details/page.tsx
```
`/declared-children` is complete because `children` uses `page.tsx` and
`@panel` uses its default. `/declared-children/details` is incomplete
because `@panel` matches its page while the declared `children` slot has
no matching page or default at `/details`, so that matcher is pruned.
A route composed entirely from declared named slots is also complete:
```text
app/named-only/
├── layout.tsx
├── @left/[...slug]/page.tsx
└── @right/[...slug]/page.tsx
```
The preceding PR's default-on `explicitParallelRouteChildren` behavior
means this loader tree contains only `left` and `right`. Strict matching
keeps `/named-only/[...slug]` because both declared slots match; it does
not invent `children` and then prune the route for failing to satisfy
that nonexistent slot.
We got here incrementally.
[#47872](https://github.com/vercel/next.js/pull/47872) introduced the
404 fallback for unmatched parallel slots,
[#60186](https://github.com/vercel/next.js/pull/60186) added a
development warning because this was confusing in practice and linked
[#51805](https://github.com/vercel/next.js/issues/51805) and
[#49569](https://github.com/vercel/next.js/issues/49569), and
[#84702](https://github.com/vercel/next.js/pull/84702) later made a
missing default a build error for named slots while leaving `children`
on the old fallback for backwards compatibility. Strict matching takes
the next step and treats a matcher that can only construct a permanent
404 tree as unmatched.
This changes soft navigations that only worked by preserving a
previously active slot even though the URL could not be loaded directly,
which is why matcher pruning remains behind its own experimental flag.
The interception retention markers backed by `default-null`, including
named host slots from the first PR in this stack, are complete route
patches and are not pruned. This change however is well motivated
because if you did client nav to a route that only matches a named slot
and then hard refresh you will end up getting a 404. This is a sign our
current semantics are actually broken.
If you want to preserve the perma 404 behavior of the
slot-without-default you can just add a default and make it call
`notFound()` unconditionally.
## Verification
- `pnpm build-all`
- Focused `normalize-catchall-routes` unit coverage
- Production and development e2e coverage on Turbopack and Webpack
- The pruning e2e matrix with Cache Components enabled
<!-- NEXT_JS_LLM -->
|
||
|
|
05ed7c1be2 |
Omit undeclared children slots from app routes (#97184)
## Summary Parallel route layouts can be composed entirely from named slots, but loader tree construction currently synthesizes a `children` fallback whenever any named slot exists. This makes `children` semantically required even when no page, default, or ordinary route branch declares it. This adds `experimental.explicitParallelRouteChildren` and enables it by default. When enabled, `children` is included in a layout slot set only when the filesystem declares an ordinary route that can render at that level. A layout by itself is only structure and does not declare a route target. Ordinary descendants are traced until they reach a page or default, including through deeper named slots, before they cause `children` to be included. Setting the flag to `false` temporarily restores the legacy implicit `children` fallback. This flag only controls whether `children` exists in the loader tree. It does not prune incomplete matchers; that is the separate `experimental.strictRouteMatching` behavior in the next PR. Named slots keep their existing default and soft navigation semantics. The preceding PR retains the slots owned by an interception host without treating `children` specially, so an undeclared child is no longer needed for that behavior. A real `children` branch still uses the retention marker when it is one of the host layout slots. ## Semantics For example, this layout declares only named slots: ```text app/dashboard/layout.tsx app/dashboard/@left/page.tsx app/dashboard/@right/page.tsx ``` With `explicitParallelRouteChildren` disabled, Next.js adds a synthetic `children` branch whose built-in default calls `notFound()`, even though the layout never declared or rendered it. With the default behavior enabled, the loader tree contains only `left` and `right`, so `/dashboard` is matched from the route targets that actually exist. This is different from an ordinary branch whose route targets are deeper in the tree: ```text app/nested/layout.tsx app/nested/@sidebar/[...slug]/page.tsx app/nested/content/layout.tsx app/nested/content/@left/[...slug]/page.tsx app/nested/content/@right/[...slug]/page.tsx ``` Here `content` really is the `children` branch of `nested`. The scan follows `content` through its layout and deeper named slots, so `children` remains required. `/nested/content/anything` can construct every declared slot, while `/nested/incomplete` only matches `sidebar` and is still incomplete. The distinction is whether the ordinary descendant eventually reaches a page or default, not whether a layout happens to exist along the way. The focused children detection coverage proves that a layout-only descendant does not synthesize `children`, while an ordinary branch whose route targets live inside deeper named slots still does. The limitation coverage also proves that named-only trees render pages, CSS, metadata, and regular error boundaries. It intentionally asserts the current broken behavior for HTTP access fallbacks and metadata or viewport failures so those expectations can be flipped when renderer ownership no longer depends on `children`. ## Verification - `pnpm build-all` - Turbopack and webpack development and production coverage for `explicit-parallel-route-children-detection` - Turbopack and webpack production coverage for `interception-dynamic-segment`, `parallel-routes-layouts`, and `explicit-parallel-route-children-legacy` - The same existing production coverage with Cache Components enabled - Turbopack and webpack production coverage for the documented named-only limitations <!-- NEXT_JS_LLM --> |
||
|
|
04e96a5bfa |
test: add test for local font with deployment id (#97987)
Follow up to a previous PR: - https://github.com/vercel/next.js/pull/82488 - https://github.com/vercel/next.js/pull/82384 Closes: - https://github.com/vercel/next.js/pull/82547 |
||
|
|
cce7bf2fc0 |
Retain interception route host slots (#97242)
## Summary
Interception routes represent a partial update to the layout that hosts
them. Today we model this for `children` by synthesizing a `__DEFAULT__`
route backed by `default-null`, but named siblings still use normal
default matching even though they should retain their active state too.
This makes retention relative to the interception host instead of the
`children` key. Every non-intercepting sibling gets the existing
`__DEFAULT__` marker backed by `default-null`, while slots inside the
newly selected interception subtree continue to use normal matching and
real defaults.
The coverage uses a named-only host to verify that named siblings retain
client state without evaluating a user default, that a sibling without a
default also retains, and that a hard refresh still loads the canonical
route.
## Example
Consider this route tree:
```text
app/
├── named-host/
│ ├── layout.tsx # renders canonical, content, secondary, and modal
│ ├── @canonical/
│ │ └── page.tsx
│ ├── @content/
│ │ ├── page.tsx # renders a stateful counter
│ │ └── default.tsx # throws if evaluated
│ ├── @secondary/
│ │ └── page.tsx # has no default.tsx
│ └── @modal/
│ ├── default.tsx
│ └── (..)named-target/
│ └── page.tsx # intercepted target
└── named-target/
└── page.tsx # canonical route
```
Before this change, a soft navigation from `/named-host` to
`/named-target` could replace `@content` with its throwing default and
treat `@secondary` as missing because only `children` had retain
semantics. After this change, only `@modal` switches to the intercepted
page; `@content` and `@secondary` keep their existing UI and the counter
keeps its state. A hard refresh still renders the canonical
`/named-target` route instead of the interception host.
## Verification
- Turbopack and webpack production coverage in
`interception-dynamic-segment`
- The same production coverage with Cache Components enabled
<!-- NEXT_JS_LLM -->
|
||
|
|
0a2bd1d531 |
test: disable flaky sync-io-blocks-root.test.ts (#97986)
Something made this test very flaky on Aug 24/Aug 25 https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.status%3A%22fail%22%20%40test.suite%3A%22sync%20IO%20that%20blocks%20the%20root%22%20-%40test.type%3Aturbopack&agg_m=count&agg_m_source=base&agg_t=count&fromUser=false&index=citest&start=1787237730805&end=1787842530805&paused=true Disabling until we can investigate. |
||
|
|
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> |
||
|
|
89d017eac4 |
Pages Router: Deprecate React 18 support (#97689)
## Summary - warn during `next dev` and `next build` when React 18 is installed, while keeping `next start` quiet - explain that React 18 remains supported in Next.js 16 but will be removed in Next.js 17 - update the React version guidance with React 19 and TypeScript type upgrade commands ## Verification - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/e2e/react-current-version/react-current-version.test.ts` - `NEXT_TEST_REACT_VERSION=18.3.1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/e2e/react-current-version/react-current-version.test.ts` - targeted production build/start coverage passed with React 18 and React 19 - Not passing: `pnpm --filter=next types` (existing unrelated errors in `httpget.ts`, `image-optimizer.ts`, `proxy-request.ts`, telemetry typings, and compiled declarations) - Not passing: `pnpm lint` (existing unused Turbo Tasks check) <!-- NEXT_JS_LLM --> |
||
|
|
529d290b8f |
[test] Unpin Vercel CLI version (#97974)
Reverts #97960 vercel@59.7.0 should fix this again |
||
|
|
80f846c74e |
Support immutable static assets with output: 'export' (#97711)
There was no real reason why this wasn't done thus far. `output: 'export'` already uses the adapter anyway. Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> |
||
|
|
d75bf1b85a |
Fix build error when aliasing typescript to @typescript/typescript6 (#97942)
This was previously broken, because the binary isn't called `tsc` but
`tsc6`:
```json
"dependencies": {
"typescript": "npm:@typescript/typescript6@6.0.1"
}
```
---------
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
|
||
|
|
a65ccea59c |
Replace resumed render bailout error with ReactDOM.browser behind a flag (#96844)
Stacked on #96843. This PR replaces the bailout error used to abort resumed renders with `ReactDOM.browser()` behind the experimental flag. ### How? Pass the flag to the App Router runtime, use `ReactDOM.browser()` as the abort reason when enabled, and preserve existing behavior otherwise. <!-- NEXT_JS_LLM --> |
||
|
|
c11c48e42d |
Replace useSearchParams bailout error with ReactDOM.browser behind a flag (#96843)
Stacked on #96826. This PR replaces the bailout error used by `useSearchParams` with `ReactDOM.browser()` behind the experimental flag. ### How? Move the bailout behind a browser-safe module boundary, use `ReactDOM.browser()` when enabled, and preserve existing behavior otherwise. <!-- NEXT_JS_LLM --> |
||
|
|
23985382a7 |
Replace CSRBailout error with ReactDOM.browser behind a flag (#96826)
CSRBailout error is the use case for `ReactDOM.browser()`. Replace behind an experimental flag. This PR replaces the bailout error used in next/dynamic ssr: false. |