mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
v16.4.0-canary.21
3960 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`. |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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.
|
||
|
|
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> |
||
|
|
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 |
||
|
|
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"]]` |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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 -->
|
||
|
|
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 --> |
||
|
|
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. |
||
|
|
fad317505d |
fix(next/image): reject non-2xx internal image responses (#97957)
- Fixes https://github.com/vercel/next.js/issues/82357 - Closes https://github.com/vercel/next.js/pull/96985 ### What? `fetchInternalImage()` only rejects the internal response when `statusCode` is falsy, and `MockedResponse` defaults it to `200`, so nothing really gets rejected there. A `307` coming from a `redirects()` entry in `next.config`, or a `404` for a path that isn't there, both count as a successful image fetch. That body then reaches `detectContentType()`, which returns `null`, and all you get is: ``` The requested resource isn't a valid image for /path/to/image.png received null ``` Nothing in that line points back at the response that actually came in, so a redirect looks exactly like a corrupt image. ### Why? `fetchExternalImage()` right above already covers this with `if (!res.ok)` and logs `res.status`. The internal path is the only fetch in the file without that check, so the same failure gets reported at the fetch for remote images, and three steps later as "not a valid image" for local ones. ### How? The guard now rejects anything outside the 2xx range, which is what `res.ok` means, and the log line carries the status next to the href. The thrown `ImageError` message stays generic, same as the external path, so nothing new shows up in the HTTP response body. Because that throw passes `mocked.res.statusCode` into `ImageError`, a non-2xx internal response now reports its own status instead of always landing on `400`. `ImageError` still maps anything under 400 to `500`, so a redirect answers `500` and a `404` stays `404`. That is what `fetchExternalImage()` has always done, and it lines the two up: a missing local image now returns `404` just like a missing remote one. Three assertions in `test/e2e/image-optimizer/util.ts` shift because of that: - `should not forward cookie header`: `/api/conditional-cookie` answers `401` when the cookie is missing, so `400` becomes `401` - `should error if the image file does not exist`: `/does_not_exist.jpg` returns `404` now, with the internal response message - `should error if the resource isn't a valid image`: this one requested `/test.txt`, but the fixture in `public/` is `text.txt`, so it was quietly exercising a missing file instead of a non-image file. Pointed at the file that exists, it keeps its original `400` and finally tests what its name says Two unit tests cover the new guard, one `307` and one `404`. Every other test in that file sets `statusCode = 200`, so they are untouched. #96985 is open against the same report with a different angle: it threads the status through `ImageUpstream` so the existing "isn't a valid image" log can print it. This one stops the request at the fetch, so the redirect never reaches the optimizer at all. Whichever one fits better, feel free to close mine. ### Verification - `jest test/unit/image-optimizer/`: 139/139, and the two new tests fail against a build without the guard - `pnpm test-dev test/e2e/image-optimizer/content-disposition-type.test.ts`: 97/97 - `pnpm test-start test/e2e/image-optimizer/content-disposition-type.test.ts`: 97/97 - `prettier --check` clean on the three changed files <!-- NEXT_JS_LLM --> Co-authored-by: Steven <steven@ceriously.com> |
||
|
|
1a7ccf481d |
Re-enable AVIF image optimization (#97931)
Reverts #97875 and bumps `sharp` from `^0.35.3` to `^0.35.4`, re-enabling AVIF image optimization. `minimumReleaseAge` interplay (verified with pnpm 10.33.0 and 11.22.0 in a standalone repo with a `file:` dependency declaring sharp as an optional dependency): - sharp 0.35.4 was published on 2026-08-26 and is younger than this repo's 48h `minimumReleaseAge`. - pnpm aborts the whole install with `ERR_PNPM_NO_MATURE_MATCHING_VERSION` when no in-range release satisfies the age gate. This applies to `optionalDependencies` (no warn-and-skip) and through `file:` dependencies alike. - The gate also covers the `@img/sharp-*` binary packages sharp 0.35.4 depends on, so excluding only `sharp@0.35.4` is not enough. - `pnpm-workspace.yaml` therefore exempts `sharp@0.35.4` and `@img/sharp-*` (pnpm does not allow version qualifiers on name patterns in this setting). Both entries can be dropped once 0.35.4 is older than 48 hours. - Downstream users who configure their own `minimumReleaseAge` will hit the same hard install failure when installing a release with this bump until 0.35.4 ages out, unless they add the same excludes. Keeping `^0.35.3` is not a safer alternative: age-gated installs would then silently resolve the vulnerable 0.35.3. Part of https://linear.app/vercel/issue/VOC-34654/ --------- Co-authored-by: Steven <steven@ceriously.com> |
||
|
|
0c7f81e6a3 |
Don't report a client-aborted RSC stream as a render error (#96715)
When a client disconnects mid-stream, `pipeNodeReadableToNodeResponse`
destroys the readable React is piping into. React sees its destination
close before the render ended and aborts with a generic `Error("The
destination stream closed early.")`, which `onError` has no way to
distinguish from a real render error, so it ends up in `onRequestError`
as a server render error.
The Web streams path doesn't have this problem: cancelling the readable
passes the `ResponseAborted` reason straight through to React. This does
the same for the Node path by aborting the pipeable with that reason as
soon as the passthrough is destroyed early, before React's own close
handler runs. Genuine render errors still go through untouched.
Fixes #96704
---------
Co-authored-by: Jude Gao <jude.gao@vercel.com>
|
||
|
|
d352f1740a |
[PPF] Only track runtime accesses when the promise is used (#97165)
Improves how we track accesses of runtime data in static prerenders (which affects whether a page will use static or runtime shells). Now, Instead of tracking it when the hanging promise is created, we'll track it when `then/catch/finally` is called on the promise (which we use as a signal for being used). The promise tracking required some novel work, because: - Using a proxy seems to cause `Promise.then` to show up in call stacks (we've had issues with proxies throwing off React's IO tracking before, i believe this is a similar problem) - patching `then/catch/finally` on the promise directly doesn't work, because `then` doesn't get called for native `await` The only way of doing this that i've found is a custom `Promise` subclass that tracks the access in its `then` implementation. This is what `TrackedPromise` does. To improve the debuggability, i've added some debug logs enabled by `NEXT_PRIVATE_DEBUG_RUNTIME_DATA=1` to detect when a page is deopted to runtime and what expression caused it. --- This fixes a false deopt caused by `createServerPathnameForMetadata` - when called on a route with fallback params, it would always track a fallback params access even if no icons/og images that actually use it exist. This meant that we'd never statically optimize any routes that had fallback params. After this PR, we only track a runtime data access if a metadata route that uses that params actually exists, in which case the `pathname` promise gets awaited. (as a future optimization, we should do the equivalent of vary params tracking on metadata routes to know if the params are used at all. also, the logic in `pathname.ts` is generally in need of cleanup) |
||
|
|
94327e8ade |
Port React's @gate test directive to the e2e harness (#96228)
The test suite has accumulated a bunch of patterns for disabling tests
that are known to fail under some configuration: `it.skip`, `if
(isNextDev) { test('skipped in dev mode', () => {}); return }`, whole
describes toggled off by checking `process.env.__NEXT_CACHE_COMPONENTS`.
These all have the same flaw: nothing tells you when the thing you
skipped starts working. The test stays disabled forever, and the
workaround it was guarding rots along with it.
React solves this with the `@gate` pragma, and this PR ports it to the
Next.js e2e harness:
```ts
// Blocked on the optimization that marks a route as fully static when
// no dynamic params are referenced in Server Components.
// @gate !cacheComponents
it('navigates to a page with a lazily-generated static param', async () => {
// body unchanged
})
```
The test still runs. If the condition is false and the test fails, the
failure is absorbed and the suite stays green. If it _passes_, the suite
fails: the gate is stale, delete it. So instead of a skip that hides a
fixed bug indefinitely, you get a CI failure the day the fix lands.
When the condition is static, the inversion is Jest's own `test.failing`
under the hood. A lazy condition isn't known until the fixture's
resolved config is read inside the body, so those tests invert at
runtime instead.
`// @force-gate <condition>` skips for real — for tests that can't even
be attempted (prefetching is disabled in dev, deploy has no local build
output, the fixture won't build under the condition), and for tests of a
new API, where the disabled state can only throw and running it proves
nothing:
```ts
// Prefetching is disabled in dev, so this suite has nothing to test.
// @force-gate prefetching
describe('segment cache prefetch scheduling', () => {
// ...
})
```
There's no staleness check in that case, so this is a judgment call:
prefer `@gate` when the off state fails for a meaningful reason — the
flag changes behavior that already exists — and `@force-gate` when the
body can only throw because the API doesn't exist. A static condition
(mode, bundler) resolves at collection time into a normal Jest skip. A
lazy condition resolves at runtime, and when a lazy force-gate on a
describe is false, we skip the fixture build entirely — that's what
makes it usable for suites whose fixtures are build-incompatible with
the condition. (One caveat: Jest has no way to skip a test that's
already running, so these report as passing with a warning in the log,
not as skipped.)
Conditions live in a hand-written registry. I considered deriving the
lazy ones from the config schema automatically, but a gate is a claim
about which dimension of the test matrix explains a failure, and I'd
rather each of those claims be spelled out with a description.
Referencing an undeclared name fails the suite at collection time, so a
typo can't silently disable a gate.
The important design decision for lazy conditions is that they read the
fixture's _resolved_ config, never `process.env`. The env var isn't the
truth: `__NEXT_CACHE_COMPONENTS=true` only applies when the fixture
doesn't set `cacheComponents` itself, and config resolution implies
flags the fixture never mentions (`cacheComponents: true` alone turns on
`experimental.ppr`). Resolution happens in a child process, because
in-process `loadConfig` would leak the fixture's `.env` files into the
Jest worker. Suites with no lazy gate never pay for any of this.
The condition expression is parsed using a small grammar (also ported
from the React repo). An expression that doesn't parse fails the suite:
```ts
// @gate mode === 'start' && !cacheComponents
// @gate !(turbopack || rspack)
```
There's also a runtime version, mirroring React's `gate(flags =>
flags.enableFoo)`, for tests that run under both states but assert
differently (and for `it.each`, where the pragma can't attach):
```ts
import { gate } from 'next-test-utils'
it('renders the fallback', async () => {
if (await gate((conditions) => conditions.cacheComponents)) {
// PPR: the fallback is part of the static shell
} else {
// fully dynamic: the fallback streams in
}
})
```
It also accepts the pragma expression language as a string: `await
gate('cacheComponents && !dev')`.
Docs are in `test/lib/gate/README.md`; `test/unit/gate/` covers the
transform, the expression language, and the runtime.
|
||
|
|
43d81b9d55 |
feat(turbopack): resolve /-rooted imports from the project directory (#97799)
### What?
A request starting with `/` — `import '/content/where'`,
`require('/foo.js')` — now resolves from
the **project directory** (the one holding `next.config`), and cannot
reach outside it. Previously
it resolved from the root of the filesystem while reporting *"server
relative imports are not
implemented yet"*.
This is the base of a stack: the follow-up on top of it moves
`import.meta.glob`'s `/`-rooted
patterns onto the same root, so a pattern and a plain import agree on
what `/` means.
### Why?
The feature was half-present and mislabelled. The
`Request::ServerRelative` arm rewrote `/foo` to
`./foo` and resolved it against the filesystem root, then emitted a "not
implemented yet" issue
unless an import-map alias had already matched. Users got a diagnostic
telling them to give up on
something that in fact resolved.
Worse, resolving from the *filesystem* root means `/` escapes the
project. In a Turbopack execution
test — where the filesystem root is the repository root —
`require('/package.json')` resolved this
repository's own `package.json`. In a monorepo, where Turbopack's root
is the workspace root and the
app lives in `apps/web`, `/content/x.js` would reach a workspace-level
file rather than the app's.
The intended semantics are Vite's, and they were confirmed by
measurement rather than from memory.
In a workspace where `content/*.js` exists *both* at the workspace root
and under the Vite root, so
the two are distinguishable, Vite 5.4.21:
- resolves `/content/…` from the configured `root` — changing `root`
changes which file wins;
- fails outright for a file that exists only above `root`, rather than
walking up;
- behaves identically for a plain `import` and for `import.meta.glob`.
### How?
`ResolveOptions` gains `server_relative_root`, the directory a
`/`-rooted request resolves from, and
`ResolveOptionsContext` exposes it so embedders can set it. Next.js sets
it to the project directory
in the client, server and edge resolve contexts — all of which already
had it to hand, so nothing new
is threaded through.
Three deliberate choices:
- **The option is optional and defaults to the previous behaviour.**
`turbopack-core` is used well
beyond Next.js; an embedder that doesn't set it keeps resolving `/` from
the filesystem root. This
is also why the change is provably contained — the whole
`turbopack-tests` suite passes with no
snapshot churn.
- **It is named and documented in filesystem terms**, tied to the
existing `Request::ServerRelative`
vocabulary, rather than introducing a "project root" concept into
`turbopack-core`, which must stay
free of Next.js concepts.
- **There is no fallback.** Resolution happens in the configured root
and stops; a request that isn't
there is a normal module-not-found. A fallback to the filesystem root
would reintroduce exactly the
escape this fixes.
The "not implemented yet" issue is removed. The equivalent issue for
Windows-style absolute requests
is untouched, as is the separate tsconfig `extends` handling, which
treats a rooted path as absolute
per TypeScript's own rule.
### Testing
The Turbopack execution harness already has two distinct roots — the
repository root as the
filesystem root, and each test's own directory — so pointing the new
option at the test directory
makes "project directory" and "filesystem root" genuinely different, and
the distinction testable
without a Next.js app. Two fixtures: one where a `/`-rooted import and
require resolve from the
project directory, and one asserting that `/package.json`, which exists
only at the filesystem root,
does *not* resolve. The former emits no issues at all, which is what
pins the removal of the
diagnostic.
The end-to-end case uses the existing monorepo fixture, where the
project directory really is not the
filesystem root. A file of the same name exists in both
`apps/web/content` and the workspace root, so
the rendered value alone identifies which root was used — a test that
merely asserted "it renders"
would have passed under the old behaviour too.
### Known gap
TypeScript resolves a leading `/` as an absolute path on disk, so a
`/`-rooted import in a `.ts` file
still fails type checking (`TS2307`) even though it bundles and runs.
The e2e page is therefore
JavaScript. A tsconfig `paths` mapping works around it for users, but it
also makes the request match
the import map instead, so the two resolvers overlap; that deserves its
own decision and is tracked
separately rather than papered over here.
<!-- NEXT_JS_LLM -->
<!-- fleet 3acc7e63-52a8-4472-89cd-b1bbb8ad59fa -->
---------
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>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
|
||
|
|
09f9c8a758 |
Fix ISR misses with backslashes in segments when deployed on Windows (#97876)
https://github.com/vercel/next-js-mirror/pull/150 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7a5937a8ab |
[next/image]: disable avif image optimization (#97875)
https://github.com/vercel/next-js-mirror/pull/160 |
||
|
|
dae438dc0d |
Stop printing a stack frame for error message text (#97829)
Next.js printed a stack frame that does not exist for an error whose message ends with text that looks like a file location. A logged connection failure read `at <unknown> (Error: connect ECONNREFUSED ::1:45999)` in front of its real frames. `stacktrace-parser` tries five patterns on every line, and the pattern for JavaScriptCore makes the `<name>@` prefix optional. That pattern `:<line>:<column>`, and a line of an error message can end that way. A JavaScriptCore frame always holds `@`, also at the top level, so the prefix is not optional in a real stack. The patch for `stacktrace-parser` now requires that prefix. A line of a message no longer becomes a frame, and every JavaScriptCore frame still parses, including one whose path holds a space. A Turbopack build error puts the file and the position of the error in its message, and the parser read that line as a frame as well. That frame is gone now, and the location stays in the message. The snapshots of the build errors record the change. |
||
|
|
bf07db0713 |
Show the errors of an AggregateError behind a cause (#97830)
A failed `fetch` hid the reason of the failure. Node.js reports every refused connection attempt in the `errors` of an `AggregateError`, and the terminal logged `[errors]: [ [Error], [Error] ]` without the address or the port of a single attempt. An `AggregateError` keeps its errors in an array. Node.js spends one level of the inspect depth on that array, and one more level on each error in the array, while a `cause` costs one level only. The errors of an `AggregateError` behind a cause therefore fell outside the default depth of two. Node.js also returns an error early when it carries no own properties, before it applies the depth, so the collapse hit only the errors that carry diagnostics. Next.js now adds the missing level back for an `AggregateError`, so its errors print at the same nesting as a cause. A depth of `null` means unlimited and stays unchanged, because `null + 1` would reduce it to one level. |
||
|
|
95b77e8279 |
Fix metadata prefetch cache key for search params (#97729)
### What? Treat a `LoadingBoundary` response as `Full` when writing the response's head into the segment cache, matching the strategy already used when the scheduler creates that head entry. Adds a production e2e regression test with two concurrent prefetches for the same pathname and different search params. ### Why? `pingRuntimeHead` creates loading-boundary head entries with the concrete `Full` strategy because a head has no loading boundary. The canonical re-keying added in #96095 later wrote the fulfilled head with raw `LoadingBoundary` semantics, which drops search params from the metadata cache key. Concurrent query-specific metadata responses could therefore replace each other under one fallback key; whichever response settled last supplied the title for both URLs. Fixes #97657 ### How? Normalize `LoadingBoundary` to `Full` at the head write boundary only. Page segments retain loading-boundary keying, and shell/PPR responses keep their existing strategies. The test reveals alpha and beta links in one React render, waits for both prefetches, navigates to alpha, and verifies both body and title. It then performs a search-only navigation to an unprefetched gamma URL and verifies that the head is replaced. ### Testing - Regression boundary: test passes at `e70495c1ee` and fails at #96095 merge commit `dfa7f4f713` without the fix (`alpha` body, `beta` title). - Test passes at `dfa7f4f713` with the fix. - `NEXT_SKIP_ISOLATE=1 pnpm test-start-turbo test/e2e/app-dir/segment-cache/metadata-search-params/metadata-search-params.test.ts` (pass, plus 3 consecutive repeat runs) - `NEXT_SKIP_ISOLATE=1 pnpm test-start-webpack test/e2e/app-dir/segment-cache/metadata-search-params/metadata-search-params.test.ts` (pass) - Relevant existing Turbopack suites: 16 tests pass across metadata, static shell, metadata soft navigation, and stale search-param coverage. - Existing search-param suites: 6 tests pass with webpack. Their Turbopack builds are currently blocked on canary by an unrelated Edge Runtime `process.cwd()` error. - `pnpm --filter next build` - Targeted ESLint and Prettier checks - Structured external review: 0 findings |
||
|
|
6617057757 |
Turbopack: emit/collect (#96631)
Only add this for using it for Server Actions for now
Undocumented, not API stable. But we can't really put it behind a flag,
because the next-custom-transform is going to add it
New traits:
```rust
/// A module that can collect other modules during the collect phase.
#[turbo_tasks::value_trait]
pub trait CollectingModule: Module {
/// The namespace that this module is interesed in
#[turbo_tasks::function]
fn namespace(self: Vc<Self>) -> Vc<RcStr>;
#[turbo_tasks::function]
fn as_chunk_item(
self: Vc<Self>,
module_graph: Vc<ModuleGraph>,
chunking_context: Vc<Box<dyn ChunkingContext>>,
entry_chunk_group: Vc<Modules>,
) -> Vc<Box<dyn ChunkItem>>;
}
#[turbo_tasks::value_trait]
pub trait EmittedModuleReference: ModuleReference + ValueToString {
#[turbo_tasks::function]
fn data(self: Vc<Self>) -> Vc<CompileTimeDefineValue>;
}
```
Example:
```javascript
__turbopack_emit__('./c.js', {
namespace: 'my-test',
data: 'data-for-c', // optional
exports: ['c'], // optional
with: ... // optional, import attributes like with ESM
})
// -------------------------
const getList = __turbopack_collect__({
namespace: 'my-test',
})
const list = getList();
```
Shortcomings in the current version (which are not a blocker for now
however):
- data-only emit not implemented
- stylegroups aren't properly implemented. emitting styles is probably
broken
- pages router is still a problem: it has two separate
ChunkGroup::Entry, so you only see what was emitted on the current
runtime, not both
Recreation of https://github.com/vercel/next.js/pull/91100
|
||
|
|
c0dfeffa53 |
Add next/cache-handlers types entrypoint (#97592)
### What?
Exposes the `cacheHandlers` types (`CacheHandler`, `CacheEntry`) as
types-only exports from `next/cache`, so handlers can be checked against
the real interface:
```ts
import type { CacheHandler } from 'next/cache'
```
### Why?
Custom cache handler authors currently import `CacheHandler` and
`CacheEntry` from `next/dist/server/lib/cache-handlers/types` (an
internal path that can move between versions) or hand-copy the
interfaces, which drift silently across releases.
### Motivation
I'm working on a custom community cache handler and it'd be great to
import the types directly and keep testing against the source of truth,
rather than maintaining a hand-copied mirror that has to be re-checked
on every Next.js release.
Raised in discussion #96356.
### How?
- Adds a type-only `export type { CacheHandler, CacheEntry }` to
`packages/next/cache.d.ts`, re-exported from
`./dist/server/lib/cache-handlers/types`. No new subpath or
`package.json` `"files"` entries needed since `next/cache` already
ships.
- Docs: `cacheHandlers.mdx` now shows the public `next/cache` import
instead of the GitHub source links.
- Tests: a `satisfies CacheHandler` / `satisfies CacheEntry` fixture in
the `typescript-basic` typechecking suite (runs `tsc` against the
installed package), and the `use-cache-custom-handler` e2e fixture's
JSDoc now uses the public import.
(Originally proposed as a separate `next/cache-handlers` types-only
subpath, following the `next/types` pattern moved the export into
`next/cache` per review.)
Related: #96356
closes #97781 (only created to run deploy tests)
|
||
|
|
cf5339d1be |
test: deflake basePath external navigation (#97611)
## Summary The basePath external-navigation test depended on loading Google and recognizing its DOM before exercising browser history. That made the test sensitive to public network availability and third-party page changes; the reported failure was confirmed in the Safari/WebKit CI run on the initial attempt and both retries. For local and CI test-server runs, this replaces the public dependency with a minimal page served from a separate local origin, preserving the cross-origin, full-document navigation being tested. Deploy mode remains enabled and uses a deploy-accessible external target because deployed browsers cannot reach the test runner. Both paths now synchronize on browser origin and exact restored-page state instead of third-party DOM or a fixed sleep. ## Verification - Focused production/start-mode test passed three consecutive times in Firefox - Focused production/start-mode test passed in Chromium - Prettier and ESLint passed for the changed files - `pnpm build-all` - Not run: Safari/WebKit locally (Playwright’s unsupported Amazon Linux fallback requires incompatible Ubuntu system libraries; the Safari CI job is the definitive validation) - Not run: deploy-mode execution locally (deployment credentials unavailable; the deploy path remains enabled for CI) <!-- 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> |
||
|
|
d7aa66c345 |
Remove generated error codes (#97687)
### Why? Should come up with better solution that does not block PRs with git conflict x-ref: https://vercel.slack.com/archives/C02CDC2ALJH/p1785263902728189?thread_ts=1785263687.502649&cid=C02CDC2ALJH ### How? - Delete `errors.json`, the error-code SWC plugin, generated WASM, merge driver, and validation/build tooling. - Stop attaching error codes to server-rendering digests, redboxes, and telemetry; native `Error.code` and `Error.name` remain available where applicable. - Remove the development-overlay error feedback UI, middleware, and telemetry event that depended on stable codes. - Update fixtures, snapshots, and guidance for code-free errors and numeric-only digests. <!-- NEXT_JS_LLM --> |
||
|
|
43273a1d21 |
[PPF] Instant validation for unstable_navigation() (#97309)
Adds discriminated error messages for `unstable_navigation()` used outside of Suspense. Previously we only had to discriminate two kinds of errors in each validation flow: - App Shell -- 1. link data (if it resolves in `Runtime`) or 2. dynamic data (if it resolves in `Dynamic`) - Static Shell -- 1. runtime data (if it resolves in `Runtime`) or 2. dynamic data (if it resolves in `Dynamic`) However, with `navigation()` (96908), the App Shell flow needs to distinguish three kinds: 1. link data, 2. navigation (if it resolves in `NavigationRuntime`), and 3. dynamic data. This complicates the validation code a bit, because we can no longer get away with one retry. To accommodate this, i restructured the validation code to no longer recurse as a method of retrying. We just loop over an array that defines what order we should try the stages in + what kind of hole shows up in each stage. I've also removed `hasAmbiguousErrors` and the associated logic. since we're no longer mixing Static+Runtime segments in a runtime prefetch, this is no longer relevant -- all segments used for validation use one stage, so whether or not the error is ambiguous only depends on the stage. For ease of reviewing, this is split into two commits: 1. updating the (many) places that need a new `DynamicHoleKind` in our error messages. This one has a lot of pretty mechanical changes, and was also machine-reviewed for consistency already, but the messages themselves are worth looking at (we'll also need to update docs -- leaving that for a follow-up) 2. the actual instant validation changes, i.e. using the new `DynamicHoleKind` and adding the new pass |
||
|
|
8482890aca |
test: make app-document-import-order more robust (#97682)
- The `brower.eval(callback)` version was a bit weird, use beforePageLoad as we do everywhere else - Properly parse the URL and match against the pathname, to not break with `?dpl` |
||
|
|
ba9f073cc2 |
Add a Turbopack error for missing root layouts (#97639)
## Summary Turbopack currently builds an App Router page without a root layout, leaving the problem to surface later at runtime. Report it while building the app structure instead: `route/page.js doesn't have a root layout. To fix this error, make sure every page has a root layout.` The check uses the layouts already inherited during directory-tree traversal, so route groups and apps with multiple root layouts keep working. Route handlers are unaffected. Webpack behavior is intentionally unchanged in this PR. ## Verification - `HEADLESS=true pnpm test-dev-turbo test/e2e/app-dir/create-root-layout/create-root-layout.test.ts` - `HEADLESS=true pnpm test-start-turbo test/e2e/app-dir/create-root-layout/create-root-layout.test.ts` - `HEADLESS=true pnpm test-dev-webpack test/e2e/app-dir/create-root-layout/create-root-layout.test.ts` - `HEADLESS=true pnpm test-start-webpack test/e2e/app-dir/create-root-layout/create-root-layout.test.ts` - `HEADLESS=true pnpm test-dev-turbo test/e2e/app-dir/root-layout/root-layout.test.ts` |
||
|
|
3cdb56c251 |
[PPF] unstable_prefetch() (#97622)
Implements `unstable_prefetch()`, which is intended for use in
`partialPrefetching`. `await unstable_prefetch()` excludes content from
the app shell -- it will only be available when using `prefetch={true}`
(speculative prefetch) or during navigations.
As a rule of thumb, `unstable_prefetch()` resolves whenever static
`params` would:
- in a static prerender
- but NOT the app shell extracted from it, which is param-less
- in a runtime prefetch (`prefetch={true}`)
- but NOT a runtime app shell, which is param-less
Note that `prefetch()` is URL data, so using it in an App Shell without
Suspense will trigger an instant insight.
### Implementation notes
`prefetch()` is treated like URL data, so it resolves in the
`PrefetchStatic/PrefetchRuntime` stages added in #96908. The
implementation is basically analogous to `unstable_navigation()` except
using different stages. i've considered abstracting them into one
implementation, but decided against that for now, we can deduplicate
later.
Error messages about URL data have not been updated to mention it yet --
we will do that as a follow up, along with docs.
`await prefetch()` does not count as a runtime data access, meaning that
it won't affect the static prefetch hint for a route. however `await
prefetch(); await cookies()` does deopt the route, because using a
speculative runtime prefetch would reveal more content. Note that this
may cause us to unnecessarily deopt a shell to runtime even if only the
speculative part of the content would be improved by a runtime request;
this is not a new issue, but it's something we should optimize.
|
||
|
|
6c0dd8400b |
Turbopack: deduplicate Pages Router app chunks (#97664)
Previously, Turbopack treated `_app` and Pages router pages as completely separate. So using code both in `_app` and `pages/foo.tsx` would lead to a lot of duplicated code to be loaded at runtime. But in reality, `_app` is always loaded for Pages, so we can thread the availability info and skip chunking modules that were already loaded by `_app`. Recreation of https://github.com/vercel/next.js/pull/97549 Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> |
||
|
|
14a69ef78b |
fix: preserve trailing slash during export MPA fallback (#97613)
Keeps the trailing slash when a static export falls back to a full page load, so you land on the canonical URL instead of bouncing through a redirect. Fixes https://github.com/vercel/next.js/issues/97608 --------- Co-authored-by: Jude Gao <jude.gao@vercel.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e3a78fd3c1 |
refactor: move useDynamic{Route,Search}Params to reduce snapshot churn (#97360)
Relocates `useDynamicRouteParams` and `useDynamicSearchParams` to avoid having to update `client-hook-abort-reasons.test.ts` every time there's changes in `dynamic-rendering.ts`. Webpack leaked the source location of the `React.use` calls inside, so any code change that moved the source of the hook would require a snapshot update. For reasons i don't quite understand, moving the hooks to a different file improved the ignore-listing, i.e. we no longer point to the internals of the hooks. This is surprising but not unwelcome. |