mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/fallback-root-cache
15 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7aa66c345 |
Remove generated error codes (#97687)
### Why? Should come up with better solution that does not block PRs with git conflict x-ref: https://vercel.slack.com/archives/C02CDC2ALJH/p1785263902728189?thread_ts=1785263687.502649&cid=C02CDC2ALJH ### How? - Delete `errors.json`, the error-code SWC plugin, generated WASM, merge driver, and validation/build tooling. - Stop attaching error codes to server-rendering digests, redboxes, and telemetry; native `Error.code` and `Error.name` remain available where applicable. - Remove the development-overlay error feedback UI, middleware, and telemetry event that depended on stable codes. - Update fixtures, snapshots, and guidance for code-free errors and numeric-only digests. <!-- NEXT_JS_LLM --> |
||
|
|
529ddc3c8d |
[test] Unflake two cache-components-dev-streaming assertions (#97246)
The streaming assertion in `should stream suspense boundaries while filling caches in the background` polled a bare `<p>` through `retry()` with its 3000ms default. Because `retry` gives up as soon as `waited + interval > duration`, the effective budget is about 2.6s, and the fixture's cache fill alone takes 2000ms, so the assertion had roughly 600ms of headroom. That budget also had to absorb one browser round trip per attempt, because a selector that matches the fallback as well as the content cannot wait for anything: `waitForSelector` returns the fallback immediately, so the waiting had to happen in the test process at a 500ms granularity. This change gives the two paragraphs the ids `#cached-fallback` and `#cached`, in line with every other route in this fixture, which is what lets the wait move into the browser. Playwright now resolves the moment the content is revealed, in a single round trip, and #95466 had already moved the rest of the suite to that pattern. The two assertions that check what the shell itself delivers still need the original "whichever element arrives first" probe, and they now express it as the selector list `#cached, #cached-fallback` instead of relying on `p` to match both. The budget for the reveal is 10s rather than the 5s that `elementByCss` narrows the harness default to, because the wait has to cover more than the fill. In the linked failure the content bytes had arrived one second after the shell committed, and the reveal was still at least 2.4s away, since the browser was busy evaluating the dev bundle. A shorter fill would not help with that, as it does not shorten the part of the wait that CI actually spends. The convergence assertion in `serves a short-expire cache warm on reload and converges to a fresh value` ran out of the same budget for a different reason: every attempt performed a full dev page reload on top of the 1.5s regeneration, and measured runs needed 1.8s to 3.4s. It now reads the value over HTTP with `next.render$`, the way #97187 does, which observes the same server-side cache state without downloading and evaluating the dev bundle. Under the same contention those reads converge in 1.1s to 1.9s, and they stay there when the load is quadrupled, because only the 1.5s regeneration gates the loop. The default budget therefore covers it, and the test drops from 15.5s to 3.6s. Both fixes were verified by raising the fill to 3.2s to emulate the CI-side delay: the previous assertions then fail deterministically with the CI signature, and the new ones pass. [Flakiness metrics](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.name%3A%22cache-components-dev-streaming%20should%20stream%20suspense%20boundaries%20while%20filling%20caches%20in%20the%20background%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%22fail%22&agg_m=count&agg_m_source=base&agg_t=count&citest_explorer_sort=timestamp%2Casc&cols=%40test.status%2Ctimestamp%2C%40test.suite%2C%40test.name%2C%40duration%2C%40test.service%2C%40git.branch¤tTab=overview&eventStack=&fromUser=true&index=citest&start=1783948256248&end=1786540256248&paused=false) |
||
|
|
3de2d1a213 |
Unify allow-runtime with Partial Prefetching (#96106)
Removes the "allow-runtime" prefetch config, and turns its behavior on implicitly wherever Partial Prefetching is enabled. The original motivation for "allow-runtime" was to give apps more control over server costs triggered by prefetches. Until a route explicitly opts in, prefetches would only be served from the CDN, not from the server. The problem, though, was it was very confusing to know when to add or remove this configuration. The incentive for many apps was to add it everywhere, with no clear signal for when to remove it. Our updated thinking is that Partial Prefetching itself already provides sufficient protection against runaway prefetching costs: per-link prefetches only happen on Link components that explicitly opt in with the prefetch prop. The optimizations landed earlier in this stack also make allow-runtime less necessary: on pages where all the content is statically renderable, prefetches are served from the static cache and no runtime request is ever issued; only a page that accesses non-static data is prefetched at runtime. The upshot of this decision is that runtime versus static becomes an internal optimization; the same content gets prefetched regardless of whether or how Next.js is able to optimize it. |
||
|
|
18d2e2da5f |
Insights: use a single Learn more link in console errors (#95967)
## Summary
The instant / blocking-prerender insight console errors printed a docs
URL under each fix option (two or three anchors per message). This
collapses them to a single `Learn more:` link at the end of each
message, while keeping the `[stream]`/`[cache]`/`[block]` fix-option
labels. It restores the single-link format these builders originally
shipped with.
**Before:**
```
Ways to fix this:
- [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
https://nextjs.org/docs/messages/blocking-prerender-runtime#wrap-in-or-move-into-suspense
- [block] Set `export const instant = false` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-runtime#allow-blocking-route
```
**After:**
```
Ways to fix this:
- [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
- [block] Set `export const instant = false` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-runtime
```
Covers all 16 insight-kind errors, across `blocking-route-messages.ts`,
`sync-io-messages.ts`, `dynamic-rendering-utils.ts`, and
`instant-messages.ts`.
Because the dev overlay classified these errors by the `#`-anchored docs
URL, `getBlockingRouteErrorDetails` is updated to match the anchor-less
`Learn more:` URL. Console-message snapshots and the guidance-data
extraction test are updated to the new format. The dev-overlay fix-card
data (`instant-guidance-data.ts`) keeps its per-card links, since those
are the overlay UI rather than the console message.
## Verification
- `pnpm --filter=next types`
- Snapshots regenerated with `jest -u` for the affected suites
<!-- NEXT_JS_LLM -->
|
||
|
|
4d9e955c01 |
[test] Unflake cache-components-dev-streaming test suite (#95466)
[Flakiness metrics](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%22fail%22%20%40test.suite%3Acache-components-dev-streaming&agg_m=count&agg_m_source=base&agg_t=count&fromUser=false&index=citest&start=1782496431626&end=1783101231626&paused=false) <img width="425" height="170" alt="Screenshot 2026-07-03 at 19 55 48" src="https://github.com/user-attachments/assets/7751d204-1ceb-4cc0-b90e-8f8f70adb803" /> The streaming assertions in this suite used `elementByCssInstant`, which runs `page.waitForSelector` with a 10ms timeout. That budget is below the floor of a Playwright selector query under CI load: the call is a cross-process round-trip to the browser, and in a streaming render the page's main thread is busy processing chunks, so the injected visibility poll frequently cannot complete within 10ms even when the element is already present and visible. Playwright then rejects with a timeout, and the surrounding `retry()` only hides this behind a coarse 500ms poll, leaving the tests racy. We have observed the instant selector throwing immediately after a prior assertion had already confirmed the same element was present. This change replaces those `elementByCssInstant` and `retry` blocks with `elementByCss(selector, { waitUntil: false })`. That gives the selector query Playwright's default 5s budget and its efficient built-in waiting, while still skipping the page `load` event, which (with `waitUntil: 'commit'`) only fires once the slowest cache has filled. The `retry()` wrappers become redundant because every target here is a distinct id from its fallback (for example `#dynamic` versus `#dynamic-fallback`), and the Suspense boundary reveals the content subtree in a single commit, so the moment the element exists its text is already final. The `#cached` assertion keeps an explicit `timeout: 10000` because its cache fill takes about 5s, which would race the default 5s selector timeout. The `retry()` around the bare `<p>` in the `use-cache` test is left in place: there the fallback and the content are both an id-less `<p>`, so the same element's text changes from `Loading...` to a date, and only a retry can observe that transition. |
||
|
|
2850659b74 |
Cache short-expire 'use cache' values across dev reloads (#95362)
Development has recently gained several mechanisms that make `'use
cache'` reloads fast under Cache Components: `'use cache: private'`
entries are persisted in a dedicated in-memory handler,
`cacheMaxMemorySize: 0` uses a real in-memory handler instead of the
no-op stub, and custom handlers are fronted by a fast built-in handler
through the tiered handler. One case was still missing. A value that
opts into a dynamic, client-only life with an explicit short `expire`
(for example `cacheLife({ expire: 0 })`, or the built-in `'seconds'`
profile) was treated as a miss on every reload, for both the built-in
default handler and custom handlers, so reloads re-ran the cache
function and streamed slowly.
The reason is that `expire` is the value's expiration bound, the longest
it may still be served before it has to be treated as expired. That is
its purpose in both dev and production; what differs is which threshold
the built-in in-memory handler enforces. In `next dev` it serves stale
entries up to `expire` to keep reloads fast, whereas in production it
drops them earlier, once past `revalidate`. An `expire` of zero
therefore leaves the dev handler no window in which a reload can be
served from the cache, and the wrapper's serve-vs-regenerate check,
which also keys on `expire`, regenerates instead.
This change extends the same dev-only treatment to those values without
altering their resolved cache life. The built-in default handler now
retains an entry for at least `MIN_PRERENDERABLE_EXPIRE` in dev, a
minimum the custom front handler inherits by being a built-in default
handler, and the wrapper applies the same minimum when deciding whether
to serve or regenerate. That affects the retain and serve decisions
only; the entry keeps its real `expire`, so the staged dev render still
resolves it at the appropriate stage rather than in the shell stage. A
short-`expire` entry is also re-warmed in the background on every
dynamic request render, so a reload serves the previously cached value
immediately and the freshly recomputed one appears on the next reload.
This is the same stale-while-revalidate trade-off already accepted for
the private-cache and `cacheMaxMemorySize: 0` dev optimizations, which
likewise favor a fast reload over serving a value these configurations
would not otherwise cache at all. For custom handlers the re-warm
re-executes the function and writes through to the backing.
Unlike the private-cache case, we deliberately do not force a dynamic
cache life here, because forcing `revalidate: 0` would leak into the
cache life propagated to an enclosing `'use cache'` and trip the
nested-dynamic error with the wrong message. And unlike the size-0 case,
keeping the resolved life alone is not enough, because a short `expire`
is exactly what makes the dev handler drop the entry, which is why the
minimum retention is needed. Because the dev front handler now enforces
that minimum, the tiered handler can no longer evict a stale front entry
by writing `expire: 0` (the minimum would keep it alive), so
`toExpiredEntry` now writes a negative `expire`, which the default
handler recognizes as an eviction sentinel and reports as missing
regardless of the retention minimum. This mirrors the existing
`revalidate = -1` convention, though a negative `expire` means the entry
is dropped rather than served-but-revalidated.
Everything is gated on `process.env.__NEXT_DEV_SERVER`, so production
behaves exactly as before: short-`expire` values keep their real cache
life, and configured handlers are used directly. New development tests
cover the built-in and custom-handler cases, asserting that a cache-miss
navigation shows the Suspense fallback while a cache-hit one does not,
and that a reload serves the cached value yet converges to a fresh one.
---
<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
|
||
|
|
41114a31b8 |
Remove 'silence this warning' from instant validation fix output (#95187)
## What
Removes the `silence this warning` phrasing from the structured fix
lines in instant validation output:
- `- [block] Set \`export const instant = false\` to ~~silence this
warning and~~ allow a blocking route`
- `- [ignore] Set \`export const instant = false\` to ~~silence this
warning and~~ opt the route out of instant-navigation validation`
- `- [ignore] Set \`export const instant = false\` to ~~silence this
warning and~~ opt the dropped segment out of instant-navigation
validation`
## Why
The line called itself a warning while being logged via `Error:`. This
PR originally renamed it to `silence this error`, but that has the
mirror problem: at validation level `warning` or `manual-warning` (or
when the check only surfaces in dev) nothing blocks, so "error"
over-claims.
The verb is also wrong either way. Setting `instant = false` doesn't
silence a problem that still exists. It declares that blocking is
acceptable for the route, so validation stops treating it as one.
Removing the clause leaves wording that is correct at every validation
level and matches the dev overlay cards ("Allow blocking route",
"Disable validation on this route") and the docs sections the lines link
to.
## How
- `blocking-route-messages.ts`, `dynamic-rendering-utils.ts`: drop
`silence this warning and` from the `[block]` lines
- `instant-messages.ts`: drop it from the `[ignore]` lines (unrendered
segment, link prefetch)
- `errors.json`: regenerated, append-only (codes 1394-1405)
- Storybook fixture and 15 test files updated to the new strings
<!-- NEXT_JS_LLM_PR -->
|
||
|
|
3767dfae1f |
test: fork select tests on partialPrefetching (#95279)
Stopgap until we make this a proper part of the CI matrix |
||
|
|
8c6cb0e080 |
[PP] Reveal after ShellRuntime when simulating a Shell Prefetch in dev (#95149)
When `partialPrefetching` is on, the shell that we usually care about
displaying is the App Shell, which is represented by the ShellRuntime
stage. We should only show the cold cache indicator for caches that
happen up until then, and release the client-side promise appropriately.
This PR extends the existing `revealAfterStage` mechanics to account for
this.
> Note that this is incomplete: if we're navigating via `<Link
prefetch={true}>` (or have `partialPrefetching: "unstable_eager"), we
might want to use the Runtime stage instead. I'm leaving those for a
follow up -- representing the common case (a shell prefetch) seems like
the most useful thing.
I've re-worked the `revealAfterStage/holdStreamUntilRevealed` setup into
an object (`DevNavigationKind`) that represents the navigation that
we're trying to simulate -- either an initial load or a client nav. This
gets rid of the invalid `holdStreamUntilRevealed = true` +
`revealAfterStage = RenderStage.Runtime/ShellRuntime` combination and
lets us bring the logic closer to where the stream blocking tricks
actually happen.
I've also done some drive-by refactoring of `streamStagedRenderInDev` to
dedupe some repetitive code, because every task was basically doing the
same thing. I've also moved all `revealAfter.resolve()` calls into
separate tasks -- we were inconsistent about this, and the `Static`
`revealAfter.resolve()` was done together with the next stage, but the
`RenderStage.Runtime` was done in a separate task.
---
Strangely, despite the changes working as expected for the Cold Cache
indicator, I can't actually get link data (`await searchParams`) to
trigger a fallback when navigating, so there's a failing test for that
in `cache-components-dev-streaming.test.ts`. I'm not sure why what's
causing this, but I'm leaving that investigation for a follow-up as
well.
|
||
|
|
f965c00411 |
Insights: drop irrelevant fix cards from instant errors (#94926)
### Why? Two Insight fix cards were misleading: 1. **`generateStaticParams` showed up on every runtime/client-hook insight.** One error covers `cookies()` / `headers()` / `params` / `searchParams`. GSP only applies to `params`; for the others it's noise. Even for `params` it nudges devs to make the route static instead of fixing the immediate error. 2. **`"use cache"` showed up on `connection()` triggers.** Caching `connection()` is contradictory. Both manifest on initial load and in-navigation (the fix-card sets are shared). ### What? 1. **Drop the GSP card** from runtime + client-hook sets. Affects [01-cookies-body](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/01-cookies-body), [03-params-body](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/03-params-body), [90-client-use-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/90-client-use-params), [41-subnav-cookies](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/41-subnav-cookies), [42-subnav-fetch](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/42-subnav-fetch). 2. **Filter `"use cache"`** when the cause is `connection()`. Affects [05-connection-body](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/05-connection-body), [08-connection-body-dynamic](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/08-connection-body-dynamic), [31-connection-in-metadata](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/31-connection-in-metadata), [33-connection-in-viewport](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/33-connection-in-viewport). [06-uncached-fetch-body](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/06-uncached-fetch-body) keeps the Cache card. ### How? - `getCards()` filters the cache card when `cause === 'connection'`. - New `deriveCauseFromCodeFrame()` helper detects `connection(` on the highlighted code-frame line. - `ParamClientHookDynamicError` collapsed into `ClientHookDynamicError`. - CLI/build messages: dropped the GSP bullet; added `(does not apply to \`connection()\`)` on the cache bullet. - Docs: removed `For known params, prerender` sections; added a connection caveat on dynamic/metadata-dynamic/viewport-dynamic pages. <!-- NEXT_JS_LLM_PR --> |
||
|
|
96e9a8e246 |
Make cacheMaxMemorySize: 0 and custom cache handlers fast in dev (#94784)
When Cache Components is enabled, `next dev` treats a `'use cache'` value as a miss and renders as if the cache were empty whenever the read does not resolve right away. Two development configurations triggered that even for values that were already cached, so warm reloads streamed slowly instead of serving the cached value: `cacheMaxMemorySize: 0` replaced the built-in default handler with a no-op stub, so nothing was cached at all, and custom cache handlers with a slow or remote `get` did not return in time. For the size-0 case, development now uses a real in-memory handler instead of the no-op stub, and the `'use cache'` wrapper forces a dynamic cache life (`revalidate: 0`, a 5-minute `expire`) for it, the same treatment private caches already receive, so every read serves the stale entry and re-warms a fresh one in the background. This also fixes the dev private handler, which was sized from `cacheMaxMemorySize` and so degraded to the no-op stub whenever `cacheMaxMemorySize: 0` was set. Custom cache handlers keep their configured cache life, since their backing owns it. Instead we put a fast built-in in-memory front handler in front of the configured one through the new `TieredCacheHandler`, which serves warm reads from the front, writes through to both tiers, and reconciles the front against the backing in the background, evicting the front entry when the backing no longer has it (the handler interface has no per-key delete, so it overwrites the entry with an already-expired copy). These dev-only handlers are kept out of the registered handler set and merged in only where tag operations iterate, so `revalidateTag` still reaches them. Everything is gated on `process.env.__NEXT_DEV_SERVER`, so production is unchanged: `cacheMaxMemorySize: 0` still caches nothing, private entries are still never persisted, and configured handlers are used directly. New development test suites cover the size-0 and custom-handler behavior. |
||
|
|
66496a4c56 |
Avoid a premature Suspense fallback flash in the streaming dev render (#94783)
The Cache Components streaming dev render streams the response while filling caches in the background, but when those caches are already warm it should behave like a production render, delivering the cached content as part of the shell rather than behind a Suspense fallback. The render previously held the stream back until the shell stage had been buffered before releasing it. That was enough for the SSR render, whose Fizz pass consumes the buffered shell server-side, but not for the browser. The browser decodes the response as it arrives over the network in multiple chunks, and across those chunks the Flight client can surface the tree to React before a boundary's children row has been processed, so React reads the still-pending children, suspends, and commits the boundary's fallback, then swaps in the content once that row is processed. Holding the stream server-side doesn't fix this, because the bytes still reach the browser in multiple network chunks; the race is in how the client processes them. The flash showed up on warm navigations to a runtime-prefetch route. This change releases the stream to the browser live and exposes a dev-only `_revealAfter` promise on the RSC payload to close that race. Fully buffering the response on the client, so the Flight client processes it in a single chunk, would avoid the race, but a streaming navigation can't be buffered without blocking it on all the dynamic content that streams in after the shell. Instead, the server resolves the promise once the shell-stage content (the static shell, or the runtime-prefetchable shell for runtime-prefetch routes) has been flushed to the stream, or earlier on a cache miss, since a cold render can't be prod-representative anyway and we would rather let everything stream as fast as possible. A client navigation gates revealing the response on it, deferring resolving the response's deferred RSCs until it settles. Because the promise's resolution row follows the children's row in the RSC payload, the children's row has already been processed by the time the client unblocks, so the fallback no longer appears. The SSR render keeps the original hold. The `_revealAfter` reveal gate is a client-side mechanism and doesn't apply to the HTML render, which consumes the same payload to produce the initial HTML and would otherwise stream a boundary's fallback before its content arrived. For that consumer the render holds the stream until the shell-stage content has flushed, so the HTML reflects the prerendered shell rather than a premature fallback. A first navigation to a not-yet-known route needs separate handling. Such a navigation has no prior cache entry, so the server returns the new subtree's content inline in the navigation response rather than as a deferred RSC, which means the deferred-RSC gate doesn't cover it. `navigateToUnknownRoute` now awaits `_revealAfter` (decoded from that response) before building the navigation tree, so the inline content is decoded by the time React reads it, the same gate the known-route path applies to its deferred RSCs. On the server, `streamStagedRenderInDev` resolves `revealAfter` at the shell boundary (or earlier on a cache miss) in one task and advances into the dynamic stage in the next, so the resolution row flushes ahead of the dynamic chunks and the client unblocks as soon as the shell is ready rather than only while the dynamic content streams. The shell-boundary stage, previously `streamReleaseStage`, is renamed `revealAfterStage` to match the new mechanism. A regression test in `cache-components-dev-streaming` hard-reloads the home page and then navigates to the runtime-prefetch route many times, exercising both the unknown-route inline-seed first navigation and the known-route deferred-RSC path, and asserts that the private cache's fallback never enters the DOM. The SSR path remains covered by the existing `ppr-root-param-fallback` test, which checks that a warm initial render places the cached content in the shell rather than its `Suspense` fallback. |
||
|
|
5b0aa04b10 |
Persist 'use cache: private' entries in dev (#94694)
Private caches were never stored in a cache handler, so every reload in `next dev` re-ran them from scratch and they registered as a cache miss on each load. This change persists `'use cache: private'` entries in development in a dedicated built-in in-memory handler so that warm reloads are fast. The handler is gated on `process.env.__NEXT_DEV_SERVER` and is kept out of the kind-keyed handlers map so it can never be replaced by a user-configured `default` handler: private cache entries can hold data specific to the incoming request (for example, derived from its cookies or headers) and must never reach a remote or otherwise persistent handler. Production keeps private caches non-persisted as before. The coarse cache-handler key for a dev private cache is scoped by the request's cookies and headers so entries for requests with different request data don't collide. It excludes Next-internal cookies that aren't application data (the HMR refresh hash, already part of the cache key, and the instant-navigation cookie, which toggles while a navigation lock is held) and the transport and content-negotiation headers that vary between otherwise-equivalent requests (a browser reload adds `cache-control`, and `accept` and `sec-fetch-*` differ between an HTML navigation and an RSC request). Keying by only the cookies and headers a cache actually reads is left as a follow-up; read root params are already tracked that way, the same as for public caches. The cache life is forced to `revalidate: 0` with a 5-minute `expire`, so each read serves the stale entry immediately and warms a fresh one in the background through the existing stale-while-revalidate path. Cross-request deduplication now applies to private caches in development too, so concurrent requests with identical request data share a single fill; it remains skipped in production where request-specific data must not be shared across requests. To make this deterministic, `saveToCacheHandler` resolves the metadata a cross-request joiner awaits only after the entry has been written to the handler, so the joiner finds it when re-reading its recomputed key. This closes a pre-existing race in that path, present for public caches too but never surfaced by their cross-request test, where the joiner's metadata could resolve before the handler write had landed. The defensive invariant that rejected reading a private entry from a handler is removed: it only existed to narrow `cacheContext.kind` for a code path that no longer needs it, and dev now legitimately reads persisted private entries while production never registers a private handler to read from. |
||
|
|
5b99d26df8 |
instant: polish client-hook overlay wording, cards, and docs links (#94496)
### What? Polishes the dev-overlay UX for the client-hook prerender error after Josh's framework fix landed in [#94494](https://github.com/vercel/next.js/pull/94494). The overlay now names the hook in the headline and shows per-hook fix cards. ### Why? Different hooks need different fixes. `useSearchParams` always suspends, but `generateStaticParams` doesn't apply to it. `useParams` is the only hook GSP resolves at build time. `usePathname` and `useSelectedLayoutSegment(s)` need a Suspense boundary or the `[block]` export. ### How? - Headline now reads "Next.js encountered URL data `useX()` in a Client Component outside of `<Suspense>`", matching the body factory wording. - Per-hook card sets in `instant-guidance-data.ts`: `useSearchParams` → Stream + Block; `useParams` → Stream + GSP + Block; `usePathname` / `useSelectedLayoutSegment(s)` → Stream + Block. - Build-time message in `ClientHookDynamicError` / `ParamClientHookDynamicError` matches the overlay card set. - Companion docs page: [vercel/front#72622](https://github.com/vercel/front/pull/72622). ### Verification Demo scenarios on the [error-messages-overhaul test app](https://error-messages-overhaul-ibsl.labs.vercel.dev/): [88-client-use-pathname](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/88-client-use-pathname), [89-client-use-search-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/89-client-use-search-params), [90-client-use-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/90-client-use-params), [91-client-use-selected-layout-segment](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/91-client-use-selected-layout-segment), [92-client-use-selected-layout-segments](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/92-client-use-selected-layout-segments). <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Josh Story <gnoff@storyposted.com> |
||
|
|
6f4d94ac88 |
Stream Cache Components dev render instead of restarting on cache miss (#94457)
When Cache Components is enabled, `next dev` previously simulated a
production loading experience on every cold request. The render did a
prospective pass to detect cache misses, and on any miss it waited for
every cache to fill via `cacheSignal.cacheReady()` and then restarted
the render with warm caches before streaming anything, so the browser
saw nothing until the slowest cache had filled. Every cold load blocked
on cache population.
This change replaces the restart-on-cache-miss flow with a single
non-abandoning staged render that streams immediately and fills caches
as a side effect. On a cold load the Suspense fallbacks stream right
away and the cached content resolves as its cache fills; on a warm
reload the staged progression matches the previous no-cache-miss path.
The render is split into clearly owned pieces: `setUpStagedDevRender`
builds the staged controller, cache signal, and resume cache;
`streamStagedRenderInDev{Node,Web}` runs the streaming render and
reports a result once the stream has fully finished; and
`stagedRenderWithCachesInDev{Node,Web}` returns the stream and leaves
the validation follow-up detached so it never blocks the response.
The render advances its stages in sequential tasks, and the stream is
not handed back the instant it exists: it is held until the render has
advanced through the stage whose content belongs in the shell. That is
the static stage for initial loads, HMR refreshes, and plain
navigations, or the runtime stage for client navigations to a route with
a runtime prefetch config, whose runtime-prefetchable content the
navigation's prefetch would have settled. It never waits for the dynamic
stage. Buffering the shell before the first flush keeps the streaming
renderer from emitting a premature Suspense fallback for content that
belongs in the shell, and it mirrors production, where the static shell
(plus runtime-prefetchable content where configured) is served and the
remaining holes stream in as fallbacks.
Two internal reads that would otherwise register as synchronous IO and
wrongly force the render to the dynamic stage, the cache handler's
tag-expiry clock check and the hot reloader's module-scope dev client
id, are now read untracked: via `performance.timeOrigin +
performance.now()` like the `'use cache'` handler, and only in the
browser where the HMR connection reads it, respectively.
Cache Components rules validation now runs in that background follow-up,
once the streamed render has fully settled. `planDevValidation` inspects
the finished render and picks one of three paths: forward an invalid
dynamic usage error the streamed render already recorded and stop (for
example a request API used inside `'use cache'`); validate the streamed
render's own chunks when it neither missed caches nor hit sync IO; or,
when it did either, validate a dedicated warm-cache render instead.
Because that warm render reads the filled caches back rather than
filling them, it can surface an invalid dynamic usage error the cold
streamed render cannot, such as a nested dynamic `use cache` cache life
that propagated to a parent with no explicit `cacheLife`; that error is
forwarded and validation is skipped, just as one recorded by the
streamed render is.
Since cold loads no longer block on cache fills, the transient
cache-status indicator that reflected that wait is no longer emitted; a
follow-up will instead add an indicator that tells the user whether a
render streamed with cache misses, and so wasn't representative of
production.
|