## Summary
- skip the redundant aggregation-root update when
`ConnectChildOperation` reconnects an already constructed top-level task
that is already an aggregation root
- avoid entering the aggregation operation loop when no update was
queued
The fast path is intentionally narrow. It applies only when the
connection has no parent, the task has materialized output, and the
task's aggregation number is already the root sentinel. The task may
still be dirty and need to run; this only skips an idempotent
aggregation update. A task that is not yet a root still goes through the
normal update on its first top-level connection. Tasks without output
are still scheduled, parented connections retain their activeness and
aggregation updates, and transient-state boundaries are unchanged.
## Findings
The earlier scheduler experiment has been removed completely.
The apparent benefit from keeping a worker alive with
`std::thread::yield_now()` was specific to the benchmark topology:
Criterion's `Runtime::block_on` caller runs outside the single-worker
Tokio runtime, so that external caller can enqueue the next root task
while the runtime worker is yielding at the OS level. The sole Tokio
worker itself cannot produce work during that loop. In normal
scheduling, tasks typically enqueue their children before the current
job exits, and a multi-worker runtime can start another worker when it
is below its target. That does not justify burning CPU in an idle
worker, nor replacing the loop with an async-yield retry loop.
The retained optimization is independent of that artifact. An already
constructed top-level task that is already rooted has no new root update
to perform; re-enqueuing the idempotent update only forces the operation
executor through unnecessary suspend points.
This PR now contains no changes to `priority_runner`, worker lifetime,
busy waiting, OS-thread yielding, async yielding, HMR, filesystem
watching, or documentation.
## Benchmark results
The benchmark source, runtime construction, profiles, allocator,
toolchain, and worker count were unchanged. Frozen baseline and
candidate executables were run adjacently in alternating order. Three
focused pairs met the control-drift threshold; one pair was excluded
before analysis because its Tokio control moved by more than 2%.
Median candidate changes across the valid pairs:
| Case | Change |
| --- | ---: |
| direct control | +0.071% |
| Tokio control | -0.177% |
| uncached task | +1.266% |
| cached, same key | -37.868% |
| cached, different keys | -26.783% |
The complete all-duration local sweeps did not meet the control
thresholds, so their task measurements are deliberately not cited. Fresh
remote CodeSpeed and Stats results are the authoritative guardrail for
the full benchmark.
## Verification
- `cargo fmt --all -- --check`
- `cargo clippy -p turbo-tasks-backend --all-targets -- -D warnings -A
deprecated`
- `cargo test -p turbo-tasks-backend`
- `pnpm build-all`
- two-engine autoreview after the final changes, with no findings
- Not run: `pnpm --filter=next dev` (the final diff is Rust-only and was
covered by the full bootstrap build and Cargo suites)
This PR introduces two new things: a pass of the analyzer that
recognises exports from CJS modules (in the form of `exports.foo`, see
`analyze_cjs_exports`) and then a CodeGen that reads `export_usage_info`
to remove CJS exports that are not used.
This PR does not add anything additionally to `export_usage_info`, so
this means that we only trim from modules that are imported with `import
{ foo } from "bar"`. The CodeGen depends on the output of
`analyze_cjs_exports` for the locations of exports, whether it is safe
to drop exports (its pretty conservative), and also whether `__esModule`
is present.
Noticed while reading through the symlink code.
The symlink logic had some clever `ErrorKind::AlreadyExists` logic that
folded the retry into the main retry loop, but we still had a separate
retry loop for after handling `ErrorKind::NotFound`. This folds the
loops together.
I hand-implemented the symlink version of this and had opus port it to
the non-symlink version.
## Summary
Run all Rust workspace doctests as part of the existing Turbo-cached
cargo unit test task so future documentation regressions fail CI without
adding an uncached workspace build.
## Verification
- `cargo test --workspace --doc --profile=release-with-assertions
--no-fail-fast`
- Not run: `pnpm --filter=@next/swc test-cargo-unit` (`cargo-nextest` is
installed in CI but unavailable locally; the new doctest suffix passed
independently)
<!-- NEXT_JS_LLM -->
## Summary
<img width="1304" height="1546" alt="CleanShot 2026-07-19 at 01 07
57@2x"
src="https://github.com/user-attachments/assets/b9a6a7c9-dc95-42d5-9cce-f29e6d869d92"
/>
Adds a "Set up your editor" section to the App Router installation
guide. The App Router names files by convention (`page.tsx`,
`layout.tsx`, `route.ts`), so an editor quickly fills with same-named
tabs. This section shows how to tell them apart with VS Code / Cursor
custom editor labels, labeled two folders deep so dynamic routes like
`blog/[id]/page.tsx` do not all collapse to the same `[id]` label. It
includes:
- A copy-paste `.vscode/settings.json` config
(`workbench.editor.customLabels.patterns`) for setting it up by hand.
- A copyable prompt that points a coding agent back at this section to
apply the config, including adapting it to other editors.
- A good-to-know that JetBrains IDEs (WebStorm, IntelliJ) do this
automatically.
## Verification
- Previewed locally against the docs site: the section renders (HTTP
200), with the config block, the prompt card, and the good-to-know all
rendering correctly.
<!-- NEXT_JS_LLM_PR -->
### What?
Fix the `cms-contentful` example to use the async APIs introduced in
Next.js 15:
- Await `draftMode()` in `app/page.tsx`, `app/posts/[slug]/page.tsx`,
and
`app/api/disable-draft/route.ts`
- Update `params` type to `Promise<{ slug: string }>` and await it in
`app/posts/[slug]/page.tsx`
### Why?
In Next.js 15, `draftMode()` from `next/headers` became async and must
be awaited.
Page `params` are also now typed as a `Promise`. The example was using
the old
synchronous patterns, causing the build to fail and the app to not work
when running against the latest
version of Next.js -- this example project uses latest version of
next.js in the package.json.
### How?
Awaited `draftMode()` at each call site and updated the `params` type
signature to
`Promise<{ slug: string }>` with a corresponding `await`.
Closes NEXT-
Fixes #
#94877 removed the embedded plugin marketplace because its plugin
carried copies of skills that drifted from `skills/`. This re-adds
plugin packaging without duplicating any content: `skills/` itself is
the plugin root. `skills/.claude-plugin/plugin.json` sets `"skills":
["./"]` so the four skills at `skills/*/SKILL.md` are discovered in
place, and the root `.claude-plugin/marketplace.json` points its single
entry at `./skills`. External catalogs can reference the plugin with a
`git-subdir` source (`path: "skills"`), which sparse-clones only that
directory instead of the whole repo.
`claude plugin validate` passes on both manifests. `claude --plugin-dir
./skills` discovers all four skills
(`nextjs:next-cache-components-adoption`,
`nextjs:next-cache-components-optimizer`, `nextjs:next-dev-loop`,
`nextjs:next-partial-prefetching-adoption`).
The `Stats (webpack)` / `Stats (turbopack)` CI jobs have been
intermittently failing on PRs and canary (e.g. [this
run](https://github.com/vercel/next.js/actions/runs/29476089144/job/87563318723)).
The jobs run on arm64 runners, where the `ubuntu:24.04` base image
fetches apt packages from `ports.ubuntu.com`, which is intermittently
unreachable. Since `apt-get update` only warns when index fetches fail,
the Docker build breaks one layer later at `apt-get install` with
"Unable to locate package curl".
This change switches the base image to `buildpack-deps:noble-scm`, which
already ships curl, ca-certificates, and git, so the build no longer
makes any apt calls at all. The `apt-get upgrade` step is dropped along
with it; it was already a silent no-op whenever the mirror was down, and
the ephemeral CI container does not rely on it.
Both `.github/next-stats-action.Dockerfile` (the one CI builds via
`action.yml`) and the duplicate
`.github/actions/next-stats-action/Dockerfile` are updated to stay in
sync.
A back/forward (history traversal) navigation should not be captured by
the Nav Inspector, because it's not the same as a regular navigation: it
reads the entire navigation from cache, rather than fetching new data.
It might be the case that data in the bfcache is missing for some
reason, but in the normal case all the data will be available and the
navigation will be instant. So that's how we should model it in the Nav
Inspector.
In the future we may want to update the Nav Inspector UI to show that
the current screen was the result of a back/forward navigation, but for
now we will just reset the Nav Inspector back to the pending ("Waiting
for navigation...") state.
Includes the regression tests from #95793.
Co-authored-by: Dan Abramov <[email protected]>
### What
The generated `nextjs-agent-rules` block in `AGENTS.md` glued the `<!--
BEGIN:nextjs-agent-rules -->` / `<!-- END:nextjs-agent-rules -->`
comment markers directly against the `#` heading and the surrounding
paragraphs:
```md
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
...
<!-- END:nextjs-agent-rules -->
```
Markdown formatters (Prettier, and CommonMark tooling in general) treat
block-level HTML comments as needing surrounding blank lines, so
formatting an `AGENTS.md` reflows the block:
```md
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
...
<!-- END:nextjs-agent-rules -->
```
On the next `next dev`, the generator detects the block as non-current
and rewrites it back to the glued form. For any project that runs
Prettier over `AGENTS.md`, this produces churn on every commit — the
opposite of what the block's own "commit it to keep the tree clean" note
promises.
### Change
Emit blank lines around the markers so the generated block is
formatter-stable. Both generators are updated together:
- `packages/next/src/server/lib/generate-agent-files.ts` (`next dev`
writer)
- `packages/create-next-app/helpers/generate-agent-files.ts`
The `next-codemod` `agents-md` refresh delegates to the installed
package's generator, so it needs no change. The docs sample in
`ai-agents.mdx` and this repo's own `packages/next/AGENTS.md` already
use the blank-line shape, so they now match what's generated.
### Notes
- Existing projects get a one-time block rewrite on their next `next
dev` (expected — the block is described as re-added by `next dev`),
after which it stays stable under formatting.
- The `agent-rules-auto-generate` test asserts against the live
generator output, so it tracks the new shape automatically.
With the Instant Navigation Testing lock held (the Navigation Inspector
paused), repeated client navigations between pages that share a layout
could sometimes get stuck without ever resolving.
The root cause turned out to be a subtle problem with the client
router's use of `useDeferredValue` to switch between the cached UI and
the final UI. The mechanism only works as we intend when mounting a new
part of the tree. When updating an existing part of the tree, like a
shared layout, React will skip the cached UI entirely.
We were already aware of this issue with the `useDeferredValue`
approach. The plan is to instead model cached navigation as an
optimistic update followed by a transition update. Before we can make
such a change, though, we need to make a change in React to allow
optimistic updates to suspend (currently, optimistic updates act like
sync updates, and can trigger existing Suspense boundaries to switch
back to their fallback state).
To workaround this issue when using the Navigation Inspector, we're
going to change how the inspector works when multiple navigations are
performed during the same "lock". Instead of keeping the entire sequence
blocked until the end, we will instead only block the most recent one.
This more closely matches what the inspector UI implies, anyway: it
doesn't show a stack of pending navigations, only the most recent one.
This doesn't absolve us from needing to switch to using an optimistic
update. That's needed regardless to address similar behavior in
production. But this sidesteps the issue for now, and we were planning
to make this change to the Navigation Inspector regardless.
Co-authored-by: Sam Selikoff <[email protected]>
The experimental `appShells` flag was added while the feature was being
developed. It was not meant to be a public-facing flag.
Some of the behavior that was gated behind the `appShells` flag is
purely an optimization and can be landed without any gate.
There are also some behaviors that we don't want to turn on unless
you've opted into the new prefetching model via Partial Prefetching.
Arguably these too are internal optimizations, but because they can
change the number of requests that are made to the server (e.g. one
request for the shell, another for the page data), we will gate these
behind Partial Prefetching to minimize the impact on existing
applications.
The result is that until you opt into Partial Prefetching, you should
not see any meaningfully detrimental impact to prefetching costs,
regardless of how your app is structured.
## Summary
Fix Request Insights collection so the dev server records all non-hidden
Next.js spans locally, including non-vanilla trace and wrapped spans,
while preserving the existing OpenTelemetry export allowlist unless
verbose tracing is enabled.
Keep reporting timelines correct by propagating request identity before
work async storage exists, honoring explicit span timestamps, and
anchoring request duration to the BaseServer request span. Standardize
span categories on `next.span_category`, retain presentation-only UI
filtering, and filter internal request identity headers at ingress.
## Verification
- `pnpm test-dev-turbo
test/development/app-dir/request-insights/request-insights.test.ts`
<!-- NEXT_JS_LLM -->
This is a small performance thing Claude pointed out that I've added:
```
// Only the pages-router `route-loader` reads `__TURBOPACK_PAGE_BOOTSTRAP`. App routes
// navigate via flight and never use it, so skip app entries to keep `_buildManifest.js`
// (loaded on every page) small.
```
This PR builds on top of the other PRs in this stack, it is the PR that
switches to inlining the bootstrap code and dropping the runtime file
that each route had.
We used to generate the runtime file for each `ChunkGroupResult` in
`evaluated_chunk_group()`, I've added `chunk_group_bootstrap_params`
which are the parameters that were unique to each chunk group. These are
inlined into each page's HTML in
https://github.com/vercel/next.js/pull/94666. They look like:
```javascript
{"otherChunks":["output/0_9x_turbopack-tests_tests_snapshot_basic-tree-shake_export-namespace_input_1mqgz2-._.js"],
"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/basic-tree-shake/export-namespace/input/index.js [test] (ecmascript)"]}
```
Whether or not the inline-able code is generated is based on the
`inline_chunk_group_bootstrap` which is still false in all cases at the
moment but will be turned to true in
https://github.com/vercel/next.js/pull/94666.
This method generates the params for this code (random example for
reference):
```javascript
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([
"output/1ece_tests_snapshot_basic-tree-shake_export-namespace_input_index_0tbr8qy.js",
{"otherChunks":["output/0_9x_turbopack-tests_tests_snapshot_basic-tree-shake_export-namespace_input_1mqgz2-._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/basic-tree-shake/export-namespace/input/index.js [test] (ecmascript)"]}
]);
```
This is a pretty small change on its own (no functionality change), it
makes it a separate method so that it can be called in future PRs. I
will flag that the shape for inline-d bootstrap code will be slightly
different (further up the stack). Because it isn't a chunk itself it
does not have the first item in the array
(`output/1ece_tests_snapshot_basic-tree-shake_export-namespace_input_index_0tbr8qy.js`):
```javascript
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push({"otherChunks":["static/chunks/node_modules_react-dom_82bf80._.js","static/chunks/_4f9bce._.js","static/chunks/pages_index_tsx_5a9e._.js"],"runtimeModuleIds":[96168]});
```
This item only has `otherChunks` and `runtimeModuleIds`. New code is
added in https://github.com/vercel/next.js/pull/94664 to handle this
different shape.
The goal of this PR is to extract all the generic runtime code into one
asset that is shared by each route. This makes the per-route runtime
tiny, which will be removed in later PRs in this stack.
The diff is unfortunately massive, however, these are almost all because
of the snapshots.
---------
Co-authored-by: Benjamin Woodruff <[email protected]>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
#95815 refactored the NFT flow and ended up only tracing the RSC
template and it's subgraph. Not the server actions loader module whose
module graph is completely separate.
---
Clauded.
Attempts to fix
https://github.com/vercel/next.js/discussions/95130#discussioncomment-17652155.
---
<details>
## Summary
Fixes the NFT tracing regression behind #95815 and the deploy failures
reported in
https://github.com/vercel/next.js/issues/87737#issuecomment-4897366831.
Since the 16.3 NFT rewrite (#94224 / #92901), a `serverExternalPackages`
entry that is imported **only from a `'use server'` action referenced by
a client component** ends up in the route's trace as the content-hashed
alias symlink (`.next/node_modules/<pkg>-<hash>`) **without the store
files the symlink points to**. Any output assembled from the traces
(Vercel lambdas, `output: 'standalone'`) then contains a dangling
symlink, and invoking the action throws `Failed to load external module
<pkg>-<hash>` (HTTP 500). 16.2.9 traces both. Both CJS and ESM externals
are affected, with any package manager — the ESM/bun/monorepo framing in
the issue reports was a coincidence of which packages were only
reachable through actions.
The cause: `AppEndpoint::trace_result` started the trace DFS only from
`rsc_entry`, but the server actions loader is a separate module-graph
entry (`additional_entries`). It is chunked into the endpoint output
(which emits the alias symlink via the chunk's output assets), yet its
subgraph — including the externals' traced target files — was never
visited by the tracer. `trace_endpoint` now accepts multiple entry
modules and the app endpoint passes the actions loader alongside
`rsc_entry`.
The test observes the user-visible failure: it builds with `output:
'standalone'`, deletes everything except the standalone output (so only
traced files are available, like a deployed lambda), runs `server.js`,
and invokes the server action in a browser. Without the fix the action
fails with `Failed to load external module lodash-<hash>`; on 16.2.9 the
same fixture responds with the action result. Note the fixture must not
import the externals from any other route: the standalone output is the
union of all route traces, so a route that traces them correctly would
mask the missing entries.
## Verification
- `pnpm test-start-turbo
test/production/standalone-mode/server-action-externals/standalone-mode-server-action-externals.test.ts`
— fails without the fix (`Failed to load external module
lodash-fb78ad0536ffcdab` from the standalone server), passes with it.
The equivalent standalone flow on `[email protected]` returns the action
result with HTTP 200.
- `pnpm test-start-turbo
test/e2e/externals-transitive/externals-transitive.test.ts`
- `pnpm test-start-turbo
test/production/standalone-mode/server-actions/standalone-mode-server-actions.test.ts`
<!-- NEXT_JS_LLM -->
</details>
App Shells are meant to be useful for handling navigations that are not
prefetched. If the stale time is very low then they must be evicted
often from the client router and thus their utility is undermined. We
now exclude caches that have a stale time under 5 minutes from app
shells.
For the default cacheLife profiles this means only the `seconds` profile
is excluded becasue every other profile has a stale of exactly 5 minutes
at this time.
In the future we may want to allow for this threshhold to be configured.
This shouldn't be too disruptive since generally content that is
changing often is likely not in your app shell to begin with.
### What?
Remove outdated experimental status notices for `@next/routing` from the
adapters documentation and package README.
### Why?
`@next/routing` is no longer experimental, and the existing notices
incorrectly tie its stabilization to the adapters API.
### How?
Delete the obsolete notices while leaving the routing API documentation
unchanged.
### Verification
- `pnpm prettier --with-node-modules --ignore-path .prettierignore
--write
docs/01-app/03-api-reference/07-adapters/05-routing-with-next-routing.mdx
packages/next-routing/README.md`
- Repository lint-staged checks passed during commit
- Not run: runtime tests (documentation-only change)
<!-- NEXT_JS_LLM_PR -->
Common piece of feedback I've gotten on my PRs. Would be nice to have it
in the `AGENTS.md` as I often use Claude to one shot the plumbing of a
flag.
though - maybe we should have a turbopack-specific AGENTS.md?
Fixes a race you can observe if you:
1. Open page A
2. Navigate to page B
3. Cmd+Shift+R while on page B...
4. ...and then navigate Back super fast
In that situation, the router gets confused and shows page B's content
with page A's URL. From then on, going Back and Forward changes the URL
(and tab title) but keeps showing B's content. See the new tests.
## Why This Happens
Navigating from A to B client-side creates B's history entry via
`pushState`. Reloading B replaces that document with B's fresh
server-rendered HTML. Pressing Back at that point fires `popstate` to A,
but nothing listens to it yet. Then JS loads, and we happily hydrate B's
content onto B's HTML despite technically being on A route.
## The Fix
Use Navigation API to detect that we’re on a different entry than we
started. Once the router mounts, replay the missed traversal the same
way the `onPopState` handler would have handled it.
(I originally tried to avoid using Navigation API but wasn’t sure how to
detect this reliably.)
## Test Plan
New tests are red before the fix.
Docs for the newly added `graph` option, with this additional section,
`Balancing requests and grouping`, trying to explain how the graph
option works.
Also adding a couple new sections:
- Choosing a strategy
- Debugging what a route actually uses
- Example
- ends with a deep dive on the high level idea of the graph alg, trying
to explain more of `weightDistribution`
Closes; https://linear.app/vercel/issue/DOC-6481/csschunking-graph-mode
and to some degree
https://linear.app/vercel/issue/DOC-3447/document-how-the-css-chunking-algorithm-works
---------
Co-authored-by: Tobias Koppers <[email protected]>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Follow-up to https://github.com/vercel/next.js/pull/95807.
Makes payload size match prod sizes closer and aligns chunk count by
adding a lot of client modules.
I originally thought to unify them into `client.js` or such but maybe
it's good to see how compilation behaves with many small files? Which is
how real projects do that.
```
=== dashboard.html (fixture) vs p-overview.html (real)
total KB 665 637
rows (model/I/T/other) 258/111/3/1 240/125/5/27
byte share model/I/T % 43/57/0 36/62/2
median row size B 906 106
max depth 43 53
mean depth 8.9 10.5
elements per KB(model) 7.7 5.2
pure-data byte share % 18 21
client-ref elements % 35 32
objects:elements ratio 1.18 1.23
avg props per element 2.6 3.1
median children fanout 4 4
string bytes % 50 54
median string len B 12 15
row refs per KB 0.4 0.4
encoded scalars per KB 0.7 1.2
undefined markers 247 740
=== docs.html (fixture) vs nextjs-docs.html (real)
total KB 487 487
rows (model/I/T/other) 72/40/7/1 55/43/2/20
byte share model/I/T % 89/8/3 94/6/0
median row size B 199 340
max depth 27 32
mean depth 22.3 12.3
elements per KB(model) 0.6 0.8
pure-data byte share % 7 9
client-ref elements % 29 33
objects:elements ratio 8.67 4.92
avg props per element 2.0 2.8
median children fanout 3 4
string bytes % 47 64
median string len B 17 35
row refs per KB 0.2 0.1
encoded scalars per KB 7.4 2.9
undefined markers 3586 1377
=== blog.html (fixture) vs vercel-blog.html (real)
total KB 778 797
rows (model/I/T/other) 72/39/0/1 102/53/7/14
byte share model/I/T % 93/7/0 87/8/5
median row size B 145 505
max depth 34 44
mean depth 9.6 12.2
elements per KB(model) 0.7 1.1
pure-data byte share % 92 85
client-ref elements % 38 33
objects:elements ratio 31.19 16.39
avg props per element 1.9 2.7
median children fanout 3 4
string bytes % 46 48
median string len B 6 9
row refs per KB 0.1 0.1
encoded scalars per KB 0.4 0.6
undefined markers 242 426
```
Co-authored-by: Claude Fable 5 <[email protected]>
This adds more fixtures modeled after Flight payloads from real sites:
- Vercel Dashboard-like (very app-y)
- React Docs-like after App Router conversion (mostly MDX site)
- Vercel Blog-like (passing down data from CMS)
(Actual data is randomly generated with a seed)
These are simplifications but I tried to incorporate the originals'
corresponding quirks. Such as Vercel Dashboard having many client
components; syntax highlight with many small spans in React Docs; Vercel
Blog currently shipping a load of data unnecessarily on the index page.
(The last one is not ideal but I think we should actually benchmark
"bad" cases like this too so I kept that.)
### Screenshots
<img width="1325" height="1021" alt="Screenshot 2026-07-15 at 04 29 43"
src="https://github.com/user-attachments/assets/62c80722-6fd5-4884-89db-106345f6412d"
/>
<img width="1360" height="994" alt="Screenshot 2026-07-15 at 04 30 21"
src="https://github.com/user-attachments/assets/0db93cd8-43ca-41df-b3e2-c422e0c94656"
/>
<img width="1368" height="991" alt="Screenshot 2026-07-15 at 04 30 43"
src="https://github.com/user-attachments/assets/9369e9be-12a4-4a1e-af32-01b6fdd4a457"
/>
### Comparison with real payloads
```
=== dashboard.html (fixture) vs p-overview.html (real)
total KB 208 637
rows (model/I/T/other) 186/48/7/1 240/125/5/27
byte share model/I/T % 92/4/4 36/62/2
median row size B 590 106
max depth 28 53
mean depth 8.4 10.5
elements per KB(model) 14.9 5.2
pure-data byte share % 24 21
client-ref elements % 41 32
objects:elements ratio 1.17 1.23
avg props per element 1.7 3.1
median children fanout 4 4
string bytes % 24 54
median string len B 10 15
row refs per KB 0.9 0.4
encoded scalars per KB 3.5 1.2
undefined markers 347 740
=== docs.html (fixture) vs nextjs-docs.html (real)
total KB 261 487
rows (model/I/T/other) 58/15/7/1 55/43/2/20
byte share model/I/T % 93/1/6 94/6/0
median row size B 193 340
max depth 27 32
mean depth 21.9 12.3
elements per KB(model) 0.8 0.8
pure-data byte share % 8 9
client-ref elements % 16 33
objects:elements ratio 6.26 4.92
avg props per element 1.7 2.8
median children fanout 4 4
string bytes % 47 64
median string len B 17 35
row refs per KB 0.2 0.1
encoded scalars per KB 7.5 2.9
undefined markers 1949 1377
=== blog.html (fixture) vs vercel-blog.html (real)
total KB 475 797
rows (model/I/T/other) 41/15/0/1 102/53/7/14
byte share model/I/T % 100/0/0 87/8/5
median row size B 426 505
max depth 34 44
mean depth 9.7 12.2
elements per KB(model) 0.8 1.1
pure-data byte share % 92 85
client-ref elements % 23 33
objects:elements ratio 28.92 16.39
avg props per element 1.9 2.7
median children fanout 3 4
string bytes % 46 48
median string len B 6 9
row refs per KB 0.1 0.1
encoded scalars per KB 0.4 0.6
undefined markers 161 426
```
---------
Co-authored-by: Claude Fable 5 <[email protected]>
## Summary
Pass the configured Request Insights state into the dev bundler service
so it subscribes to insight updates during initialization. The service
is constructed outside request-local tracing state, so checking only
`isRequestInsightsEnabled()` can incorrectly skip the subscription even
when Request Insights is enabled in configuration.
## Verification
- `pnpm build-all`
- `pnpm prettier --with-node-modules --ignore-path .prettierignore
--write packages/next/src/server/lib/dev-bundler-service.ts
packages/next/src/server/lib/router-server.ts`
- `npx eslint --config eslint.config.mjs --fix
packages/next/src/server/lib/dev-bundler-service.ts
packages/next/src/server/lib/router-server.ts`
<!-- NEXT_JS_LLM -->
Extracted from https://github.com/vercel/next.js/pull/95468, tries to
address @lubieowoce feedback.
This should be a pure refactor with no publicly observable behavior
changes. We want to simplify the state of how we're loading and tracking
modules, and simplify signatures:
- Extract a state machine into `LazyModule` that hides per-module state.
You can kick it (`loadIfNeeded`), wait for completion with
`waitUntilLoaded`, or peek-or-throw with `assertLoaded`.
- Reading `.userland` while pending now throws with an invariant so we
don't do it accidentally again. Should be unreachable from current
callsites.
- Remove the non-thunk overload from `userland` factory.
Added tests to cover the cases that the refactor had a risk of breaking.
Extracted from https://github.com/vercel/next.js/pull/95468.
We did not have error handling around async module initialization. As a
result, in `output: 'export'` mode, an error during async initialization
were reported as unhandled rejections. Now it properly fails the build.