Full motivation and plan here:
https://app.notion.com/p/vercel/Turbopack-pnpm-Global-Virtual-Store-383e06b059c480579403ddfd71cc2d40?source=copy_link
The goal is to allow `DiskFileSystem` to traverse outside of it's own
root to other configured `DiskFileSystem`s when following symlinks. We
may allow traversal in other situations in the future, but this is
limited to symlink resolution for now.
## Global Virtual Store
The motivation for this is to enable [pnpm's Global Virtual Store
feature](https://pnpm.io/global-virtual-store) (and there are other
package managers doing this, including nub and bun).
We'd expose the ability to manually configure this in `next.config.js`,
but we should also auto-configure ourselves for popular package managers
(or at least make a best effort to do so, the `PNPM_HOME` semantics can
be complicated). The `ignoreIfMissing` option is provided for this
situation: We can configure a bunch of roots automatically, and they
only actually get set up if they exist, the check for directory
existence is cheap.
## NFT changes
This requires a couple extensions to the `*.nft.json` file format:
https://github.com/vercel/next.js/pull/98469
## Related Issues
- #93556
- https://github.com/pnpm/pnpm/issues/14972
## Summary
Adds an off-by-default `experimental.agentFeedback` workflow for
collecting Next.js friction without interrupting the user’s task.
- `next dev` writes a small managed block to the project
agent-instructions file.
- Agent entry points, including `next-dev-loop`, queue possible issues
instead of opening duplicate forms.
- At the final stopping point, an internal command checks the remote
gate and returns the reporting protocol bundled with that Next.js
version. The agent attempts to anonymize each qualifying issue and opens
a separate review form once. If the browser does not open, it prints the
URL for the user without troubleshooting the failure.
- Report links no longer contain a public page token. Nothing is sent
until the user submits the form, which remains rate-limited and uses a
server-only ingest credential.
- Disabling `agentFeedback` or `agentRules` removes only its managed
block on the next `next dev`. Empty generated agent files are cleaned
up; user-authored content is preserved.
- Adds API references for both options and updates the AI agents guide.
Bundling the protocol keeps the managed block small and allows the
report format to evolve with each Next.js version. The receiving form is
implemented in
[vercel/front#85739](https://github.com/vercel/front/pull/85739) and
should deploy before this workflow is enabled.
## Verification
- `NEXT_SKIP_ISOLATE=1 pnpm test-dev-turbo
test/development/app-dir/agent-rules-auto-generate/agent-rules-auto-generate.test.ts`
- `pnpm jest packages/next/src/server/lib/generate-agent-files.test.ts
packages/next/src/cli/internal`
- `npx eslint --config eslint.config.mjs
packages/create-next-app/helpers/generate-agent-files.ts
packages/next/src/server/config-shared.ts
packages/next/src/server/lib/generate-agent-files.ts
test/development/app-dir/agent-rules-auto-generate/agent-rules-auto-generate.test.ts`
<!-- NEXT_JS_LLM -->
Pretty straightforward. But a number of loaders depend on it including
- postcss-loader
- sass-loader
- stylus-loader
- vue-loader
- nunjucks-loader
- thread-loader
## Summary
- add a Partial Prefetching optimizer Skill for selected source Links
and destinations
- keep prefetch stages and Link policy in the public [Optimizing
prefetching
guide](https://nextjs.org/docs/app/guides/optimizing-prefetching), and
rely on the same static-shell documentation as the Cache Components
optimizer for cache placement, Suspense, loading UI, and authorization
- keep the Skill focused on the product decision, production `instant()`
loop, differential verification, and mutation freshness when an
optimization adds a cache around mutable data
- reference the canonical
[`unstable_prefetch()`](https://nextjs.org/docs/app/api-reference/functions/prefetch)
and
[`unstable_navigation()`](https://nextjs.org/docs/app/api-reference/functions/navigation)
docs instead of explaining the APIs again in the Skill
- link cache-writing optimizations to the canonical
[Revalidating](https://nextjs.org/docs/app/getting-started/caching-and-revalidating#revalidating)
documentation and verify a populated-cache mutation in the Skill and
eval
- bundle a self-contained production rig aligned with the related
adoption and optimizer Skills, tuned to record each source-link and
prefetch-stage contract
- add an eval that compares baseline, the bundled guide, and the local
Skill against the same prefetched-UI and cache-freshness goal
- document the optimizer in the AI agents guide
This follows the same guide/Skill split as #97737. Partial Prefetching
adoption preserves existing behavior; this optimizer is the later
decision about what is worth preparing before a click and what should
wait for navigation.
Related: #97712, #97737, #97759, #98122, #98175
## Eval
One run per treatment with Claude Opus 4.8 and Claude Sonnet 4.5, using
a fixed Claude Haiku 4.5 judge. The bundled-guide and Skill treatments
use a local Next.js tarball containing the new guide and API references.
| Model | Treatment | Criteria | Score change | Overall | Duration |
Tokens processed | Total run cost | Cost change |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Opus 4.8 | Baseline | 10/10 (100%) | — | Pass | 18m 42s | 3.39M |
$3.46 | — |
| Opus 4.8 | Bundled guide | 9/10 (90%) | -10 pp | Fail | 13m 37s |
3.02M | $2.84 | -18.0% |
| Opus 4.8 | Local Skill | 10/10 (100%) | +10 pp | Pass | 17m 22s |
2.80M | $2.54 | -10.6% |
| Sonnet 4.5 | Baseline | 2/10 (20%) | — | Fail | 15m 55s | 1.97M |
$1.06 | — |
| Sonnet 4.5 | Bundled guide | 5/10 (50%) | +30 pp | Fail | 15m 49s |
2.17M | $1.11 | +4.5% |
| Sonnet 4.5 | Local Skill | 8/10 (80%) | +30 pp | Fail | 15m 31s |
1.41M | $0.77 | -30.4% |
Token totals count each unique model message once and include uncached
input, cache creation, cache reads, and output. Cache reads account for
most of the total in every treatment. Total run cost applies the current
AI Gateway base rates to each token category: Opus 4.8 costs $5/M input,
$6.25/M cache writes, $0.50/M cache reads, and $25/M output; Sonnet 4.5
costs $3/M input, $3.75/M cache writes, $0.30/M cache reads, and $15/M
output. The six agent runs cost $11.78 in total.
Compared with baseline, the Opus Skill run maintained a 100% criteria
pass rate while reducing cost by 26.7%. The Sonnet Skill run improved
the criteria pass rate from 20% to 80%, a 60 percentage-point increase,
while reducing cost by 27.3%.
The stronger model could solve the fixture without the Skill, although
it independently searched the bundled Next.js documentation and took
slightly longer than the Skill treatment. The Opus guide-only run
implemented the correct behavior but missed the required
RED-before-GREEN proof.
The Sonnet treatments show a clearer progression. Baseline did not
implement the required caching, navigation stage, `instant()` contract,
or cache invalidation. The bundled guide added the cached summary and
production test, but missed the navigation stage, deferred-state
assertions, tag invalidation, and RED-before-GREEN proof. The Skill
treatment completed the `instant()` RED-to-GREEN loop and final
production behavior, but left `connection()` in the related-sessions
read and used path revalidation instead of tagging the cached read. It
therefore passed 8 of 10 criteria but failed the overall eval.
All six measured runs completed through grading. Chromium and its system
dependencies are provisioned before the agent starts, so missing browser
libraries did not determine these results.
## Verification
- `pnpm --filter=next build`
- `pnpm prettier --check` for the changed guide, prompt, Skill, and eval
fixture
- `pnpm exec tsc -p
evals/evals/agent-051-optimize-partial-prefetching/tsconfig.json
--noEmit`
- `node --check run-evals.js`
- `git diff --check`
- packed a local Next.js tarball and confirmed it contains the optimizer
guide plus the `navigation()` and `prefetch()` references
- verified the `navigation()` contract in the minimal reproduction and
NextBeats on Next.js 16.4.0-canary.22
- merged the latest `canary`, preserving both generic fixture setup and
Playwright provisioning
<!-- NEXT_JS_LLM -->
### What?
`import.meta.glob()` now resolves `../`-relative and
`/`-project-root-absolute
patterns (and `base`), validates every pattern (positive and negative)
for
whether it escapes the filesystem root, and shares the underlying
"resolve leading `./`/`../` against a directory" primitive with
`next-core::relativize_glob` (used by `outputFileTracingIncludes`)
instead of
duplicating it.
### Why?
`import.meta.glob()` handed its patterns straight to Turbopack's `Glob`,
which
matches relative to the scanned directory and understands neither a
leading
`/` nor `..` segments. `import.meta.glob('../../content/*.js')` and
`import.meta.glob('/content/*.js')` therefore silently returned `{}`,
and a
`base` that walked above the filesystem root failed with an internal
error
and a panic report instead of a normal diagnostic. A negative
(exclusion)
pattern that escaped the root was silently dropped, which is the worst
outcome for an exclusion — it would silently include files the user
asked to
exclude.
### How?
- Patterns are split into the directory they're rooted in and the
remaining
glob, matched relative to a single common scan directory covering every
positive and negative pattern. `/` is resolved from the project root,
matching Vite's rule that patterns are relative to the importing file or
absolute from the project root.
- Map keys follow the pattern too: `base` now produces base-relative
keys
(`'./zeta.js'` instead of `'../../content/zeta.js'`, matching Vite), a
project-root-absolute pattern produces project-root-absolute keys, and
origin-relative keys are unchanged otherwise.
- Every pattern is validated up front; one that leaves the project (a
`base`,
a positive pattern, or a negative one) reports an error issue at the
call
site and yields an empty object, instead of crashing or being silently
dropped.
- The `./`/`../`-resolution loop is extracted into
`turbo_tasks_fs::glob::relativize_glob`, a crate both `next-core` and
`turbopack-ecmascript` already depend on. `next-core::relativize_glob`
is
now a thin wrapper over it (same name, signature and error message); its
5
unit tests are removed since the shared primitive's own tests cover the
same cases.
<!-- NEXT_JS_LLM -->
Co-authored-by: Niklas Mischkulnig
<4586894+mischnic@users.noreply.github.com>
---------
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
There are a bunch of different loaders I found depending on this
- vue-loader
- svelte-loader
- svg-sprite-loader
- nunjucks-loader
- workerize-loader
- worklet-loader
- nativescript-worker-loader
- thread-loader
- svg-sprite-generation-loader
This doesn't by itself fix any of them fully, but does increase
compatibility
This fully fixes handlebars loader in my tests and a number of other
loaders depend on this as well. The implementation for getResolve is a
bit different in turbopack, but this should be semantically the same.
Adds API reference pages for the two render-interruption functions, both
marked `version: experimental`.
- `unstable_navigation()`, which keeps a subtree out of a route's
prefetches so it renders on the navigation.
- `unstable_prefetch()`, which keeps a subtree out of a route's App
Shell so it renders on a per-link prefetch or on the navigation.
Each page opens with the App Shell, per-link prefetch, navigation
ladder, states which boundary its function moves content past, and
cross-references the sibling function and the Adopting Partial
Prefetching guide.
Enabling `experimental.taint` makes Next.js taint `process.env` for you
(`app-render.tsx`), so passing it whole to a Client Component throws.
The docs never mentioned this.
Adds it to the existing "Good to know" callout, plus a caveat that the
taint is by object reference, so `process.env.MY_VAR` and `{
...process.env }` still pass through.
## Summary
Upgrades the vendored `web-vitals` dependency to v6.0.1 and enables
soft-navigation reporting for CLS, INP, and LCP. This lets
`useReportWebVitals` report metrics against the navigation where they
occurred, including `navigationURL` for route attribution.
The upgrade also removes the obsolete FID integration and compatibility
patch, regenerates the standard and attribution bundles, and updates the
public metric types and documentation to match the current upstream API.
Related to #97161.
## Verification
- Added production-mode coverage for initial and soft-navigation Web
Vitals reporting with both Turbopack and Webpack.
<!-- NEXT_JS_LLM -->
## Summary
- add the preload pattern to the current App Router data fetching guide
- distinguish automatic fetch memoization, React cache for non-fetch
data, and Cache Functions
- clarify request-scoped private caching and when stale: Infinity should
not constrain the route stale time
The previous-model caching guide remains unchanged.
## Verification
- Prettier on both edited docs
- alex on both edited docs
- git diff --check
- Not run: full framework build because this is a documentation-only
change
<!-- NEXT_JS_LLM -->
### What?
Centralizes related <code>"use cache"</code> scope errors and rewrites
them as shorter, actionable messages with consistent <code>Learn
more:</code> links. It also updates the reachable revalidation errors
for rendering and <code>generateStaticParams</code>.
### Why?
The previous messages were long, inconsistent, and sometimes missing
documentation links. Some new cache-specific revalidation messages were
also hidden by an earlier render-phase error. The new messages name the
constraint, give the immediate fix, and link to the relevant
documentation.
### How?
Adds centralized error factories and dedicated error pages for request
data, cache configuration, private-cache composition, and revalidation.
When an active App Router route is available, the messages include it.
The error pages use consistent terminology and complete examples. The
<code>unstable_cache()</code> reference now documents the
request-dependent operations that these errors reject.
### Before and after
Route-based examples use <code>/products</code>. Bracketed values are
alternatives in matching order. For example, <code>[E1482, E1486,
E1489]</code> maps to <code>[headers(), cookies(), request.url]</code>.
Each runtime error contains one alternative, but the table groups
messages with the same template.
#### Messages that include the route
| Cases | Before | After |
| --- | --- | --- |
| E1480 · <code>searchParams</code> in <code>"use cache"</code> | Route
/products used <code>searchParams</code> inside "use cache". Accessing
dynamic request data inside a cache scope is not supported. If you need
some search params inside a cached function await
<code>searchParams</code> outside of the cached function and pass only
the required search params as arguments to the cached function. See more
info here: https://nextjs.org/docs/messages/next-request-in-use-cache |
Route "/products": <code>searchParams</code> can't be read inside
<code>"use cache"</code>. Await it outside the cached function and pass
what you need as an argument.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| [E1482, E1486, E1489] · [<code>headers()</code>,
<code>cookies()</code>, <code>request.url</code>] in <code>"use
cache"</code> | Route /products used [<code>headers()</code>,
<code>cookies()</code>, <code>request.url</code>] inside "use cache".
Accessing Dynamic data sources inside a cache scope is not supported. If
you need this data inside a cached function use [<code>headers()</code>,
<code>cookies()</code>, <code>request.url</code>] outside of the cached
function and pass the required dynamic data in as an argument. See more
info here: https://nextjs.org/docs/messages/next-request-in-use-cache |
Route "/products": [<code>headers()</code>, <code>cookies()</code>,
<code>request.url</code>] can't be read inside <code>"use cache"</code>.
Read it outside the cached function and pass what you need as an
argument.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| [E1481, E1485, E1492] · [<code>cookies()</code>,
<code>request.url</code>, <code>headers()</code>] in
<code>unstable_cache()</code> | Route /products used
[<code>cookies()</code>, <code>request.url</code>,
<code>headers()</code>] inside a function cached with
<code>unstable_cache()</code>. Accessing Dynamic data sources inside a
cache scope is not supported. If you need this data inside a cached
function use [<code>cookies()</code>, <code>request.url</code>,
<code>headers()</code>] outside of the cached function and pass the
required dynamic data in as an argument. See more info here:
https://nextjs.org/docs/app/api-reference/functions/unstable_cache |
Route "/products": [<code>cookies()</code>, <code>request.url</code>,
<code>headers()</code>] can't be read inside
<code>unstable_cache()</code>. Read it outside the cached function and
pass what you need as an argument.<br><br>Learn more:
https://nextjs.org/docs/app/api-reference/functions/unstable_cache |
| [E1484, E1491] · <code>draftMode().enable()</code> in
[<code>unstable_cache()</code>, <code>"use cache"</code>] | Route
/products used "draftMode().enable()" inside [a function cached with
<code>unstable_cache()</code>, "use cache"]. The enabled status of
<code>draftMode()</code> can be read in caches but you must not enable
or disable <code>draftMode()</code> inside a cache. See more info here:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] | Route
"/products": <code>draftMode().enable()</code> can't be called inside
[<code>unstable_cache()</code>, <code>"use cache"</code>]. Draft mode
can be read inside a cached function, but enabling or disabling it must
happen outside.<br><br>Learn more:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] |
| [E1488, E1499] · <code>connection()</code> in
[<code>unstable_cache()</code>, <code>"use cache"</code>] | Route
/products used <code>connection()</code> inside [a function cached with
<code>unstable_cache()</code>, "use cache"]. The
<code>connection()</code> function is used to indicate the subsequent
code must only run when there is an actual request, but caches must be
able to be produced before a request, so this function is not allowed in
this scope. See more info here:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] | Route
"/products": <code>connection()</code> can't be called inside
[<code>unstable_cache()</code>, <code>"use cache"</code>] because cached
functions may run during prerendering, without an incoming request. Call
it outside the cached function.<br><br>Learn more:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] |
| E1494 · <code>connection()</code> in <code>"use cache: private"</code>
| Route /products used <code>connection()</code> inside "use cache:
private". The <code>connection()</code> function is used to indicate the
subsequent code must only run when there is an actual navigation
request, but caches must be able to be produced before a navigation
request, so this function is not allowed in this scope. See more info
here: https://nextjs.org/docs/messages/next-request-in-use-cache | Route
"/products": <code>connection()</code> can't be called inside <code>"use
cache: private"</code> because private cached functions may run during
prefetching, without a navigation request. Call it outside the cached
function.<br><br>Learn more:
https://nextjs.org/docs/app/api-reference/directives/use-cache-private |
| [E1483, E1495] · <code>revalidateTag("products")</code> in [<code>"use
cache"</code>, <code>unstable_cache()</code>] | Route /products used
"revalidateTag products" inside [a "use cache", a function cached with
"unstable_cache(...)"] which is unsupported. To ensure revalidation is
performed consistently it must always happen outside of renders and
cached functions. See more info here:
https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
| Route "/products": <code>revalidateTag("products")</code> can't be
called during render, inside a cached function, or inside
<code>generateStaticParams</code>. Call it from a Server Action or Route
Handler instead.<br><br>Learn more:
https://nextjs.org/docs/messages/revalidate-in-use-cache |
| During render | Route /products used "revalidateTag products" during
render which is unsupported. To ensure revalidation is performed
consistently it must always happen outside of renders and cached
functions. See more info here:
https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
| Route "/products": <code>revalidateTag("products")</code> can't be
called during render, inside a cached function, or inside
<code>generateStaticParams</code>. Call it from a Server Action or Route
Handler instead.<br><br>Learn more:
https://nextjs.org/docs/messages/revalidate-in-use-cache |
| Inside <code>generateStaticParams</code> | Route /products used
"revalidateTag products" inside <code>generateStaticParams</code> which
is unsupported. To ensure revalidation is performed consistently it must
always happen outside of renders and cached functions. See more info
here:
https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
| Route "/products": <code>revalidateTag("products")</code> can't be
called inside <code>generateStaticParams</code>. Call it from a Server
Action or Route Handler instead.<br><br>Learn more:
https://nextjs.org/docs/messages/revalidate-in-use-cache |
#### Other messages
| Cases | Before | After |
| --- | --- | --- |
| [E1477, E1490] · Nested cache with [short <code>expire</code>,
<code>revalidate: 0</code>] | A "use cache" with [short
<code>expire</code> (under 5 minutes), zero <code>revalidate</code>] is
nested inside another "use cache" that has no explicit
<code>cacheLife</code>, which is not allowed during prerendering. Add
<code>cacheLife()</code> to the outer "use cache" to choose whether it
should be prerendered [with longer <code>expire</code>, with non-zero
<code>revalidate</code>] or remain dynamic [with short
<code>expire</code>, with zero <code>revalidate</code>]. Read more:
https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife
| Route <code>"/products"</code>: A nested <code>"use cache"</code> with
[a short <code>expire</code> (under 5 minutes), <code>revalidate:
0</code>] is inside an outer <code>"use cache"</code> that has no
<code>cacheLife()</code>. Add <code>cacheLife()</code> to the outer one
to choose whether to prerender it [with a longer <code>expire</code>,
with a non-zero <code>revalidate</code>] or keep it dynamic [with a
short <code>expire</code>, with <code>revalidate:
0</code>].<br><br>Learn more:
https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife
|
| E1478 · External promise | Filling a "use cache" entry appears to be
stuck on shared state from the outer render scope. The same function
completed when run in isolation, which usually means a module-scoped
value (for example a top-level Map used to dedupe fetches) is joining a
promise created outside the cache. "use cache" already dedupes calls
with the same arguments within a request and across requests on the same
server instance, so the surrounding dedupe layer is both unnecessary and
the likely cause. Remove it and rely on "use cache" alone for
deduping.<br><br><em>No documentation link.</em> | Route
<code>"/products"</code>: A <code>"use cache"</code> function is
awaiting a promise created outside it. The same call completed when run
in isolation, so a module-scoped value (often a top-level
<code>Map</code> used to dedupe fetches) is most likely blocking it.
<code>"use cache"</code> already dedupes calls with the same arguments.
Remove the surrounding dedupe layer.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| [E1479, E1498] · [<code>cacheTag()</code>, <code>cacheLife()</code>]
outside a cached function | [<code>cacheTag()</code>,
<code>cacheLife()</code>] can only be called inside a "use cache"
function.<br><br><em>No documentation link.</em> | Route
<code>"/products"</code>: [<code>cacheTag()</code>,
<code>cacheLife()</code>] can only be called inside a <code>"use
cache"</code> or <code>"use cache: private"</code>
function.<br><br>Learn more:
[https://nextjs.org/docs/messages/cache-tag-outside-use-cache,
https://nextjs.org/docs/messages/cache-life-outside-use-cache]<br><br>Outside
an App Router route, the same message is shown without the route prefix.
|
| E1487 · Prerender timeout | Filling a cache during prerender timed
out, likely because request-specific arguments such as params,
searchParams, cookies() or dynamic data were used inside "use
cache".<br><br><em>No documentation link.</em> | Route
<code>"/products"</code>: A <code>"use cache"</code> function took too
long during prerendering. The most common cause is passing unresolved
request-specific arguments, such as <code>params</code> or
<code>searchParams</code>, into the cached function. Resolve the data
before calling the function and pass only the values you
need.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| E1493 · Private cache inside a public cache | "use cache: private"
must not be used within "use cache". It can only be nested inside of
another "use cache: private".<br><br><em>No documentation link.</em> |
Route <code>"/products"</code>: <code>"use cache: private"</code> can't
be nested inside <code>"use cache"</code> because a shared cached
function can't depend on private request data. Nest it only inside
another <code>"use cache: private"</code>.<br><br>Learn more:
https://nextjs.org/docs/messages/use-cache-private-composition |
| E1496 · Private cache without a request | "use cache: private" cannot
be used outside of a request context.<br><br><em>No documentation
link.</em> | Route <code>"/products"</code>: <code>"use cache:
private"</code> needs an active request, so it can't be used during
<code>generateStaticParams</code> or other build-time contexts. Move it
to a request-time component or function.<br><br>Learn more:
https://nextjs.org/docs/messages/use-cache-private-composition |
| E1497 · Private cache inside <code>unstable_cache()</code> | "use
cache: private" must not be used within
<code>unstable_cache()</code>.<br><br><em>No documentation link.</em> |
Route <code>"/products"</code>: <code>"use cache: private"</code> can't
be used inside <code>unstable_cache()</code> because
<code>unstable_cache()</code> uses a shared cache that can't contain
private request data. Call the private cached function outside
<code>unstable_cache()</code>.<br><br>Learn more:
https://nextjs.org/docs/messages/use-cache-private-composition |
### Runtime verification
- Exercised the 13 route-based message IDs through minimal dev-runtime
reproductions. Twelve reached the new factories from userland, with
byte-identical output across three requests each.
- The `request.url` error for `"use cache"` Route Handlers is currently
limited to the prerender path. A dynamic request captured by a cached
closure can bypass that tracking; this is existing framework behavior to
follow up separately.
- Passing the `searchParams` promise into a nested cached function can
still surface the earlier synchronous dynamic-API error before this
factory. The new message is verified when the cache scope reads its own
`searchParams` value.
- A follow-up preview verification triggered 17 of the 19 rewritten
messages from userland with byte-identical output. The timeout-driven
external-promise and prerender-timeout messages were verified in source
and through their focused test coverage.
### Verification
- <code>CI=1 pnpm build-all</code>
- <code>pnpm --filter=next types</code>
- <code>pnpm --filter=next build</code>
- <code>HEADLESS=true pnpm test-dev-turbo
test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts</code>
- <code>HEADLESS=true pnpm test-start-turbo
test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts</code>
- <code>NEXT_SKIP_ISOLATE=1 HEADLESS=true pnpm test-dev-webpack
test/e2e/app-dir/cache-components-errors/use-cache.test.ts -t 'cacheLife
with (expire < 5 minutes|revalidate: 0)'</code> (6 tests and 6
snapshots passed)
- <code>pnpm test-dev
test/e2e/app-dir/cache-components-errors/use-cache.test.ts --projects
jest.config.*</code> (42 tests and 42 snapshots passed across Turbopack
and webpack)
- <code>pnpm test-dev
test/e2e/app-dir/use-cache-hanging/use-cache-hanging.test.ts --projects
jest.config.*</code> (10 tests and 8 snapshots passed across Turbopack
and webpack)
- <code>pnpm test-dev
test/e2e/app-dir/use-cache-configured-timeout/use-cache-configured-timeout.test.ts
--projects jest.config.*</code> (4 tests and 2 snapshots passed across
Turbopack and webpack)
- Prettier, ESLint, and Alex on the changed source, tests, and error
pages
<!-- NEXT_JS_LLM -->
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The documented in-memory handler stored the resolved entry and returned
it from every `get`. A stored stream keeps the request that produced the
entry reachable. The second cache hit also fails, because it reads a
stream that another reader already consumed. The render then reports
"Invalid state: ReadableStream is locked".
The "Handling Streams" section described a second, milder mistake. It
told a handler to tee the stored stream in `get`. One branch goes back
into the store, and the other serves the current read. That pattern
serves every hit correctly. The built-in handler used it until #97941,
so the page described the framework's own implementation. The stored
branch retains the same request, and it also holds the source stream and
its buffered chunks. The size a store accounts for covers none of that.
The page now states that cost, because a reader who thinks of teeing
would otherwise read the rule as inapplicable.
`get` told a handler to drop an entry once it is past `revalidate`.
Next.js compares `timestamp` against `expire` on every read, and it
treats a too-old entry as a miss. It applies the same check to
`revalidate` when the result goes into another server cache. A handler
therefore needs no age check at all. The built-in handler still drops at
`revalidate`, as a deliberate policy for an in-memory cache, and the
page now presents that as a policy instead of a rule.
Something still has to reclaim entries, so the page states that eviction
belongs to the handler. Next.js never deletes from it. An age check at
serve time is no substitute, because it only reaches the keys that a
reader still asks for.
The page now follows an order in which no rule appears after the code
that depends on it. The stream rules sit in one section ahead of the API
reference. `CacheEntry Type` moves above the methods that reference it.
`set` precedes `get`, because a handler writes an entry before it reads
one. The tag and error sections move above the examples.
`types.ts` described `refreshTags` as periodic, and the page described
it as a call before each request. Neither is accurate. It runs once per
request, before the first cache read for its kind. A request that reads
nothing from a handler never calls it.
Neither `types.ts` nor the page documented the `revalidate: -1` signal,
which the built-in handler relies on. A negative value always lies in
the past, so it serves an entry with a stale tag one more time while
Next.js generates a fresh one.
`types.ts` now states these rules as well, so a handler author no longer
has to read the built-in implementation to find them. It also covers the
`pendingEntry` promise, which a handler must not retain either.
`streamFromBuffer` now records why it has to stay a default stream. A
byte stream transfers the buffer of every chunk it receives. That would
detach the buffer an in-memory handler serves many reads from. The
comment in `tiered-cache-handler.ts` about cancelling a teed stream now
says that the backing handler is user-configured, because the built-in
one no longer returns such a branch.
The handler test from #97941 now records a limit of its own. It only
fails on Node 20 and 22, whose `AsyncLocalStorage` attaches the active
store to every promise. CI runs Node 20.9, so a regression fails there.
A local run on a newer Node passes.
Nothing in this change affects runtime behavior.
### What?
Adds export-name mangling to Turbopack, behind a new experimental option
`experimental.turbopackMangleExportNames` (default `false`). It is
independent of minification:
`--no-mangling` is a minifier flag and does not affect it.
When enabled, each ECMAScript module's *used* export names — including
`default` and
`__esModule` — are replaced by short keys in the emitted output, both
where the module registers
its exports and where every consumer reads them. Modules whose export
names could be observed by
user code keep their original names, decided per module.
This is a reland of #89060 (on top of the already-merged #89406),
originally written by Matt
Mastracci, who is credited as a co-author on the commit.
Stacked on top: #97676 flips the default to `true` on canary releases,
so Next.js's own CI
exercises the feature broadly before it is considered for stable.
### Why?
Bundle size. A module's export keys exist only to link modules together:
the producing module
emits `{ someVeryLongExportName: … }` and every consumer reads
`ns["someVeryLongExportName"]`.
Both sides are generated by us, so as long as producer and consumer
agree — and the name isn't
observable from user code — the key can be a single character. Long
export names are extremely
common in real dependency graphs (icon sets, utility packages, barrel
files), and each one is paid
for once in the module that defines it and once per importing module.
### How?
**Ported, not rebased.** `canary` is ~2500 commits past the original
stack's base, and the files it
touched were independently rewritten in the meantime (export-analysis
refactor #92781, CJS analysis
for scope hoisting #95826, the `module_fragments` subsystem #95978). A
probe rebase produced 16
conflicting files on the first commit alone, so the original branches
were used as a reference
implementation — for intent, the identifier alphabet, and test coverage
— and the feature was
rebuilt on today's infrastructure. #89561 from the original stack
(erasing the Next.js wrapper
module types) is deliberately **not** part of this change; it turned out
to be unnecessary, because
those modules already declare whole-module export usage and therefore
back off on their own.
**The name table** (`references/esm/mangle/table.rs`) hashes each name
into a table of all valid JS
identifiers of the smallest length that fits the name set — 15 exports
get single-character keys —
and resolves collisions by open addressing. Hashing rather than
assigning `a`, `b`, `c`, … is what
keeps names stable: an unrelated edit elsewhere in the module doesn't
renumber every other export,
and a collision only perturbs its own cluster. Assignment happens in two
passes: every name that is
*already* a valid identifier at the chosen length keeps itself and
reserves its bucket first, and
only then is anything hashed — so an export called `a` keeps `a`, and
nothing else can be assigned
it. Both passes iterate in sorted order, so the mapping depends only on
the set of names.
A module with exactly **one** mangleable export is special-cased to a
fixed key, `f`, rather than a
hashed one. `f` is the most common character in JS keywords (`if`,
`for`, `function`), and every
single-export module in the graph then emits the same `.f` / `.f()` byte
sequences, which gzip's
back-references pick up across the whole bundle — a bigger win than
hashing, at the cost of that one
key changing when a second export is added.
A fixed list, `RESERVED_KEYS`, is withheld from every table for two
different reasons: JS reserved
words (`if`, `in`, `do`, `for`, `let`, `new`, `try`, `var`) are legal as
quoted property keys but a
minifier will not fold `ns["if"]` into the shorter `ns.if`, so handing
one out costs bytes instead of
saving them; and `__esModule` is withheld because the runtime's `esm()`
helper defines that property
on every module's exports object regardless of what the module itself
exports, so an assigned key
landing on it would collide. (`default` needs no such protection — once
it is mangled like any other
export, nothing else emits a property under that literal name.)
**One source of truth for the mapping.** `mangled_export_names(module,
chunking_context)` is a
turbo-task that both the producing side (`EsmExports::code_generation`)
and the consuming side
(`ReferencedAssetIdent::Module`, the single place a cross-module export
access is materialized) ask
for the *target* module's map. Neither side computes a table of its own,
so they cannot disagree,
and the task derives export usage from the chunking context itself
rather than accepting it as an
argument, so a caller can't supply usage from the wrong graph. Re-export
chains need no special
handling, because the consumer side already resolves through re-exports
to the module that produces
the binding.
The mangling decision itself lives on `EsmExports` as a
`mangle_export_names: bool` field, rather
than a separate trait method every module type has to override. A module
that derives its exports
from another one (a facade, a locals module, a part, a rename) inherits
the flag with the data,
which removed seven hand-written delegations and the possibility of a
new wrapper type forgetting
one.
**A mangling decision must not cross module identities.** A few module
types hand out *another*
module's exports value as their own (the WASM loader module, the
module-fragments side-effects
wrapper, the client-reference proxy). If that borrowed value carried a
real mangling decision, the
producing and consuming sides would key their lookups on two different
modules and could compute two
different keys for the same export — this actually broke every WASM- and
`@vercel/og`-based test
once the default-on layer exercised it in CI.
`EcmascriptExports::borrowed()` is the one place this
is handled: it always returns an unmangled view, and every such
pass-through site uses it.
**Back-off is per module**, built on the export-usage information that
landed after the original PR
(`BindingUsageInfo` / `ModuleExportUsageInfo`) rather than the
original's locals/facade-split
heuristic. A module keeps its names when its usage is `All` (a namespace
import that couldn't be
lowered, a computed property access, an unresolvable `export *`, or a
chunk-group entry — which
covers client references and the Next.js wrapper modules), when it is
read through a namespace value
at all, when its exports are dynamic or not statically known ESM, or
when names aren't being mangled
in this build.
`__webpack_exports_info__` gains `canMangle` and `mangledName` per
export, which is how a running
test can observe the mapping; with the option off it emits exactly what
it emitted before.
### Testing
- `turbo-tasks-hash`/table unit tests: encode/decode round-trip,
degenerate-name rejection, table
sizing, the single-export fixed key (including its own reservation),
reserved-word withholding
(including a reserved bucket-count test that stays in sync with the
reserved list), the
preserved-name pass running before any hashing, uniqueness under heavy
collision, order
independence, wrap-around probing, and same-tier stability.
- 13 `turbopack-tests` execution fixtures under
`tests/execution/turbopack/exports/mangle-*`,
several ported from the original PR and from webpack's
`test/configCases/mangle`: named imports,
re-export chains and default exports (including one literally named
`__esModule`), escaping
namespaces (`Object.keys`, `delete ns.missing`, `export * as`, CJS
interop), destructuring,
prototype-shadowing names (`toString`, `$1`, `__1`), a 60-export
two-character table, dynamic
`import()` with `webpackExports` / `turbopackExports`, a CommonJS
consumer of an ESM module,
dynamic re-exports, scope hoisting on and off, and a control with the
option off.
- 3 committed snapshot fixtures under `tests/snapshot/mangle-exports`,
so the emitted keys, the
back-off, and the fixed single-export key are visible in review.
- One fixture under `__skipped__`, which the harness asserts *fails*,
recording the
namespace-materialization gap below.
- Full suite: 546 unit + 272 execution + 125 snapshot tests pass, with
no snapshot churn across the
several refactors this PR went through in review.
- Verified against real builds: targeted app-dir, worker, WASM, and
`@vercel/og`-based e2e suites
pass with the option forced on (the failures that remain are
external-network tests that fail
identically with it off), and a small two-page app shrank by 0.76% of
total emitted JS / 0.53%
gzipped.
### Known limitations, each intentional
- **A module read through `import * as ns` is never mangled**, even when
every read is statically
tracked, because the analysis doesn't yet distinguish a lowered named
read from a materialized
namespace object. Namespace imports are common, so this leaves real wins
on the table; unlocking
it is the highest-value follow-up.
- **Escaping namespaces back off entirely.** Webpack instead keeps
mangling and materializes a
namespace object keyed by the original names. That is the `__skipped__`
fixture: implementing it
turns the suite red until the fixture is moved out.
- **CommonJS export mangling is out of scope.** The producing side is
reachable, but the consuming
accesses live in user source and nothing rewrites them today; it needs
its own design pass.
Closes PACK-435
<!-- NEXT_JS_LLM -->
Co-authored-by: Luke Sandberg
<210140+lukesandberg@users.noreply.github.com>
<!-- fleet ecdfa248-cd54-41ac-b4a2-c9d49e2a67ee -->
---------
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>
The `allowedDevOrigins`, and `allowedOrigins` options, behave slightly
different to each other, the former drops the PORT, and it is not clear
where are these picked up from. This PR improves guidance on how to use
these two.
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.
## 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>
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 -->
### 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: #96356closes#97781 (only created to run deploy tests)
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.
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>
Clarifying that:
> - **`stale` from 30 seconds up to 5 minutes**: included in prerenders,
but excluded from the route's [App Shell](/docs/app/glossary#app-shell).
Means, 30 <= stale and stale < 5minutes
Completing the input-output pair for cached functions.
This is addressing user confusion around what happens to a cached
function, and functions within the scope.
### Why?
generateMetadata can be marked with use cache, but Cache Function return
values must be serializable. The metadataBase examples use URL objects,
so the interaction needs co-located guidance for cached metadata.
### How?
Add a Good to know note that recommends returning metadataBase as a
string, such as with url.toString(), and links to the use cache
serialization requirements. Runtime behavior is unchanged.
<!-- NEXT_JS_LLM -->
---------
Co-authored-by: Joseph <joseph.chamochumbi@vercel.com>
### What?
Documents the `pagination` metadata field in the `generateMetadata` API
reference.
### Why?
The `Metadata` type supports `pagination.previous` and
`pagination.next`, but the Metadata Fields documentation did not list
the field.
Fixes#83264
### How?
Adds a `pagination` section with an example and the generated `<link
rel="prev">` / `<link rel="next">` output.
### Tests
- `pnpm prettier --check
docs/01-app/03-api-reference/04-functions/generate-metadata.mdx`
- `pnpm lint-eslint
docs/01-app/03-api-reference/04-functions/generate-metadata.mdx`
- `git diff --check HEAD~1..HEAD`
<!-- NEXT_JS_LLM_PR -->
## Summary
Document that `permanentRedirect()` throws and should be called outside
a broad `try/catch` block.
The `redirect()` and `notFound()` references already explain this
control-flow requirement. The `permanentRedirect()` reference omits it,
so application error handling can accidentally suppress the redirect.
Align the page with the `redirect()` reference by adding a dedicated
Behavior section for the try/catch guidance and temporary-redirect
cross-link.
## Verification
- Prettier, ESLint, and `git diff --check`
- Adding
docs/01-app/03-api-reference/05-config/01-next-config-js/cacheMaxMemorySize.mdx
- Various use cache snippet fixes
- Break nuance for fetch default
- Point to correct turbopack cache flags
The `use-cache-private` suite covered private caches in server
components only, so this adds a route handler that calls one `'use
cache: private'` function three times: a concurrent pair, and a third
call after a short delay. The concurrent pair joins a single in-flight
invocation, while the delayed call lands after the map of pending
intra-request invocations has dropped its entry, so it is served from
the completed entry that the request retains. Each call passes a fresh
object literal, which makes the React `cache()` memo miss on reference
equality so that the lookup falls through to the serialized cache key.
The cache function also reads a cookie, which covers that request APIs
are allowed inside a private cache in a route handler.
A second test asserts that nothing is shared across requests. In
production private entries are neither persisted nor deduped across
requests, so every request re-runs the cache function. In development
they are persisted and forced to `revalidate: 0`, so the next request is
served the stale entry while a fresh one warms in the background, which
the dev branch of the test follows. Both tests pass their own `id`
search param, and therefore use their own cache key, so the entry that
dev persists for one is never served to the other.
This also drops the claim that the directive is not available in route
handlers from the `'use cache: private'` reference. Nothing in the
source rejects it, and it was already allowed for request work unit
stores when that note was written.
- Enrich the directives entry point
- New guide for Server and Client components. Conceptual backing for the
getting started page.
---------
Co-authored-by: Aurora Scharff <aurora.sofie@gmail.com>
### What?
Adds support for passing a module type to the TypeScript overloads for
`import.meta.glob`:
```ts
const entries = import.meta.glob<Entry>("./entries/*.ts");
```
This works with both lazy and eager imports. If a module type isn’t
provided, it defaults to `unknown`, which matches the current behavior.
### Why?
Next.js already supports Vite’s `import.meta.glob` API at runtime, but
its TypeScript declarations don’t support [Vite’s module
generic](https://github.com/vitejs/vite/blob/57fea001d154e7dd8d5d74d3082731f1dcfd31be/packages/vite/types/importGlob.d.ts#L113-L130).
Because of this, a typed glob works in Vite but fails in Next.js:
```text
TS2558: Expected 0 type arguments, but got 1.
```
Consumers currently have to cast the result or add their own
`ImportMeta` declaration to describe the matched modules. Supporting the
generic removes the need for those workarounds and makes typed glob
imports consistent between Next.js and Vite.
### How?
Each existing overload now accepts a generic module type, `M`, which
defaults to `unknown`:
- Lazy imports return `Record<string, () => Promise<M>>`.
- Eager imports return `Record<string, M>`.
Using `unknown` as the default means existing calls without a generic
keep the same types as before.
I also updated the existing e2e fixture. It had an old local
`ImportMeta.glob` declaration that returned `Record<string, any>`.
Removing that declaration exposed the missing generic and allowed the
five `as any` casts in the fixture to be removed. Without the generic,
all five glob calls fail during `next build` with `TS2558`. With it, all
five pass.
The Turbopack documentation has also been updated with examples showing
how to use the generic with lazy and eager imports.
### Checklist
- [x] `pnpm --filter=next types` passes
- [x] `pnpm prettier-fix` run
- [x] Documentation follows the docs contribution guide
- [x] E2E test passes
---------
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
## Summary
- Fix wording in Link prefetch docs: `navigation's` -> `navigation`
- Fix wording in CRA migration guide: `clients components` -> `Client
Components`
## Why
These are user-facing documentation wording issues. This change improves
clarity and readability for readers following these guides.
## Scope
Docs-only changes. No runtime behavior changes.
Attribution: This change was originally authored by @Rezakarimzadeh98 in
#96998.
Co-authored-by: Reza Karimzadeh <r.karimzadeh1998@gmail.com>
With the latest GA changes, when using Cache Components with Partial
Prefetching:
> prefetch={true} turns on prefetching
These changes try to align around that idea.
- runtime prefetching guide is now about optimizing
- various changes in prose across docs
- even fix an error page for pages router, which had a broad title
---------
Co-authored-by: Aurora Scharff <aurora.sofie@gmail.com>
Closes https://github.com/vercel/next.js/issues/96574.
Implements:
> experimental.turbopackMinify accepts per-environment granularity, e.g.
{ server: false, client: true } (restoring serverMinification parity)
I also added the edge runtime in there, even though it isn't too widely
used.
`TurbopackModuleType` has listed `'text'` for a while, but the config
schema
and `ConfiguredModuleType::parse` both rejected it. It now maps to the
existing
`TextSourceTransform` (the same thing `import … with { type: 'text' }`
uses).
`'json'`, which Rust already accepted, is added to the TypeScript type
and
schema too.
`type: 'raw'` has always been documented as "Return raw contents as
string",
but it mapped to an opaque module with no exports instead — that
mismatch is
why `import * as ns from './alpha.md'` with `{ '*.md': { type: 'raw' }
}`
evaluated to `undefined`. `raw` is now an alias of `text`: both run the
file
through `TextSourceTransform` and export its contents as a string.
Neither
name is deprecated; `text` remains the descriptive spelling that matches
the
`import … with { type: 'text' }` import attribute, `raw` keeps working
exactly
as documented. No configuration value maps to the opaque module type
anymore;
`type: 'asset'` remains the way to emit a file and get its URL.
This is also what makes Vite's `?raw` workable: Turbopack has no
built-in
`?raw` handling, so a `{ condition: { query: '?raw' }, type: 'text' }`
(or
`'raw'`) rule is what turns those files into strings. Docs previously
implied
`?raw`/`?url` worked standalone; that's corrected, and the module type
table
is completed and describes `raw`/`text` as equivalent.
Reading a binding off a module that genuinely can't be placed in an
ECMAScript chunk (a stylesheet, a native addon, …) used to evaluate to
`undefined` with no diagnostic; it now reports an error naming the
module.
Side-effect-only imports still work.
<!-- NEXT_JS_LLM -->
---------
Co-authored-by: vercel-fleet[bot] <308483924+vercel-fleet[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
## What
Updates docs links whose targets have moved. Found by an automated docs
link audit: every old URL below currently serves a `308` (permanent
redirect), so readers take an extra hop and the links rot as redirect
maps are pruned.
Each replacement is the **final `200` URL** of the redirect chain
(verified with `curl -sIL`), not just the first hop. Only the path
component was rewritten — scheme, host, query strings
(`?utm_source=...`) and fragments are untouched.
| Old path | Now points at |
| --- | --- |
|
`vercel.com/docs/concepts/functions/edge-functions/og-image-generation`
| `/docs/og-image-generation` |
| `vercel.com/docs/frameworks/nextjs` |
`/docs/frameworks/full-stack/nextjs` |
| `vercel.com/docs/observability/otel-overview` |
`/docs/tracing/instrumentation` |
| `vercel.com/docs/concepts/observability/otel-overview/quickstart` |
`/docs/tracing/instrumentation` |
| `vercel.com/docs/edge-config/get-started` |
`/docs/global-config/get-started` |
| `vercel.com/docs/functions/vercel-functions-package` |
`/docs/functions/functions-api-reference/vercel-functions-package` |
| `vercel.com/docs/edge-network/overview` | `/docs/cdn` |
| `vercel.com/docs/storage/vercel-blob` (×4) | `/docs/vercel-blob` |
| `vercel.com/docs/storage/vercel-blob/server-upload` |
`/docs/vercel-blob/server-upload` |
| `vercel.com/docs/storage/vercel-blob/client-upload` |
`/docs/vercel-blob/client-upload` |
| `vercel.com/docs/concepts/image-optimization` |
`/docs/image-optimization` |
| `nextjs.org/docs/app/building-your-application/rendering` |
`/docs/app/getting-started/caching` |
The four Pages Router pages flagged by the audit (`deploying`,
`guides/instrumentation`, `guides/open-telemetry`, `guides/redirecting`)
share their source with the App Router versions via frontmatter
`source:`, so fixing the App Router files covers them.
## One link worth a reviewer's eye
In `use-client.mdx`, the client-server **boundary** link pointed at
`/docs/app/building-your-application/rendering#network-boundary`. That
path 308s to `/docs/app/getting-started/caching`, which is what this PR
uses — but the redirect target is topically unrelated to the
client-server boundary, and no `#network-boundary` heading exists on it
(or anywhere else in `docs/`). If you'd prefer,
`/docs/app/getting-started/server-and-client-components` is the closer
page and I'm happy to switch it (the anchor would have to be dropped
either way).
## Also checked: `viewTransition`
The `next-config-js` reference index on nextjs.org still lists an entry
linking to
`/docs/app/api-reference/config/next-config-js/viewTransition`, which
404s. No change is needed in this repo: that page was intentionally
deleted in 28a9465d4 ("docs: view transitions guide — skill section,
source-audit fixes, flag-removal docs", #96097) as the docs half of the
`experimental.viewTransition` flag removal (#96098), and the guide now
states that view transitions work with no configuration. There is no
surviving config option to repoint at, and no link to it remains
anywhere in `docs/` or `packages/*/src` on `main` — the stale index
entry comes from the nextjs.org versioned (v15) nav manifest, outside
this repo.
## Verification
- `prettier --check` passes on all changed files.
- `alex` (`lint-language`) reports only pre-existing prose warnings on
untouched lines.
- No `next.js` build was run; this is a docs-only, path-only change.
▲ Created with [Vercel
devbox](https://vercel.com/vercel/~/sandboxes/devboxes/1us5zdxyl2nk5ccodexgd9w3f6k2)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Joseph <sephxd1234@gmail.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
The event parameter (second position) passed to proxy was not documented
in the Params section
<!-- 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 #
-->
- Rework the
`/docs/app/api-reference/config/next-config-js/turbopackFileSystemCache`
API reference.
- Adjust the table notes in
`/docs/app/api-reference/turbopack#configuration` — `[^1]` works well,
but the table notes are auto-magically placed at the bottom of the page.
Going with this hand rolled table notes for now.
- Tidy the Build Caching section in
`/docs/app/api-reference/turbopack#configuration`
- Update the Upgrade to version 16 section on dev/build cache
- Removed the `Build caching` from Webpack gaps
- Added Filesystem caching to Performance and Fast Refresh