[pull] canary from vercel:canary - #1345
Merged
Merged
Conversation
### What? Make the production test harness wait for a build process's piped stdio to close before `NextStartInstance.start()` continues to launch the server or reports a build failure. ### Why? A child process's `exit` event can fire before trailing stdout and stderr have been delivered to the parent. This can make start-mode tests intermittently miss diagnostics emitted near the end of a production build, even though the build itself reported them correctly. The standalone `build()` helper already uses the later `close` lifecycle event to avoid this race. Applying the same rule to the build phase inside `start()` removes the equivalent latent output-drain hazard and makes child-process handling consistent across the harness. ### How? Resolve the build phase on the child process's `close` event. Unlike `exit`, `close` is emitted after piped stdio closes, so all trailing build output has reached the harness before it starts the server or rejects the build. Exit-code and signal handling remain unchanged. ### Verification - `pnpm types` - `pnpm test-start-webpack test/production/production-build-dir/production-build-dir.test.ts` - `pnpm test-start-webpack test/production/app-dir/sync-io-blocks-root/sync-io-blocks-root.test.ts` - Repeated webpack stress campaign for the sync-IO suite: 5/5 runs, including 20/20 `--debug-prerender` route cases - Prettier and ESLint on `test/lib/next-modes/next-start.ts` <!-- NEXT_JS_LLM --> <!-- fleet 821362ed-9f96-4e53-b56e-49bf00e878b9 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Something made this test very flaky on Aug 24/Aug 25 https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.status%3A%22fail%22%20%40test.suite%3A%22sync%20IO%20that%20blocks%20the%20root%22%20-%40test.type%3Aturbopack&agg_m=count&agg_m_source=base&agg_t=count&fromUser=false&index=citest&start=1787237730805&end=1787842530805&paused=true Disabling until we can investigate.
## 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 -->
This PR improves the meta descriptions on certain pages. <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors We encourage you to use AI to assist you in researching, creating, and reviewing changes. However, you must review and deeply understand the contributions you are making. For this reason, **pull request descriptions from external contributors must be written by a human**. ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Signed commits - This repository requires verified commit signatures on protected branches. - If this pull request is blocked for unsigned commits, re-sign the commits and force-push the branch. - A `Signed-off-by` line in the commit message is not enough. ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # -->
Fixes #97934. The default use cache handler uses live ReadableStream objects. Those streams retain references to the request’s async context and closed HTTP response, causing memory to grow with cached entries and potentially produce OOMs. The fix converts each stream into a plain Buffer before caching it. Every cache hit then gets a fresh temporary stream created from those bytes. The content remains cached, but completed requests can now be garbage-collected. --- Basically - the LRU cache stores live ReadableStream objects, which keep completed request contexts and closed responses in memory until that cache is evicted.
Closes #97766. ### What? Widen the chunk ident hash in `turbopack-core/src/ident.rs` from 7 to 13 base38 characters. ### Why? 7 base38 characters is ≈36 bits nominally, and closer to ~32 in practice because the leading character is bounded — an effective space of roughly 6×10⁹. Any population of chunks that shares a readable name prefix and is distinguished **only** by that hash is therefore a birthday problem: at n names the collision probability is ≈ n²/2M, so around 10⁵ such chunks collide with probability of order 1 and the build aborts with: ``` Two or more assets with different content were emitted to the same output path ``` This is not hypothetical at real app sizes: - #97765 — deterministic reproduction (this PR fixes it): 120,000 chunks sharing one prefix, fails byte-identically on every run, same collided path and same rejected content hashes across consecutive builds. - #96976 — production App Router app, ~69,000 emitted server chunks, ≈9% failure probability per build measured, growing quadratically with chunk count. The ergonomics are the painful part: the colliding pair is chosen by the hash, not by any structural property. The failure lands on whichever branch last touched the module graph and looks like that branch's fault; bisecting finds a commit but never a responsible file; and moving a module only reshuffles which pair collides. We verified that a module renamed *inside* a colliding chunk does not change the chunk's name at all — same two chunks, same 7-character name, renamed file listed inside. ### How? ```diff let hash = encode_base38(hasher.finish()); - // 7 base38 chars ≈ 36 bits of collision resistance - let truncated_hash = &hash[..7]; - write!(name, "_{truncated_hash}")?; + // Use the complete fixed-width base38 encoding of the 64-bit hash. + // + // This suffix is what distinguishes idents that share a readable + // name prefix, so truncating it makes distinct output paths collide + // for large module graphs, failing the build with "Two or more + // assets with different content were emitted to the same output + // path". Keeping all 64 bits removes that failure mode, at the cost + // of a slightly longer file name. + write!(name, "_{hash}")?; ``` The complete 13-character base38 encoding preserves all 64 bits of the identity hash. Cost: 6 extra characters per emitted chunk filename. ### Notes for reviewers - Output filenames change wholesale on upgrade, as with any naming change; nothing reads these names across versions. - Snapshot tests that pin emitted chunk names have been regenerated. - If you would rather solve this by hashing more distinguishing input rather than widening the truncation, I am glad to rework it; the length is the smallest change that removes the failure mode. <!-- NEXT_JS_LLM --> <!-- fleet daf6a1b0-ebda-4eed-a89f-03e5ec7dc357 --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: jonnyuniverse <jon@fractionalventure.partners> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
## 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 -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )