mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/catchall-fallback-values
35737 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
c51f582a70 |
docs: clarify revalidateTag profile expire semantics (#97836)
Once you call `revalidateTag`, the next request that reads the tagged data kicks off revalidation. That tagged data is SWR until the revalidation completes, or until the expire time, whichever comes first. It is the profile's expire time, counted from the `revalidateTag` call, that defines the limit after which requests block. If the first request after `revalidateTag` is called arrives after the expire time, it'd block. So the profile is a way to say, after this tag is revalidated, past the expire time, data correctness is more important than being fast. |
||
|
|
03a09695be |
docs: param access on use-server (#97865)
Correctness pass — following https://github.com/vercel/next.js/pull/97823 |
||
|
|
3d57c6e581 |
docs: fix typos and correctness issues in App Router docs (#97823)
## Summary A systematic review pass over the App Router documentation (`docs/01-app`, 281 MDX files) fixing clear-cut typos, grammar issues, and technical inaccuracies — code samples, file names, and API signatures that had drifted from the implementation in `packages/next/src`. The review ran four passes: an automated spell check with a triaged allowlist, targeted grammar-pattern searches, structural checks (frontmatter, code fences, link targets), and a technical-accuracy pass over all getting-started pages plus high-traffic API reference pages, with every suspected inaccuracy verified against the source before editing. Only unambiguous issues were fixed; anything debatable was deliberately left alone (e.g. the `/aboot` route in the typedRoutes docs is an *intentional* invalid-route example, and the CLI docs table intentionally mirrors the actual `--help` output). Notable fixes: - JSX examples now `await params`, matching the Promise-based API and their TypeScript counterparts - `generateStaticParams()` is now exported in samples so the convention actually takes effect - `<Link prefetch>` props table includes `"auto"`, matching the `boolean | "auto" | null` type in `link.tsx` - `Navigator.geolocation` casing, the canonical react.dev `"use client"` link, and a missing `Link` import in the typedRoutes docs - Mismatched or wrong `filename=` labels in TS/JS switcher pairs (`form.mdx`, `mutating-data.mdx`, `css.mdx`) and a wrong `generateStaticParams` anchor target - ESLint monorepo example registers the plugin as `@next/next` so the documented rule prefixes resolve - Spelling/grammar: `emphaize`, `invokations`, `behaviour` → American spelling, "setup" → "set up" as a verb (5×), "a RSC" → "an RSC", `NextJS` → `Next.js`, a stray comma, and "a `opengraph-image.tsx`" article plus its folder path ## Verification - `cspell` over all 281 files in `docs/01-app`: 0 issues outside a reviewed allowlist of product names, API identifiers, and intentional example strings - `prettier --check` (3.6.2, repo config): passes on all 19 changed files - Grammar-pattern and structure greps: clean - Technical claims cross-checked against `packages/next/src` and `packages/eslint-plugin-next` - Docs-only change; no runtime code touched <!-- NEXT_JS_LLM --> <!-- fleet eeae4385-18f5-406d-915e-986961503ed2 --> 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> |
||
|
|
b77a54df29 |
Add deploy release test skill (#97563)
## Summary - add a repository-local skill for manually triggering `test_e2e_deploy_release.yml` - require the exact commit preview package to be available before dispatch - preserve default workflow inputs and verify the resulting branch, SHA, and package URL <!-- NEXT_JS_LLM --> |
||
|
|
6826f5abe5 | [test] Deflake the page config test for a string config value (#97848) | ||
|
|
76e24273c7 |
test: stabilize read-only page recreation (#97674)
### What? Stabilizes the read-only source HMR page deletion and recreation test in webpack development mode. ### Why? The test reloads after restoring a deleted page because webpack does not automatically refresh when the route reappears. In polling watch mode, that reload could wait for the full page load or be aborted by a concurrent Fast Refresh navigation. Either error escaped the retry callback, allowing one transient navigation race to fail the test before it could retry. ### How? The webpack-only reload now waits only until the document is available instead of waiting on potentially slow subresources. Transient reload failures are handled inside the retry callback, while the existing page-content assertion remains the success condition. This preserves the test's intent and ensures a route that does not recover still fails. ### Verification - Fresh `pnpm build` followed by a cold headless webpack run: 3 passed - Three additional headless webpack runs: 3 passed each - Headless Turbopack run: 2 passed, 1 expected skip - Focused Prettier and ESLint checks <!-- 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> |
||
|
|
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. |
||
|
|
b2f3cb6a03 |
Upgrade React from eafeac09-20260819 to bd6ea412-20260824 (#97812)
|
||
|
|
1780fa0375 |
fix: reuse a single drain listener when piping Node streams through gzip (#97698)
## Summary
On Next 16.3+, App Router HTML and RSC responses are piped with
`pipeNodeReadableToNodeResponse`. Under gzip backpressure it called
`res.once('drain')` on every write that returned `false`, which leaks
one listener per backpressured write onto the zlib stream:
```
MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added to [Gzip]. MaxListeners is 10.
```
The `compression` middleware overrides only `res.on`, forwarding it to
the zlib stream once that stream exists (or buffering it until then). It
leaves `removeListener` pointing at the response, so a `once` listener
is added to the stream but never removed from it. `off` cannot reach it
either.
`createWriterFromResponse`, directly above in the same file, already
uses a single `res.on('drain')` for the response lifetime. This makes
the Node-readable path match: pause on `write() === false`, resume from
one listener, `off` on `close`.
Resuming on that event is correct for both cases. With compression
active the wrapped `res.write` writes into the zlib stream, so `false`
means the zlib stream's buffer is full and its `drain` is the right
signal — the middleware separately wires the socket's `drain` to
`stream.resume()`. With compression inactive the listener stays on the
response, where `off` does remove it.
The pause is latched so a `drain` only resumes a readable that this
function paused, and a `drain` arriving after teardown is ignored.
### Scope
- Distinct from #96173 / `releaseCompressionStream`, which destroys the
zlib stream on **abort**. This warning fires on a **live** stream that
is still flowing: slow clients, large streamed HTML/RSC.
- Deliberately leaves the bare `readable.destroy()` in the `close`
handler alone. That is the separate abort-reason bug in #96704, with
fixes already open in #96715 and #96717.
- Not fixable by bumping the vendored `compression`: as #96173
established, upstream `compression@1.8.1` still has no premature-close
handling, and it is a prebuilt bundle here. `setMaxListeners` or
`compress: false` would only hide this.
- Mentioned as a `compress: false` workaround in #95130.
## Verification
`packages/next/src/server/pipe-readable.test.ts` drives real
backpressure (48 × 64 KiB through a throttled reader) and asserts
exactly one `drain` registration on the zlib stream, no
`MaxListenersExceededWarning`, and a complete, byte-correct payload
after `gunzip`. The payload assertion is what catches a latch that
stalls the stream.
Confirmed it fails without the fix. Measured against the real compiled
function on Next 16.3, patching only the drain handling:
| | before | after |
| --- | --- | --- |
| `drain` registrations on `[Gzip]` | 48 (one per backpressured write) |
**1** |
| `MaxListenersExceededWarning` | 1 | **0** |
| decompressed payload | complete | complete |
At 120 × 64 KiB the before-count scales with writes (120 registrations);
the after-count stays at 1. Separately reproduced end to end on a Next
16.3 standalone production build with `compress: true`: slow-consuming a
large gzipped page emitted the warning, and after this change it did
not, with `Content-Encoding: gzip` still set and valid gzip on the wire.
I could not run `pnpm test` in this checkout, so please treat CI as the
source of truth for harness integration. The assertions themselves were
validated against the real function as described above.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jude Gao <jude.gao@vercel.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
bc218dbb85 |
test: preserve server cache after compile error (#97724)
### What? Adds a focused Turbopack development regression test covering server HMR recovery from a compile error. ### Why? A server compile error must not force a full server module reevaluation when HMR recovers. Clearing the server module cache would discard stable state held by unchanged dependencies and undermine server Fast Refresh module preservation. ### How? The test reuses the existing module-preservation fixture and its unchanged server dependency evaluation timestamp. It introduces a deterministic syntax error, verifies the Turbopack build-error redbox, repairs the page, waits for the repaired server render, and then confirms the dependency still exposes its original timestamp. The patched fixture is restored in a `finally` block so failures cannot leak state into later tests. ### Testing - `pnpm test-dev-turbo test/development/app-dir/server-hmr/server-hmr.test.ts` - 17 tests passed <!-- NEXT_JS_LLM --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
3bfdb29e5a | v16.4.0-canary.6 v16.4.0-canary.6 | ||
|
|
33b7edfc19 |
turbo-tasks: execute scheduled tasks inline when they are read (#96808)
### What? Two changes to how the async turbo-tasks engine handles a read whose value isn't ready yet: 1. When a read finds its task merely *scheduled* (queued, not started), it takes the task out of the scheduler queue and executes it on the reading thread instead of waiting for a worker to pick it up. If the execution completes without yielding, the read returns synchronously; if it yields, the partially-polled execution is handed to tokio and the read waits as before. 2. When a read finds its task's execution already **in progress** (a worker is running it), it now waits directly on the task's completion event instead of also attempting to take it over — avoiding a futile acquisition of the scheduler's queue lock, which is otherwise the most contended lock in the system. An inline execution is visible in traces: the executed task's span carries `inline_execution = "complete"` when the reader's poll finished it, or `"partial"` when it yielded and was handed to the runtime. A task a worker executed leaves the field unset. Optional diagnostics (`InlineExecutionStats`) count queue pushes, claim attempts and their outcomes, and reads that waited for an already-running task. They are behind the `inline_execution_stats` Cargo feature, off by default, so a normal build has no counters, no atomics and no extra fields. ### Why? A large share of read misses target tasks that have merely been scheduled, not tasks actually being computed elsewhere. Parking on those adds two avoidable thread hops (schedule → worker pickup → wake) for work the reader could simply do itself. Conversely, a read of a task a worker is already executing gains nothing from trying to claim it, so it shouldn't pay for trying. ### How? - `PriorityRunner` gained a keyed claim: queued entries are indexed by a `ScheduleKey` (task or local task) and stored in a slot store, so one entry can be removed by key under the existing queue lock while the priority heap keeps a tombstone that a popping worker skips. Every scheduled execution still runs exactly once. - The backend already distinguished a queued task from one already executing (`InProgressState::Scheduled` vs. `InProgress`) when building a listener for a read, and discarded that distinction. It's now threaded through as `ReadOutcome<T> = Value(T) | Scheduled(EventListener) | InProgress(EventListener)`; only `Scheduled` reads attempt a claim. The state is a hint — a worker pops a task off the queue before it marks it started, so a read can see `Scheduled` for a task that is no longer claimable. Acting on a stale hint costs one failed claim, never correctness. - Inline execution nests (A reads B inline, B reads C inline, ...), so it's capped at 16 levels per thread to bound stack growth; at the cap, reads fall back to waiting for a worker as before. The cap is a thread-local counter because it guards the *thread* stack, not the task stack. - Recording the outcome on the *executed task's* span needs a small handoff: when `poll_once_or_spawn` returns, that task's span has already been exited, so the executor registers the span it instruments the task body with in a slot that only exists while a claimed task is polled inline. - Added `turbo-tasks-backend/tests/inline_read_execution.rs` (8 tests), `inline_execution_span.rs` and `inline_execution_span_worker.rs`, plus unit tests in `priority_runner.rs`/`manager.rs` covering keyed claim/exactly-once, inline completion for global and local tasks, the in-progress/scheduled distinction, nested inline executions, restoring from a persistent cache, and a deep dependency chain (stack-depth guard). ### Benchmarks `bench/nested-deps-app-router-many-pages` (1000 pages, 3020 generated components, 3010 routes), release builds of `@next/swc` for this branch and for its merge base, runs interleaved, first run of each variant discarded as warm-up. Shared 8-vCPU VM. **Cold `next build --turbopack`** — 5 measured runs per variant: | metric | canary (median) | this branch (median) | delta | | --- | ---: | ---: | ---: | | total build | 96.70 s | 89.29 s | **−7.7 %** | | turbopack compile phase | 68 s | 61 s | **−10.3 %** | Raw totals (ms) — canary: 93566, 95354, 96702, 96717, 97545; branch: 89070, 89140, 89287, 89892, 90462. The distributions don't overlap (the branch is faster in all 25 pairwise comparisons, exact two-sided Mann-Whitney p ≈ 0.008), and the 7.4 s median gap is about twice canary's own 4.0 s run-to-run spread. **Incremental build** (second build reusing the persistent cache) — 3 measured runs per variant: 36.14 s vs 37.29 s median, compile phase 10.4 s vs 10.5 s. The 1.2 s gap is *smaller* than canary's own 1.2 s spread, so this is **no measurable difference** — the incremental case is dominated by restore and by static generation in Node workers rather than by the scheduling this PR changes. **Cache-hit reads** (`task_overhead/turbo-cached-*`, criterion): no regression. Measured before the rebase, on the same read-path code — the hit path never enters this code, since the inline attempt only happens on the branch where a read reports a miss. Caveats: one app and one workload shape, on a shared VM, so only the relative comparison on the same machine is meaningful, not the absolute seconds. <!-- NEXT_JS_LLM --> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> --------- 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: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> |
||
|
|
4b4b878c99 |
Revert "Record fallback cache lifetime for blocking PPR routes (#97821)" (#97835)
Reverts #97821, which shipped in v16.4.0-canary.5 and broke the deploy e2e tests (run 32770221068): segment-cache/prefetch-app-shell's "uses separate static App Shells for links with different root params" fails deterministically. The reverted change stamped the collected cache lifetime onto the cache entries seeded from fallback files. Those entries are placeholders that exist to be replaced — their short default lifetime is what drives the regeneration that specializes them — so a blocking route's segment entries were served as static-forever placeholders and the App Shell for a non-prerendered root param never arrived. A redesigned fix that records the rendered-content lifetime in separate, additive fields (leaving the seeded-entry lifetime untouched) is staged on `acdlite/fallback-entry-lifetimes`. |
||
|
|
2cee98ab97 |
Sweep stale Turbopack output from distDir on dev startup (#97591)
## Summary Turbopack dev never removes the content-hashed chunks it supersedes, so `.next/dev` grows without bound. Webpack dev avoids this by wiping `distDir` on every start. Adds a `maxAgeMs` argument to `recursiveDeleteSyncWithAsyncRetries`: delete files whose mtime is at least that old, remove directories once empty. Turbopack dev passes one week. ## Verification - `pnpm test-unit test/unit/recursive-delete.test.ts` - ~Not run:~ dev-server behavior against a real `.next/dev` <!-- NEXT_JS_LLM --> |
||
|
|
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 |
||
|
|
ef85120648 |
Turbopack: Improve file existence error handling in realpath_with_links and in module resolution (#97717)
Fixes some regressions from
https://github.com/vercel/next.js/pull/97395.
Human summary of changes:
- Plainly return `NotFound` for `realpath_with_link` whenever any step
in the resolution algorithm fails, matching the behavior the libc uses.
- When probing in the module resolution algorithm, explicitly check for
`NotFound`.
- Fully remove the extremely non-standard and confusing behavior of
trying to return the last processed path from `realpath_with_links` if a
failure occurs.
- Modify `realpath` to return a `RealPathError` instead of just an
`anyhow::Error`, so that callers that care about the actual error kind
but not all of the intermediate links can still use it instead of
`realpath_with_links`.
- Rename `RealPathResultError` to `RealPathError`, and make it actually
implement `Display` so that it's easier to coerce to an `anyhow::Error`.
This requires including a tiny bit more data on this object, but in the
hot path it's just one extra clone of a `FileSystemPath`.
Human discussion:
https://vercel.slack.com/archives/C09R44U5HQW/p1787322213695299
AI slop summary of why #97395 caused a regression:
> The regression came from newly added missing-target detection in
|
||
|
|
8911fe3612 |
Write traces with buffered synchronous IO (#96874)
### What?
Writes `.next/trace` with a fixed buffer and `fs.writeSync` instead of
an `fs.WriteStream`. `Reporter.flushAll` becomes synchronous and drops
its unused `opts.end` parameter.
### Why?
**A production build could hang on the trace reporter.**
`flushAll` ended the write stream whenever the phase was not
`PHASE_DEVELOPMENT_SERVER`:
```ts
if (opts?.end || phase !== PHASE_DEVELOPMENT_SERVER) {
return writeStream.end()
}
```
No caller ever passed `opts.end`, so in production the stream was ended
on *every* flush. But builds flush repeatedly — after each Turbopack
compilation event in `backgroundLogCompilationEvents`, and again in the
`finally` of `next build` before `uploadTrace` reads the file. Only the
last of those is terminal.
That gives a flush → write → flush sequence against a stream that is
already finished:
1. The first flush calls `writeStream.end()`.
2. A later span is reported. `write()` on an ended stream returns
`false` and, because the stream will never drain again, never emits
`'drain'`.
3. The reporter parks on `await this.drainPromise`.
4. The next flush awaits that write. Nothing resolves it.
The build hangs in its `finally`, and every span after the first flush
is missing from the trace file.
### How?
`RotatingWriteStream` now holds a 16KB `Buffer`, encodes each event into
it directly, and writes with `fs.writeSync` when it fills or when
`flushAll` is called. There is no close, so the end-of-stream state that
caused the hang no longer exists.
Trace files are small — under 1KB for a production build, a few MB for a
long dev session — so the async write path was never buying throughput.
Buffering to 16KB keeps a build at a handful of writes rather than one
per span, which matters because the webpack/rspack profiling plugins
open a span per module(!!).
### Testing
`packages/next/src/trace/trace.test.ts` covers the reporter directly:
flushing repeatedly, auto-flush when the buffer fills,
truncate-vs-append per phase, rotation at the size limit (counted in
UTF-8 bytes, not UTF-16 code units), complete batches across a sweep of
event sizes that land on the buffer boundary, an event too large to
buffer at all, and a partially encoded multi-byte event.
Verified end to end against a real `next build` (34 spans, valid NDJSON,
`next-build` span present) and a real `next dev` session (spans landing
mid-session, and accumulating across a restart).
|
||
|
|
7d29e358c8 | v16.4.0-canary.5 v16.4.0-canary.5 | ||
|
|
2690ad4922 |
Record fallback cache lifetime for blocking PPR routes (#97821)
A dynamic route that can't serve an HTML fallback (`fallback: null`, e.g. because its cached shell is keyed on the route params) still produces cacheable fallback artifacts during the build: the per-segment prefetch payloads served to the client Segment Cache, and the RSC template. Previously the prerender manifest only recorded fallbackRevalidate/fallbackExpire when the HTML fallback itself was servable, so these routes shipped their segment and .rsc outputs with no lifetime at all. Cache layers interpret a missing revalidate as a 1-second lifetime — both deployment infrastructure and the self-hosted incremental cache (calculateRevalidate). The result is that every segment entry for such a route goes stale one second after it's written, so nearly every prefetch of a non-prerendered URL serves stale, triggers a background revalidation, and writes a new cache entry. On a site with dynamic product/listing routes and default viewport prefetching, this amplifies into millions of cache writes per hour. The build already computes the correct lifetime: the export's collected cache control, which describes exactly the caches that completed into the fallback artifacts. The manifest now records it for every PPR route regardless of fallback mode, and the adapter's segment and RSC template outputs fall back to the manifest values when there's no HTML fallback output to inherit them from. When the export collected no cache control, the lifetime is `false` (keep until invalidated), matching how servable fallbacks are already treated. fallbackStatus and fallbackHeaders remain gated on the PRERENDER fallback mode because they describe the prerendered fallback response itself, which doesn't exist for blocking routes. |
||
|
|
9480f67d61 |
Document catchError for the Pages Router (#97748)
Supersedes https://github.com/vercel/next.js/pull/91848 `catchError` works in both the App Router and Pages Router, but its API reference only documented the App Router behavior. This adds a Pages Router reference backed by the same canonical document and scopes the content that differs between routers. The Pages Router examples use `reset()` because `retry()` is App Router-only, omit App Router-only navigation behavior, and use Pages Router component paths. The existing App Router guidance and shared API reference remain unchanged. <!-- NEXT_JS_LLM --> |
||
|
|
6df5c4562f |
clippy: allow needless late init where it makes sense (#96410)
This exempts certain blocks from the `needsless_late_init` rule where it hinders readability. Co-authored-by: vercel-fleet[bot] <308483924+vercel-fleet[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
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
|
||
|
|
30e73d2d14 |
docs: note that response headers must be set before calling handle() in a custom server (#97693)
Fixes: #82387 A custom server that calls `res.setHeader()` after `await handle(req, res)` silently loses the header, because Next.js has already begun sending the response by the time `handle` resolves. Unlike a normal Node `setHeader()` after `headersSent`, this does not throw, so the header just never reaches the client. This adds a note to the custom server guide showing the correct ordering and pointing out that the same applies to framework wrappers such as `reply.raw` in Fastify. |
||
|
|
88483bdedc |
Deduplicate the regress, wat/wasmparser and base64 dependencies (#97762)
### What? Collapses three duplicated crate dependencies, so each is built once instead of twice. **−150 KiB** of code Manifests and `Cargo.lock` only — no source changes, and none were needed. **`regress` → 0.11.1** (−133 KiB, the bulk of the win) **`wat` and `wasmparser` → the 239 family** (−13 KiB) **`base64` → one workspace declaration on 0.22** (−1.2 KiB) |
||
|
|
75c1f1f6e0 |
perf: split cold TurboMalloc accounting paths (#97767)
### What? Reduce the binary-size cost of TurboMalloc's allocation instrumentation while preserving its hot-path performance and accounting semantics. This adds a focused Criterion benchmark for TurboMalloc's raw allocation paths and moves only the uncommon global counter refill/spill operations behind cold, non-inlined helpers. Thread-local allocation counters, buffer checks, and common buffer adjustments remain inline at allocation sites. ### Why? Rust gives global allocator methods special inlining treatment. TurboMalloc's thread-local accounting was consequently duplicated across allocation and deallocation call sites in `next-swc`, contributing several megabytes of machine code. Moving the whole accounting path out of line recovered most of that space but made small allocations slower. Splitting only the rare atomic paths provides a smaller size reduction without that hot-path cost. ### How? The common path remains explicitly inlineable through `add`, `remove`, `update`, and the thread-local access helper. When the local buffer must be refilled or spilled, it calls a `#[cold] #[inline(never)]` helper that performs the global relaxed atomic update. The arithmetic, thresholds, target buffer values, counter increments, and atomic ordering are unchanged. The counter test now also covers realloc growth/spill and shrink/flush behavior. ### Benchmark results Linux x86_64 Intel Xeon, Rust 1.99.0-nightly / LLVM 22.1.8. Native artifacts used the repository's production release command. Direct application measurements used clean `bench/heavy-npm-deps` `next build --turbopack` runs, with 8 samples per arm and rotated/interleaved ordering. #### Binary size | alternative | raw artifact delta | `.text` delta | |---|---:|---:| | No instrumentation | −6,260,168 B (−4.23%) | −5,466,304 B (−7.69%) | | `inline(never)` on `add` / `remove` / `update` | −4,338,856 B (−2.93%) | −4,342,464 B (−6.11%) | | `inline(never)` on whole `GlobalAlloc` methods | −4,153,792 B (−2.80%) | −3,714,688 B (−5.23%) | | **Cold atomic refill/spill helpers (this PR)** | **−502,864 B (−0.34%)** | **−732,736 B (−1.03%)** | The current artifact was approximately 148.1 MB raw with 71.1 MB of `.text`. The retained split recovers 8% of the raw and 13% of the `.text` saving from removing instrumentation entirely. #### Direct `next build --turbopack` | alternative | mean effect vs current | bootstrap 95% CI | permutation p | |---|---:|---:|---:| | No instrumentation | −0.70% | [−2.32%, +1.12%] | 0.466 | | `inline(never)` on `add` / `remove` / `update` | **+3.53%** | **[+0.65%, +6.43%]** | 0.0448 | | `inline(never)` on whole `GlobalAlloc` methods | +1.02% | [−2.03%, +4.44%] | 0.586 | | **Cold atomic refill/spill helpers (this PR)** | **−0.18%** | **[−3.44%, +3.61%]** | **0.929** | No application-level difference was detected for the retained split or the whole-method placement. The helper-level noinline placement showed evidence of a modest regression. #### Focused allocator Criterion benchmark Percent changes are lower-is-better. Where two figures are shown, they are independent runs in reversed order. | alternative | alloc + dealloc | alloc + realloc + dealloc | |---|---:|---:| | No instrumentation | −43.4% | −36.1% | | `inline(never)` on `add` / `remove` / `update` | +5.5% / +7.5% | +3.0% / −8.1% (inconclusive) | | `inline(never)` on whole `GlobalAlloc` methods | **+19.4% / +21.4%** | **+2.9% / +6.4%** | | **Cold atomic refill/spill helpers (this PR)** | **−4.0% / −3.0%** | **−2.8% / −5.4%** | The full 28-cell turbo-tasks overhead suite was also run twice in reversed order. Large process-level effects changed magnitude or sign between runs, so no conclusion is drawn from that suite. ### Verification - `cargo fmt -- --check` - `cargo clippy -p turbo-tasks-malloc --all-targets -- -D warnings -A deprecated` - `cargo test -p turbo-tasks-malloc` - `cargo check -p turbo-tasks-malloc --all-targets` - `cargo bench -p turbo-tasks-malloc --bench allocation -- --test` - Release `next-swc` builds and Node smoke-loads for every measured arm <!-- 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> |
||
|
|
6970724fba | v16.4.0-canary.4 v16.4.0-canary.4 | ||
|
|
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)
|
||
|
|
a1cc1eafb3 |
Remove unused return values from getPageStaticInfo (#97661)
Some cleanup, this was never used: - `config` from `getStaticInfoIncludingLayouts` - `getServerSideProps` and `getStaticProps` from pages router |
||
|
|
a11f82c557 |
[turbopack] defer NFT module content hashes (#97773)
### What? Separate trace-graph module path collection from module content hashing. Exclusion filtering now requests only cached module identifiers, while full NFT data still layers content hashes on top when they are required. ### Why? Applying output-file-tracing exclusion globs needs module paths but not content hashes. Keeping these computations separate avoids requesting full-graph hashes solely to decide which modules should be skipped. This is an inspection experiment, not a demonstrated performance improvement. A seven-sample release A/B left the median compilation phase unchanged at 2700 ms. ### How? A private identifier-only graph task owns the existing DFS traversal. `traced_module_data_for_graph` reuses those identifiers and computes hashes for its full result. `traced_modules_for_entries` drops the unused hash-salt input and consumes only identifiers for glob matching. ### Verification - `cargo fmt -- --check` - `cargo check -p next-api` - `next-server.js.nft.json`: byte-identical SHA-256 `da4c5a494efbbb38eb966066115de96adb5d0ec693fba17bbed669c99cafae43` - `next-minimal-server.js.nft.json`: byte-identical SHA-256 `7c58d40dc2ecbe6ddd26dd0c602467d80f1a8ea3320e2c6e8e5f4d6be14156cd` - Not run: clippy, Rust test suite, or integration tests (inspection experiment) <!-- 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> |
||
|
|
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> |
||
|
|
24f975677e |
docs(adapters): document assetsHashes and routing.middlewareMatchers (#96536)
While writing an adapter against `16.3.0-canary.107` I noticed the adapter docs are missing two fields that the shipped `NextAdapter` types already have. **`assetsHashes`** — every `PAGES` / `PAGES_API` / `APP_PAGE` / `APP_ROUTE` / `MIDDLEWARE` output carries it right next to `assets`, and it's declared with a doc comment in `build-complete.d.ts`, but none of the five shapes in Output Types mention it. **`routing.middlewareMatchers`** — dumping the `routing` object from a real `onBuildComplete` call gives: ``` afterFiles, beforeFiles, beforeMiddleware, dynamicRoutes, fallback, middlewareMatchers, onMatch, rsc, shouldNormalizeNextData ``` but the docs list eight of those nine, everywhere the interface appears: the Creating an Adapter snippet (which says "The interface is defined as follows"), the API Reference parameter list, and Routing Information. This one feels worth fixing soon — an adapter that does its own request matching from the documented fields alone has no way to decide when middleware should run. My guess for why nobody has hit it: if you pass `routes: routing` wholesale into `resolveRoutes` from `@next/routing`, everything works without ever looking at the field. The wording I added comes from the doc comments in the shipped types, not my own descriptions. `middlewareMatchers` is inserted where the type puts it (right after `beforeMiddleware`). For what it's worth: I checked the rest of the section against the same build while I was at it — the `output: 'export'` behavior, the prerender classification fields, `pprChain.headers`, the fallback fields, the immutable-assets flow, and the `@next/routing` params/result — and everything else matched the docs. These two were the only gaps I found. |
||
|
|
4c4d523084 |
Migrate remaining async blocks to async closures (#97701)
**View diff without whitespaces** Fix remaining cases of https://github.com/vercel/next.js/pull/97666 - Mostly replacing async blocks with async closures - And second commit: some more making closures sync where possible |
||
|
|
d69a7041f3 |
Turn off the adapter route collapses by default (#97774)
`experimental.collapseAdapterRoutes` now defaults to `false`, so a build keeps one entry per route unless the project opts in. The changes below it in this stack stay as they are, and a project that sets the option to `true` gets the route table that they produce. The default starts off so that we can dogfood the behavior on selected apps before every build gets it. The default moves back to `true` once enough apps have run with it. The four fixtures under `test/production/app-dir/adapter-dynamic-routes` set the option, because their snapshots pin the collapsed table. A build of the Cache Components fixture without the option emits the 27 entries that the first commit in this stack recorded, which is what the option now turns off. |
||
|
|
6f1fb7f73a |
Serve a run of fallback shells from one route entry (#97738)
A fallback shell repeats the whole path of its source page and resolves the leading params to concrete values. Take `/[team]/[locale]/[slug]`, where the build prerenders three combinations of the two leading params. It emits one entry per combination, and the entries differ only in that leading part of the path: ```diff - /acme/en/<slug><suffix> -> /acme/en/[slug]<matched suffix> - /acme/de/<slug><suffix> -> /acme/de/[slug]<matched suffix> - /globex/en/<slug><suffix> -> /globex/en/[slug]<matched suffix> + /(?<shellPrefix>acme/en|acme/de|globex/en)/<slug><suffix> -> /$shellPrefix/[slug]<matched suffix> ``` One entry now serves them all. Its pattern lists the leading part of each shell path as an alternative, and its destination copies whichever one matched. Those alternatives are complete, and that matters. A pattern that offered a choice per param instead, `(acme|globex)/(en|de)`, would also match `globex/de`. The build never prerendered that combination, so a request for it would resolve to an output that does not exist, and it would then fall through to whichever route claims the rewritten path. An entry serves neighbours in the manifest, and only those. It takes the position of the first shell that it replaces, so every replaced shell keeps its place relative to the routes around it. Any other route between two shells ends the run, because an entry that reached across it would move ahead of a route that a request matches first. The shells of a run also have to agree on `fallback: false`, because an entry carries one set of conditions. A source page can therefore hold several runs, and a shell can belong to none. That happens when the build resolves a different number of params for neighbouring shells, which leaves them with different paths after the resolved part. This removes the multiplier that the number of prerendered combinations applies to every route below the resolved params. We measured the same in-progress feature branch of the v0 chat app as the previous changes in this stack. Building it before and after this change removes 87% of the routes that those changes left. Across the stack that branch loses 95% of its routes. This collapse follows `experimental.collapseAdapterRoutes`, which an earlier change in this stack added. A build that sets it to `false` emits one entry per shell. **Verified with a [full deploy test run](https://github.com/vercel/next.js/actions/runs/32577936779).** |
||
|
|
91d378dbb6 |
[test] Capture the route table for apps with several param shapes (#97728)
The existing fixtures take a single root param, so they do not cover the shapes that the fallback shell entries can take. This adds fixtures and suites for three more shapes, and pins the entries that each one produces. The first has a root layout that takes two root params, and returns three combinations of them rather than the full product of four. An entry that held each root param position separately would also match `sparse/de`, a combination the build never prerendered, and a request for it would resolve to an output that does not exist. One combination is `acme.one-two,three`, which carries the characters a regex treats as special, and the snapshot shows the build escaping them. The second builds that same app with a single combination, so each source page has one fallback shell. The third has no root params at all. Its root layout sits above the dynamic segments, so `team` and `locale` are ordinary dynamic params that `generateStaticParams` enumerates on a nested layout. The build then produces two shapes of entry for one source page: entries that resolve both params, such as `/sparse/en/posts/[id]`, and entries that resolve only the first, such as `/sparse/[locale]/posts/[id]`. The order of those entries carries the behavior, because a request for `/sparse/en/posts/1` has to reach the output that resolves both params rather than the one that resolves only the first. The snapshot pins that order. Two comments on the Cache Components suite were wrong, and this corrects them. That fixture takes one root param with two values, and not two root params. Its entries also no longer come in pairs, because one entry serves the request for the page, the request for its `.rsc` payload, and a per-segment prefetch. |
||
|
|
45d3aa06cf |
Stop emitting a separate route entry for a dynamic route's RSC form (#97726)
A dynamic app page in an app with an app directory needs two routes today. One matches a request for the page, and one matches a request for its `.rsc` payload or a per-segment prefetch. Simplified, with the request pattern on the left and the artifact it resolves to on the right: ```diff - /de/<slug><.rsc|.segments/*.segment.rsc> -> /de/[slug]<matched suffix> - /de/<slug> -> /de/[slug] + /de/<slug><.rsc|.segments/*.segment.rsc|> -> /de/[slug]<matched suffix> ``` One entry now serves both, because the suffix group gains an empty final alternative. The group therefore always matches. It captures an empty string for a request that carries no suffix, and the destination copies whatever it captured, so a plain request resolves to the page and a suffixed request resolves to the artifact it asks for. The parameter is matched lazily, so a request for `/de/other.rsc` still prefers the shorter parameter and the `.rsc` suffix over a parameter that swallows the suffix. The empty alternative is deliberate, and an optional group would not be equivalent. A consumer of these entries can resolve the placeholders in a destination from the match result rather than from the pattern. A group that does not take part in the match is then absent from that result, and the literal text `$rscSuffix` survives into the destination. A group that always takes part avoids depending on how any one consumer treats an absent key, and the cost of getting it wrong is a 404 on every plain navigation to every dynamic route. One case keeps two entries. A pages router route with `fallback: false` requires the preview cookies on the request for the page, and not on the request for its `.rsc` payload. An entry carries one set of conditions and cannot express that difference, so the merge applies only when both forms agree on their conditions. Every app that has an app directory therefore halves the entries for its dynamic routes, whether or not it uses Cache Components. We measured the same in-progress feature branch of the v0 chat app as the previous change in this stack, which enumerates precomputed flag, locale and device permutations in `generateStaticParams` for a top-level dynamic segment and so multiplies every route below it. Every dynamic entry on that branch merges. Building it before and after the change removes 48% of the routes that the previous change left, replaces each merged pair with a single entry, and changes nothing else once the build ID is normalized. Across both changes that branch loses 65% of its routes. This collapse follows `experimental.collapseAdapterRoutes`, which the previous change in this stack added. A build that sets it to `false` emits a separate entry for each form. **Verified with a [full deploy test run](https://github.com/vercel/next.js/actions/runs/32540912007).** |
||
|
|
7678e6e953 |
Stop emitting a redundant route per prefetch segment (#97720)
For a dynamic app page that has a fallback shell, say `/[lang]/[slug]` with a shell for the root param value `de`, the build emits three routes to an adapter. Simplified, with the request pattern on the left and the artifact it resolves to on the right: ```diff - /de/<slug>.segments/$d$lang/$d$slug<seg> -> /de/[slug].segments/$d$lang/$d$slug<seg> /de/<slug><.rsc|.segments/*.segment.rsc> -> /de/[slug]<matched suffix> /de/<slug> -> /de/[slug] ``` This change removes the first one. The second already covers it: its suffix group accepts `.segments/<path>.segment.rsc` as well as `.rsc`, and it copies the matched suffix into the destination, so a per-segment request resolves to the same artifact either way. That second route is also the only one that ever answered `_tree` and `_full` requests, because a per-segment route pins one literal segment path in its regex. A `_tree` prefetch for `/de/<slug>` resolves to `/de/[slug].segments/_tree.segment.rsc`, which the per-segment route cannot produce. So this removes a duplicate, not a mechanism. Only fallback shells emit per-segment routes, because their artifacts sit under an unresolved param and need a rewrite to reach. Segment artifacts for a concrete prerendered path need none, since a request for that path matches them directly. Apps with many shells therefore lose close to a third of their routes. We measured an in-progress feature branch of the v0 chat app, which enumerates precomputed flag, locale and device permutations in `generateStaticParams` for a top-level dynamic segment and so multiplies every route below it. Building that branch before and after the change removes 32% of its routes, adds none, and changes nothing else once the build ID is normalized. This also adds `experimental.collapseAdapterRoutes`. It defaults to `true`, and it controls this collapse together with the ones that follow in this stack. A build that sets it to `false` emits the same route table as a build without any change in this stack. `prefetchSegmentDataRoutes` stays in `routes-manifest.json`. A build that does not use the adapter reads the field from that manifest and derives the same routes from it. This change therefore leaves that path alone. **Verified upstack with a [full deploy test run](https://github.com/vercel/next.js/actions/runs/32540912007).** |
||
|
|
0247e34bf9 |
[test] Capture the dynamic routes a build passes to an adapter (#97719)
We are about to reduce the number of these entries, and each of those changes should arrive with a diff that shows which ones it removes or merges. That is what these fixtures record. The existing behavioral suites stay responsible for regressions, and they do detect a routing change of this kind, e.g. a build that dropped the `.rsc` entries of fallback shells would fail a root param case in `segment-cache/prefetch-app-shell`. These snapshots add the part those suites may not distinguish, since an entry that collapses into a less specific match can still serve a response that renders the same content. One fixture covers Cache Components with root params, the shape that grows with the number of root param combinations. The other covers what does not depend on Cache Components: the `.rsc` and plain entry pair that every dynamic app page receives, a route handler, pages that share one shape and differ in a static last segment, and a `fallback: false` pages router route whose plain entry carries a preview bypass condition that its `.rsc` sibling does not. It also holds a proxy next to static pages router pages, which adds one entry per page and is the only case here whose count grows with the number of pages rather than with the number of route shapes. A third test builds the first fixture again under a base path and asserts the prefix on every entry. The projection stays narrow so that unrelated build output leaves it alone. Both fixtures pin `cacheComponents` and `generateBuildId`, because CI would otherwise vary them. |
||
|
|
713bd67572 |
turbo-tasks: compile conditional cell updates once, not per cell type (#97763)
### What? Makes `CurrentCellRef::conditional_update_with_shared_reference` compile once instead of once per cell type. **−345 KiB** of code `__text` 66,832,376 → 66,479,352, measured on `libnext_napi_bindings.dylib` with the shipped release profile (thin LTO, `codegen-units = 1`, `--icf=all`). The 1,302 instantiations of this function collapse to **1**. ### Why? The function took its callback as `impl FnOnce`, so it was monomorphized for every `VcValueType` in the dependency graph — but its body is identical for all of them: read the cell, call the callback, hand the result to the *non-generic* `update_own_task_cell`. Nothing in it depends on the cell type. |
||
|
|
ba530fda6e |
Turbopack: add unit test coverage for FileSystemPath::hash_file (#97743)
### What? Adds unit test coverage for `FileSystemPath::hash_file`, the helper introduced in #97507. It pins the contract per filesystem entry type: a regular file is hashed by its content, a symlink by its *own* link content (target and whether it is absolute or relative), and a directory or a path that doesn't exist can't be hashed at all. Tests only — no production code is touched. ### Why? `hash_file` exists because hashing a traced path through `read()` was wrong: `read()` follows a symlink, so a link pointing at a directory made the build fail outright with `reading file "…" Is a directory (os error 21)`, and a link pointing at a file silently produced the hash of the target's bytes. The latter matters because consumers of the trace re-create a symlink entry *as a symlink* (`copyTracedFiles` does `readlink` + `symlink`) rather than copying the resolved file, so the link — not the file behind it — is what actually gets written out and therefore what the hash has to describe. Nothing guarded that. The distinction is invisible at the call site (`hash_file` reads like an ordinary "hash this path" helper) and "just read the file and hash it" is a natural-looking simplification, which is exactly how the original crash was introduced. A missing symlink case is also the kind of thing that only shows up in the wild, on someone else's repository layout, at build time. ### How? The test drives a real `DiskFileSystem` over a temporary directory rather than exercising `LinkContent::hash` directly, because the bug being guarded lives in the entry-type dispatch — which branch gets chosen — not in the hashing itself. A test that hashed a `LinkContent` it constructed would still pass against the buggy implementation. It reuses the harness style of the existing `read_glob` tests (a `turbo_tasks::function(operation, root)` wrapper read strongly consistently), and creates symlinks through a helper that mirrors their platform handling, using a junction point for a directory link on Windows. The interesting design point is how "the link is hashed, not its target" is asserted. Doing it by mutating a file and re-hashing would pull in filesystem watching and invalidation, so it is expressed structurally instead: two directories each contain a file of the same name but with different content, and each has a symlink pointing at it through the same relative target. Those two links must hash *equally* (proving the target's content is irrelevant) while a third link with a different target must hash *differently* (proving the target itself is what counts). Both of those assertions fail against the pre-#97507 behaviour, as does the symlink-to-directory case. The remaining cases cover a dangling link and a link whose target leaves the filesystem root — both still hashable, since they are still links — and assert that every distinct link hashes distinctly, so nothing collapses into one shared "is a symlink" hash. The two assertions covering directories and missing paths document today's hard-error behaviour and carry a comment pointing at the open question already noted in `hash_file` about whether those should return `None` instead, so it is clear which assertions to revisit if that changes. ### Verification - `cargo test -p turbo-tasks-fs` (125 tests) - `cargo clippy -p turbo-tasks-fs --all-targets`, `cargo check -p turbo-tasks-fs --all-targets` - Confirmed the test actually guards the regression: reverting the symlink arm of `hash_file` to `read().hash()` makes it fail with the original `Is a directory (os error 21)` error, and it passes again once restored. <!-- 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> |
||
|
|
80576037a9 | v16.4.0-canary.3 v16.4.0-canary.3 | ||
|
|
27a8eb0999 |
[devtools] Fix indicator dragging on touch screens (#97723)
### What? - Restore `touch-action: none` on the draggable devtools indicator. - Add integration coverage for the draggable computed touch action. ### Why? PR #86816 made devtools content selectable, but also removed `touch-action: none` with the selection styles. On touch screens, that allows the browser to claim the gesture for viewport panning and suppress the pointer event stream, causing the indicator to stop moving. ### How? Keep the drag-lifecycle selection handling introduced by #86816 and restore only `touch-action: none` before the gesture begins. Fixes #97668 ### Testing - `pnpm build --filter next` - Targeted Prettier and ESLint checks - `NEXT_SKIP_ISOLATE=1 pnpm test-dev test/development/app-dir/devtools-position/default-position.test.ts` |
||
|
|
cb463843e7 | v16.4.0-canary.2 v16.4.0-canary.2 | ||
|
|
d898c8f590 |
introduce an options struct for constructing backend storage (#97284)
Improve legibility a bit, requested by @bgw here https://github.com/vercel/next.js/pull/95976#discussion_r3762093713 |
||
|
|
74a30b10ac | v16.4.0-canary.1 v16.4.0-canary.1 | ||
|
|
e694c08b6f |
docs: fix parallelRouteKey parameter name in useSelectedLayoutSegment(s) (#97710)
The `useSelectedLayoutSegment` and `useSelectedLayoutSegments` hooks accept a parameter named `parallelRouteKey` (singular), as declared in `packages/next/src/client/components/navigation.ts`: export function useSelectedLayoutSegment(parallelRouteKey: string = 'children') The docs wrote it as `parallelRoutesKey` (plural) in three files, which doesn't match the generated `navigation.d.ts` type that editors surface via IntelliSense: - `use-selected-layout-segment.mdx` — parameter signature and description - `use-selected-layout-segments.mdx` — parameter signature and description - `parallel-routes.mdx` — "With useSelectedLayoutSegment(s)" section Docs-only change; no behavior change. --- Credit: This contribution was originally authored by @see-stack in #97690 and is recreated here with signed commits. Co-authored-by: Said Nasser <sdnssr001@gmail.com> |
||
|
|
49bfb6506a |
Turbopack: Avoid cloning paths in fs watcher if the path is already in a map (#97655)
This is a spiritual continuation of https://github.com/vercel/next.js/pull/96114 . See https://github.com/vercel/next.js/pull/94735 for motivation. This codepath should already be pretty cheap, but in some cases (notably inotify on Linux where updates aren't debounced as aggressively by the OS) it could become somewhat hot and waste CPU while other somewhat-CPU-heavy work is happening in other software (e.g. `pnpm`). This removes two more sources of clones: - If we only have a `&Path`, but it's already in the `paths` map, we can use the raw entry API to avoid cloning it. - `new_paths` can use `usize` indexes into the `FxIndexMap` instead of storing another clone of the path. We could also encode this information as flags on `path`, but the iteration over `new_paths` must happen as a separate loop before the normal batch execution loop (near the bottom of `DiskWatcher::watch_thread`), so I'd like to keep it as a separate data structure to avoid having to filter out a bunch of unrelated keys without the flag when finally processing the batch. |
||
|
|
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 |