Skip to content

[pull] canary from vercel:canary#878

Merged
pull[bot] merged 13 commits intocode:canaryfrom
vercel:canary
Mar 14, 2026
Merged

[pull] canary from vercel:canary#878
pull[bot] merged 13 commits intocode:canaryfrom
vercel:canary

Conversation

@pull
Copy link

@pull pull bot commented Mar 14, 2026

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

eps1lon and others added 13 commits March 13, 2026 21:11
Writing skills for guides and API reference for more consistent
formatting.

- `write-guide` - this skill helps create guides with progressive
disclosure and a consistent format (intro, summary, step-by-step
instructions, next steps). Works best when you give agents a demo.
- `write-api-reference`  - this skill looks at the API type (component,
function, file, etc) and helps draft references following a consistent
format. Works best when you give agents context. e.g. source code, PR
descriptions, RFCs. Also explores repo context (e.g. e2e tests)


Note:

- While skills help you write a first draft, they’re 80%. Humans still
need to check for accuracy, test APIs, and take it to 110%.
- If no technical writing guidance is provided, skills work best when
you give it references of past work.

Closes:

- https://linear.app/vercel/issue/DOC-6245/create-guide-skill
- https://linear.app/vercel/issue/DOC-6246/create-api-reference-skill

---------

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
)

### What?

Switch Turbopack's hash encoding for chunk and asset output filenames
from hexadecimal (base16) to base40, using the alphabet \`0-9 a-z _ - ~
.\`. Version hashes (used for HMR update comparison, not filenames) use
base64 instead.

### Why?

Base40 encodes the same number of bits in fewer characters than hex,
producing shorter output filenames. All 40 characters are RFC 3986
unreserved (URL-safe) and safe on case-insensitive filesystems (macOS
HFS+/APFS, Windows NTFS).

Hash truncation lengths are reduced proportionally to maintain
equivalent collision resistance:

| Context | Before (hex) | After (base40) | Entropy |
|---|---|---|---|
| Content hash in chunk filenames | 16 chars | 13 chars | ~69 bits |
| Content hash in asset filenames | 8 chars | 13 chars | ~69 bits |
| Ident disambiguator hash | 8 chars | 7 chars | ~37 bits |
| Long-path prefix hash | 5 chars | 4 chars | ~21 bits |

### How?

**New encoding module** (\`turbo-tasks-hash/src/base40.rs\`):
- Defines the base40 alphabet and length constants (\`BASE40_LEN_64 =
13\`, \`BASE40_LEN_128 = 25\`)
- Implements a generic \`encode_base40_fixed<N>\` helper to avoid
duplication
- Public API: \`encode_base40(u64) -> String\` and
\`encode_base40_128(u128) -> String\`

**New base64 encoding** (\`turbo-tasks-hash/src/base64.rs\`):
- \`encode_base64(u64) -> String\` — 11-char base64 (no padding) for
version hashes
- Version hashes don't appear in URLs or filenames, so base64 is safe
and shorter

**New \`HashAlgorithm\` variants** (\`turbo-tasks-hash/src/lib.rs\`):
- \`Xxh3Hash64Base40\` and \`Xxh3Hash128Base40\` added alongside
existing hex variants
- Existing hex variants kept for internal manifests and identifiers

**\`ContentHashing\` moved to \`turbopack-core\`**:
- Moved from \`turbopack-browser\` to
\`turbopack-core/src/chunk/mod.rs\` so both \`BrowserChunkingContext\`
and \`NodeJsChunkingContext\` can use it

**Separate chunk vs asset content hashing**:
- \`BrowserChunkingContext\`: \`content_hashing\` renamed to
\`chunk_content_hashing\` (optional), new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- \`NodeJsChunkingContext\`: new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- Builder methods: \`use_content_hashing()\` renamed to
\`chunk_content_hashing()\`, new \`asset_content_hashing()\`

**Version hashes switched to base64**:
- \`turbopack-nodejs/src/ecmascript/node/version.rs\`
- \`turbopack-dev-server/src/html.rs\`
- \`turbopack-browser/src/ecmascript/version.rs\`,
\`merged/version.rs\`, \`list/version.rs\`

**Other callers updated** (15 files across turbopack and next-core):
- All chunk/asset content hashing switched from \`Xxh3Hash128Hex\` →
\`Xxh3Hash128Base40\`
- \`ContentHashing::Direct { length }\` reduced from 16 → 13
- Asset path truncations use full 13-char base40 hash (matching chunk
filenames)

**Exception — \`wasm_edge_var_name\`** (\`turbopack-wasm/src/lib.rs\`):
- Kept as \`Xxh3Hash128Hex\` because the hash is used as part of a
JavaScript variable name (\`wasm_{hash}\`), and base40 characters \`-\`,
\`~\`, \`.\` are not valid JS identifier characters.

**Scope — NOT changed:**
- Webpack configuration (unchanged)
- Internal manifests (\`routes_hashes_manifest\`,
\`project_asset_hashes_manifest\`)
- Internal identifiers (font naming, external module hashing, data URI
sources, debug IDs)
- SRI hashes (SHA-based Base64, different purpose)

---------

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
1. This is misldeading in the bundle analyzer view
2. If you have a Nextjs app consisting of only API routes, then there is
no need to ever write out any polyfill chunk.
…91188)

Adds a dedicated test suite that asserts on the errors thrown when `cookies`, `headers`, `connection`, `draftMode`, and `next/root-params` are called inside `generateStaticParams`. Currently, these all fail because `generateStaticParams` runs without a work unit store.

Each route is built individually using `--debug-build-paths` so we can capture and assert on the error for each API independently.
This auto-generated PR updates the production integration test manifest used when testing Rspack.
This auto-generated PR updates the development integration test manifest used when testing Rspack.
…e-stats.json (#90949)

After a successful, Turbopack production build, this writes
`.next/diagnostics/route-bundle-stats.json`
containing First Load JS stats for every route. 

## Why?

We used to have route bundle size statistics print after every build. It
was not very high-signal and could be misleading, often using heuristics
to give a best-effort estimate of what sizes may be at runtime. **These
kinds of metrics for route bundle sizes can be misleading, as pages can
be slow to visual/interactive completeness even if they have small first
load bytes.**

However, a minimal gauge of how much JavaScript a route ships during
initial page load can be a useful signal so long as users have
additional context and awareness of their app structure. Let's provide
just this number for now for those that could benefit.

Additionally, this does so in the form of writing a json document to the
build directory, rather than writing a table to stdout, where users have
needed to write extra tools to parse.

## Format

An array of route entries sorted descending by
`firstLoadUncompressedJsBytes`:

```json
[
  {
    "route": "/blog/[slug]",
    "firstLoadUncompressedJsBytes": 124928,
    "firstLoadChunkPaths": [
      ".next/static/chunks/framework.js",
      ".next/static/chunks/main.js",
      ".next/static/chunks/pages/blog/[slug].js"
    ]
  }
]
```

`firstLoadUncompressedJsBytes`: sum of uncompressed sizes of all JS
chunks loaded on first navigation to the route (shared chunks included),
for both Pages Router and App Router
`firstLoadChunkPaths`: paths to those chunks relative to the project
root

## Test Plan
Added an integration test. Built with a create-next-app and verified the
json document is written and the numbers align with what Chrome reports
during initial load of a page.
@wbinnssmith added these scripts a long time ago at my suggestion/urging, but I think they ended up bad for a couple reasons that were hard to foresee at the time:

- afsctool operates in-place and doesn't atomically write the compressed file, so if the process gets interrupted, your `target` directory is corrupted. https://github.com/Dr-Emann/applesauce is better for this reason.
- This doesn't acquire the cargo lock, and modifying files while cargo is running is a good way to get corruption of the `target` directory.

https://github.com/bgw/cargo-apfs-compress is my latest attempt at solving this, though I don't have a `LaunchAgents` config for it.  
  
Regarding `node_modules`: pnpm creates reflinks from a shared global store on apfs. Trying to compress these reflinked files is just going to hurt you because it'll break the data deduplication that would've otherwise happened.
…arams (#91158)

Partial fallback upgrading is currently too broad. Any PPR fallback shell can be treated as upgradeable, including routes with no `generateStaticParams` and shells that are already as specific as prerendering can make them.

In practice, that means we preserve `allowQuery` and enable background promotion for shells that should remain as partial fallbacks. That broadens the feature beyond the medium-cardinality case it was meant for and makes the build output imply per-param upgradeability when there are no prerenderable params left to fill.

This PR teaches static-path generation to compute `remainingPrerenderableParams` for each fallback shell: the unresolved pathname params that can still be filled by `generateStaticParams` in that branch. We then use that signal to gate partial fallback behavior.

With this change:

- `partialFallback` is only emitted for shells that still have prerenderable params remaining
- `allowQuery` with params is only preserved for those shells

Effectively, this limits partial fallback upgrading to shells that can still be made more specific by prerendering.
saw the issue about the _document.tsx example not working. next/font
cant be used in _document.tsx, it throws an error. removed that section
from the docs since _app.tsx already has the correct example.

fixes #88832

Co-authored-by: Kahfi Elhady <kahfie@gmail.com>
Co-authored-by: Joseph <joseph.chamochumbi@vercel.com>
…91231)

In #91158, I wired up the plumbing to indicate whether a partial
fallback shell is upgradeable. The previous heuristic was too coarse.
This wires up the handling in `next start`.

This PR teaches the runtime to complete a fallback shell only as far as
prerendering allows. Instead of promoting a generic shell like
`/[one]/[two]` directly to `/c/foo`, it promotes it to the most specific
prerendered shell for that branch, `/c/[two]`, where `[one]` has
`generateStaticParams`. That means later requests can reuse the more
complete shell, while fully dynamic params continue to stream as dynamic
content.

With this change:

- the first request still serves the generic fallback shell immediately
- the background revalidate writes a more specific shell, not a fully
concrete route
- later requests for the same prerendered branch reuse that shell
- upgrading stops once only fully dynamic params remain

This keeps the runtime behavior aligned with the build-time
`remainingPrerenderableParams` contract from the downstack PR.
@pull pull bot locked and limited conversation to collaborators Mar 14, 2026
@pull pull bot added the ⤵️ pull label Mar 14, 2026
@pull pull bot merged commit ae3f9f5 into code:canary Mar 14, 2026
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.