Skip to content

[pull] canary from vercel:canary - #1346

Merged
pull[bot] merged 9 commits into
code:canaryfrom
vercel:canary
Aug 28, 2026
Merged

[pull] canary from vercel:canary#1346
pull[bot] merged 9 commits into
code:canaryfrom
vercel:canary

Conversation

@pull

@pull pull Bot commented Aug 28, 2026

Copy link
Copy Markdown

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 : )

aurorascharff and others added 9 commits August 27, 2026 19:46
…ion (#97712)

## What

- Documents how legacy `prefetch={true}` behavior changes with Partial
Prefetching and how to choose the UI worth preserving.
- Updates the adoption skill to capture a passing flag-off `instant()`
baseline, use the same assertions to drive the migration, and retain
them as regression coverage.
- Adds a reusable production test rig, keeps additional per-link
optimization separate from adoption, and surfaces `prefetch={false}`
links that may no longer be needed.
- Applies the shared test-rig fixes discovered during dogfooding to the
Cache Components optimizer.

## Why

Legacy `prefetch={true}` could include uncached dynamic content. With
Partial Prefetching, it includes the shared App Shell and cached
URL-specific content instead. A passing build or clean insight sweep
does not prove that the intended prefetched UI survived the migration,
so the skill now records that contract before adoption and verifies it
unchanged afterward. The guide presents the same test workflow as an
optional programmatic check for manual adopters.

## Testing

- Prettier, Alex, and skill frontmatter validation
- Dogfooded on v0 in vercel/v0#28842
- Partial Prefetching adoption eval: 6/6 in
#97759
## 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](#47872) introduced the
404 fallback for unmatched parallel slots,
[#60186](#60186) added a
development warning because this was confusing in practice and linked
[#51805](#51805) and
[#49569](#49569), and
[#84702](#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 -->
The class names were unnecessarily long.

Use the same as the lightningcss default:
`[hash]_[local]` which is
`<hash of the full file path>_<original class name or identifier>`

Keep the previous longer mechanism to aid in debugging in dev

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
## Summary

- mark the deploy-release-test repository skill as internal
- prevent public skill discovery and telemetry publication for this
repo-only workflow

## Testing

- validated the SKILL.md frontmatter parses with metadata.internal set
to true
- git diff --check

Co-authored-by: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com>
When initializing a new project with create-next-app, the interactive
prompt will now ask the user if they want to enable Cache Components.

For now the option defaults to off. We'll flip the default back on once
there's a flow that guides users to the right recommendation based on
the kind of site they're building. The option can still be enabled
interactively, or with the `--cache-components` flag.

If the user chooses to enable Cache Components, the project will also be
initialized with Partial Prefetching enabled. There's no separate prompt
for Partial Prefetching; the only reason they are separate configs is
because Cache Components shipped in 16.0 before Partial Prefetching
existed.

This is not a breaking change because it does not change any behavior in
Next.js itself, only the create-next-app CLI. It has no impact on any
existing Next.js projects.
## Summary

Builds on #97591. Webpack currently removes most of `.next` on devserver
startup. We can remove more as well.

This keeps the age-based removal introduced in #97591 and expands it to
`traces`, logs, etc. However, for any other directory that is not
`cache` or `lock`, completely remove it on startup. This includes types,
etc.

Apply age-based deletion to Turbopack-owned output that can be restored
from cache or recomputed, including route entries, chunks, assets,
service workers, middleware, and instrumentation. Completely remove
other dev output so logs, traces, generated types, diagnostics, and
future framework-owned output cannot be left as mixed generations.

Use exact-path exclusion sets in `recursiveDeleteSyncWithAsyncRetries`.
The complete pass retains `cache`, `lock`, and Turbopack-owned paths;
the age-based pass then sweeps those Turbopack paths. Both passes finish
before project creation.

## Verification

- `pnpm test-unit test/unit/recursive-delete.test.ts`
- `pnpm --filter=next types`

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com>
Agent evals, skills, and the eval runner do not affect the framework
runtime, but they currently make `Determine changes` schedule the full
Rust, native, deployment, and framework test matrices.

Classify changes confined to `evals/`, `skills/`, and `run-evals.js`
with the existing lightweight documentation path. The always-on build
and lint checks still run.

Validated with Node syntax checking, Prettier, ESLint, and `git diff
--check`.
### What?

Upgrade Turbopack's workspace dependencies to DashMap 7.0.0-rc2 and
Hashbrown 0.15.4, removing Hashbrown 0.14 from the `turbo-tasks-backend`
dependency graph.

### Why?

`turbo-tasks-backend` previously compiled two Hashbrown versions because
its custom DashMap helpers shared Hashbrown 0.14's private raw table
types. Converging the backend graph on Hashbrown 0.15 removes that
duplicate backend codegen path and slightly reduces the shipped native
addon's size.

A clean release A/B of the unstripped x86-64 `next-napi-bindings` cdylib
measured:

| Revision | `libnext_napi_bindings.so` |
| --- | ---: |
| canary base `80576037` | 147,824,920 bytes |
| PR | 147,818,576 bytes |
| **Delta** | **−6,344 bytes (−0.0043%)** |

The final gain is small because unrelated SWC dependencies still pull
DashMap 6 / Hashbrown 0.14 into the same addon; this change removes only
backend-specific Hashbrown 0.14 codegen. Plus... nearly everything is
monomorphized anyway

### How?

DashMap 7 replaces its Hashbrown 0.14 raw table with Hashbrown 0.15's
public `HashTable` API. The backend helpers now use that public API
while retaining their existing shard locking and allocation behavior:

- heterogeneous cache lookup and insertion still reuse a pre-located
shard and precomputed hash;
- the entry helper passes Hashbrown's native `hash_table::Entry` into a
closure, preserving precise lock-release timing without a custom
self-referential entry type or cloned hasher;
- nonblocking removal still avoids lock-order deadlocks;
- two-key mutation uses a release-mode disjointness assertion followed
by `get_many_unchecked_mut` to avoid a redundant duplicate-pointer scan;
- snapshot draining still transfers ownership of each shard table and
releases task memory incrementally;
- custom mutable guards remain non-`Send`.

The migration also replaces several raw-pointer iteration and removal
paths with safe `HashTable` operations. Existing raw-entry users in
`auto-hash-map` and `turbopack-trace-server` explicitly enable Hashbrown
0.15's compatibility feature.

### Performance

Performance testing with the overhead.rs benchmark revealed no
regression/progression
<!-- NEXT_JS_LLM -->


<!-- fleet 53ff0bed-46a9-4361-befa-a1791d585f27 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
@pull pull Bot locked and limited conversation to collaborators Aug 28, 2026
@pull pull Bot added the ⤵️ pull label Aug 28, 2026
@pull
pull Bot merged commit ee3a50c into code:canary Aug 28, 2026
4 of 6 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants