Andrew Clark
90d · built 2026-08-09
90-day totals
- Commits
- 47
- Grow
- 8.9
- Maintenance
- 7.8
- Fixes
- 2.1
- Total ETV
- 18.8
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 88 %
- By Growth share
- Top 47 %
30-day trajectory
Last 30 days vs. the 30 days before. Up arrows on Growth and ETV mean improvement; up arrow on Fixes share means more time on fixes (worse).
↑+100.0 %
vs 11 prior
↑+15.6 pp
recent vs prior
↑+8.2 pp
recent vs prior
Daily performance
Daily ETV, stacked by Growth, Maintenance and Fixes.
Work-mix over time
Share of Growth / Maintenance / Fixes over a rolling 7-day window. Reads as 'where is effort flowing right now'.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 20 by ETV in the 90-day window.
- 2.6ETVAttempt static prefetch before resorting to runtime (#96095) If we're reasonably confident that a segment can be prefetched statically without omitting data that would have been included during a runtime prefetch (e.g. cookies), the client should attempt prefetch the segment statically instead of going straight to a runtime request. The decision for whether to do this is based on the ShouldAttemptStaticPrefetch added in previous steps. If it turns out the static response is not sufficient, then it will fall back to a runtime request. This makes prefetching cheaper for pages that are fully statically renderable. We can make the optimization better in the future with more reliable per-segment computation of the ShouldAttemptStaticPrefetch, but the current approach should at least work for fully static pages, which is what's most important. This optimization applies during both the Shell phase and the Speculative phase of the prefetching algorithm.github.com-vercel-next.js · dfa7f4f7 · 2026-07-28
- 1.2ETVUnify full/partial navigation response types (#96439) Introduces a new type, TransportData, to replace FlightDataPath. The new format is the same regardless of whether the entire page is rendered by the server or if some segments are intentionally omitted. The type also unifies FlightRouterState and CacheNodeSeedData into a single tree that includes both route information and RSC data. Previously these were sent in two separate trees that were isomorphic by convention, requiring the consumer to walk both in parallel. These new types are intended to be transport formats only. The client already has its own, richer data structure called RouteTree. The transport format is converted into the client format at the network decoding boundary. This does not remove FlightRouterState from the codebase entirely; it's still used both for issuing requests to the server (Next-Router-State-Tree) and for tracking state in the browser's history object. A future refactor may replace it for the purpose of issuing requests, but it will likely survive as the format for tracking state in history. This PR does not yet update the server to produce TransportData directly; a temporary adapter layer is used to convert from the old data formats to the new format. This intermediate step will not land on its own; the next PR in the stack will both update the server and delete the temporary adapter layer.github.com-vercel-next.js · c37b7368 · 2026-08-07
- 1.1ETVTrack whether runtime data is accessed during prefetch (#95964) Adds a server-computed signal to static per-segment prefetch responses that tells the client whether a runtime prefetch request would return more content than the static response already contains. The signal is computed by tracking whether the prerender accessed any data source that hangs during a static prerender but would resolve during a runtime prerender. For example: cookies, headers, fallback params, and search params. The information is encoded two places: - As a prefetch hint called ShouldAttemptStaticPrefetch. This tells the client that it's worth attempting to do a static prefetch instead of a runtime one. It's only an optimization, though: if the static response ends up being insufficient, the client will follow it up with a runtime request. It's semantically OK if there are false positives. - Embedded in the static segment response. This tells the client whether the static response is missing data that would have been included in a runtime response. Unlike the prefetch hint, this value must never falsely claim that no runtime request is needed. A follow-up will update the client to skip the runtime request using this information. This commit only encodes the signal into the response.github.com-vercel-next.js · 3cd6d4dd · 2026-07-27
- 1.0ETV[Flight] Add 'pending_weak' to Flight thenable protocol (#37154) Added behind a new experimental flag, `enableFlightWeakThenables`. Adds a new thenable status to the Flight protocol: `'pending_weak'`. Unlike a regular pending thenable, a weak thenable does not block the stream from closing. If it settles while the stream is still open, its value is emitted like a normal pending thenable. Otherwise its reference is left unfulfilled and on the client it stays forever pending, without erroring, even when the connection closes. It's up to the client to handle the unresolved promise in an appropriate way. The motivating use case is being able to encode metadata about a Flight stream into the response itself. For example, a framework might want to track whether a page varies by search params. It could represent this in the response as a `Promise<boolean>` that resolves to `true` as soon as the component being rendered in the stream accesses search params. If the thenable never resolves by the time the stream closes, then the client knows that no search params were ever accessed. In the future we could add a higher-level API for encoding this kind of information. For now, we intentionally start with the low-level primitive so frameworks can experiment in userspace without adding significantly to React's surface area. Internally, Flight already uses its own private thenable statuses, like `'resolved_model'`, and the protocol is designed to treat any status besides `'fulfilled'` and `'rejected'` as equivalent to `'pending'`, so `'pending_weak'` slots into the existing machinery. On the wire, a weak reference is encoded as `$w<id>`, next to `$@<id>` for regular promises, so the client knows its row may intentionally never arrive. On the client, a weak reference behaves like any other pending promise until the response closes; then, instead of erroring, it is left forever pending.github.com-facebook-react · 9b5b4d51 · 2026-07-31
- 1.0ETVServe ISR fallback shells in response to prefetch requests (#94534) Previously, a static segment prefetch that hit a route with an unresolved ISR entry could not be served a fallback shell, so the prefetch cache was left cold until the entry regenerated. This enables prefetch requests to be served the ISR fallback shell immediately, so the prefetch cache can be warmed right away instead of waiting. Because the shell is only a partial response, the client retries the prefetch a bounded number of times so it eventually warms the cache with the full (concrete) response once the server finishes regenerating in the background. Only shells that can actually be upgraded are retried — a route with no generateStaticParams never upgrades, so its shell isn't flagged and the client doesn't waste retries on it. The new serving behavior is gated behind the experimental `appShells` flag, so existing behavior is unchanged when it's off. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 70407f12 · 2026-06-23
- 0.7ETVBlock prefetch task until sufficient response is received (#96017) This is a refactor of the prefetching algorithm to block the prefetch task from completing until the response is received. Currently, a prefetch task is fire-and-forget: once a prefetch's requests are spawned, the scheduler marks the originating task as complete regardless of what the server ends up returning. However, certain planned optimizations require us to verify the response from the server to determine whether additional work is necessary. For example, we may attempt to do a static prefetch instead of a runtime prefetch if we optimistically assume that the static prefetch does not vary on runtime data. We need to verify that assumption is correct before marking the task as complete. Now, whenever a pass over the task spawns a segment request, or encounters an entry whose response hasn't arrived yet, the task registers itself on the entry and exits as Blocked instead of Done. When the entry resolves, the blocked task is pinged and the pass re-runs against the received data. Only a pass that observes every response it cares about advances the task to the next phase or completes it. Rejected entries are handled per segment: a rejection counts as an observed response, so the pass skips that segment and keeps prefetching the rest. The task never registers on a rejected entry, since nothing ever pings one. Retries are governed by the existing per-entry logic. Also fixes a cache bug this exposed: when a revalidation response is keyed at a more generic vary path than the stale partial entry that prompted it (because the segment turned out not to vary on some param), the stale entry shadowed the result on every lookup — the upgraded data was unreachable and the revalidation was wasted. With blocking, that dead end became an infinite loop: each fulfillment re-pinged the blocked task, which re-read the same stale entry and spawned another revalidation. Upserting (or re-keying) a segment now deletes settled entries at more specific vary paths that the incoming entry supersedes. The router-act test helper's "at least one request initiated" watchdog is adjusted to account for the new blocking behavior: countable requests can now be legitimately gated on an in-flight App Shell response (which the watchdog intentionally doesn't count). On expiry it now proceeds to response processing if the act scope intercepted a shell request, and keeps waiting while shell requests are in flight elsewhere on the page, instead of timing out.github.com-vercel-next.js · bdbbb1e8 · 2026-07-27
- 0.7ETVFix repeated navigations while the Instant Navigation lock is held (#95864) With the Instant Navigation Testing lock held (the Navigation Inspector paused), repeated client navigations between pages that share a layout could sometimes get stuck without ever resolving. The root cause turned out to be a subtle problem with the client router's use of `useDeferredValue` to switch between the cached UI and the final UI. The mechanism only works as we intend when mounting a new part of the tree. When updating an existing part of the tree, like a shared layout, React will skip the cached UI entirely. We were already aware of this issue with the `useDeferredValue` approach. The plan is to instead model cached navigation as an optimistic update followed by a transition update. Before we can make such a change, though, we need to make a change in React to allow optimistic updates to suspend (currently, optimistic updates act like sync updates, and can trigger existing Suspense boundaries to switch back to their fallback state). To workaround this issue when using the Navigation Inspector, we're going to change how the inspector works when multiple navigations are performed during the same "lock". Instead of keeping the entire sequence blocked until the end, we will instead only block the most recent one. This more closely matches what the inspector UI implies, anyway: it doesn't show a stack of pending navigations, only the most recent one. This doesn't absolve us from needing to switch to using an optimistic update. That's needed regardless to address similar behavior in production. But this sidesteps the issue for now, and we were planning to make this change to the Navigation Inspector regardless. Co-authored-by: Sam Selikoff <sam.selikoff@gmail.com>github.com-vercel-next.js · 4395d7c8 · 2026-07-17
- 0.6ETVinstant(): Only render shell, unless prefetch prop is set (#95150) When using the instant() API, or the Navigation Inspector, the prefetched state should match what a user is most likely to see in production with a warm cache. So, when Partial Prefetching is enabled, and the Link does not have a prefetch prop, only the shell should be allowed to render. Before this PR, the _entire_ prefetched state would display, regardless of the prefetch configuration of the route and the link. This is essential to prevent false negatives in tests that assert on the prefetched state: the test must not depend on data that wouldn't be prefetched in a real production environment. As part of the fix, I also updated the logic to ignore any cache entries that were already present before the `instant()` lock was acquired, to ensure the test is not "polluted" by earlier prefetches or navigations. Theoretically we could optimize this in the future but this is the cleanest way to ensure this property for now. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · dcf649fd · 2026-06-25
- 0.6ETVPartial Prefetching: Default to App Shell only (#94510) When Partial Prefetching is enabled (unstable_prefetch = 'partial'), this changes the default behavior of Link to only prefetch the App Shell of the target page, not the page data. Per-page data is only prefetched if the Link's `prefetch` prop is set to `true`. For dynamic/partially static pages, this roughly matches the pre-Cache Components behavior: links to those pages never include page content, only a reusable App Shell defined by `loading.tsx` (if defined). For fully static pages, this is a significant behavior change: the prior behavior of Next.js was to prefetch the entire static page, regardless of whether `prefetch={true}` was set. As a default behavior, this made sense for certain kinds of content-heavy sites, like blogs, where most of the content is expected to be generated at build time. These apps will now need to set the prefetch prop to `true` to maintain the previous behavior. The main motivation is to simplify the cost/performance model for prefetching: rendering a Link is now "free" unless `prefetch={true}` is set. I've put "free" in quotes because even if `prefetch={true}` is not set, Next.js will still prefetch a generic App Shell. However, this App Shell only needs to be rendered once per filesystem route, as opposed to once per link. So it's not prefetching in the usual sense that is meant in the context of SPA-based web applications. It's more like incremental bundle loading, except instead of loading just the code for the route, we also load the generic UI. To aid in incremental adoption of this feature, this also adds an "eager" prefetch mode that can be set on any layout or page (unstable_prefetch = 'unstable_eager'). When "eager" is set, every Link to that route is assigned an implied `prefetch={true}`, restoring the pre-Partial Prefetching behavior. Blog-like sites may find this useful, but it's mostly intended to support migrating existing apps. (For canary testers of Partial Prefetching, you can also enable eager mode globally by setting `partialPrefetching: 'unstable_eager'`. This is not recommended except for migration purposes, even for blog-like sites.) <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 16b44a85 · 2026-06-08
- 0.6ETVSupport vary params rewinding (#94809) Refactors how vary params are serialized in the Flight stream so that it's compatible with stage rewinding. The immediate motivation is preparation for allowing root params to be accessed in the App Shell, but this new approach will work more generally for any other kind of rewinding we add in the future. The design is similar to how stale time is already serialized: each access of a param is written to an AsyncIterable that is embedded into the Flight stream. When decoding the params, the client first buffers the entire stream, then synchronously drains the iterable using the special `status` and `value` fields used by the React protocol, until it reaches a suspended entry. This is compatible with rewinding because the stream can be cut off at any point and the client can still decode all the param accesses up to that point. As of this PR, this is a pure refactor: it won't change any observable behavior until the server starts allowing root params in the shell. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · b0b4a501 · 2026-06-17
- 0.6ETVPrefetch App Shells on the client (#93999) Gated behind `experimental.appShells`. Clicking a link to a route the user has never specifically prefetched now renders the route's App Shell instantly; the per-link concrete request continues in the background and streams in the param-specific content. ### Motivation Today, prefetching a parameterized route like `/chat/[id]` requires a concrete `id`. The framework caches a separate prefetch entry per link, so the cost of prefetching scales with the number of visible links — not with the number of routes. On a page with high link cardinality (a feed, a search-results page, a chat list) it is impractical to prefetch every concrete URL up front. If a per-link prefetch for `/chat/123` is still in flight when the user clicks the link, the navigation blocks until that prefetch completes; there's no cached generic shell to fall back to. The cost of a prefetch miss is a full blocking navigation. The property we want: once a user has visited a Next.js app, every subsequent navigation should transition to _something_ instantly — at minimum an App Shell — regardless of whether per-link prefetches for the concrete destination have completed. This matters most under adverse conditions (slow networks, offline, high-cardinality routes), but the guarantee is unconditional. App Shells make that property hold. A shell is a per-_route_ resource, not a per-link one — the number of shells in flight at any time scales with filesystem routes, which is bounded and small. Aggressive App Shell prefetching is affordable in a way that aggressive per-link runtime prefetching is not. ### Mechanism A new `Shell` phase sits in the prefetch scheduler between the existing `RouteTree` and `Speculative` (formerly `Segments`) phases. Shell-phase tasks issue an App Shell request and write the response under a param-independent vary path. Concurrent shell tasks for sibling links to the same route dedupe at this keypath, so the cache holds at most one shell entry per route. The headline property: if N links on a page resolve to the same route under different params, they share _one_ App Shell request collectively. Once it lands, every one of those navigations can render an instant shell, regardless of whether the param-specific concrete prefetch has completed. The Speculative phase is mostly unchanged — it still issues the per-link concrete prefetches that fill in param-specific content over time. Routes that are fully static (no runtime data anywhere in their tree) skip the Shell phase entirely, since their existing static prefetches are already shell-like in shape. ### Navigation-time cache lookup A small change in how the cache is read at navigation time is what makes the instant-shell guarantee actually hold. The cache normally returns the _most-specific_ matching entry — the right semantics for prefetch dedup, but the wrong semantics for navigation. If a fulfilled shell entry coexists with an in-flight Pending entry for a more-specific keypath that the navigation also matches, the most-specific entry is the empty Pending one, and the navigation would block on it instead of rendering the shell. Navigation now does a two-pass lookup: first prefer Fulfilled entries anywhere along the vary path (so a less-specific shell beats a more-specific Pending), then fall back to the regular behavior if nothing fulfilled is found. Prefetch reads keep the original semantics, since they need to see in-flight entries to dedupe. ### Scope The shape of the prefetch request and the response interpretation differ between static (`PPR`) and runtime (`PPRRuntime`) prefetches. Only the runtime path is covered here. The static path uses a different strategy — rewinding a single response into a shell prefix and a concrete suffix, rather than issuing a separate shell-only request — and will be added in a future PR alongside the server-side byte-offset machinery it depends on. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 1eaa37ec · 2026-05-26
- 0.6ETVbfcacheId: Opt out of state preservation (#93633) When `cacheComponents` is enabled, the App Router preserves state across navigations by rendering inactive routes inside React `<Activity>` boundaries. As a default behavior, this is a huge convenience because it lets you navigate between routes without resetting or losing ephemeral UI state (scroll position, expand/collapse, in-progress form edits). Previously the only way to do this was to explicitly track each state with an external state manager, or hoist it to a parent component. It's still the often the case that you should be explicitly tracking all important UI state, anyway, so that it survives a hard refresh of the app, or the browser window being accidentally closed. For example, forms draft states should be persisted to a server-side database or local storage engine. If you're already doing that, then it doesn't matter so much whether client state is preserved via `<Activity>` boundaries or not. For the long tail of ephemeral state that is not tracked, Next.js's philosophy is that it's a better UX default to preserve as much emphemeral state as possible. It's easier to model the cases where you _do_ want state to be reset on navigation as exceptions, compared to the other way around. However, this is a significant change compared to the pre-Cache Components previous behavior of Next.js, and compared to other web frameworks, and indeed the browser's own native bfcache (which implements state restoration for history traversal navigations only, not push/replace). There's are also lots of existing codebases that may rely on the current behavior, and may break subtly under these new semantics. Even if this new default unlocks better UX patterns, we don't want to force everyone to migrate all their code all at once. So, this PR introduces a drop-in mechanism for opting out of state preservation when navigating to a previously visited route. The API is exposed as `useRouter().bfcacheId`. It's intended to be passed to a React `key`: <form key={useRouter().bfcacheId}> The id is contextual: read from a layout, you get the layout's id; read from a page, you get the page's id. It's stable across back/forward navigations, `router.refresh()`, server actions that call `refresh()`, and search-param- or hash-only navigations — i.e., any time the surrounding segment is preserved. It changes when the segment is freshly created by a push or replace into a different route. An important detail is that the previous id is restored during a back/ foward navigation. So state preservation will still work if you navigate via the browser's back button. Why add this to `useRouter()` instead of giving it its own hook? The intent is communicate that `bfcacheId` is not considered an idiomatic pattern — the recommended fix for "I want this state to reset on navigation" is almost always something else: an explicit reset in a submit handler, or a key derived from the underlying data (e.g., a draft id from the server). `useRouter` is the hook where we expose low-level APIs that are supported but are only recommended for advanced or exceptional cases. (For example, instead of `router.push()`, you should almost always use a `<Link>` component instead.)github.com-vercel-next.js · 56d95137 · 2026-05-12
- 0.5ETVExtract App Shell from static prefetches (#94095) Adds support for extracting an App Shell from a more concrete prerender response. The server sends down a byte offset that represents the subset of the stream that corresponds to the reusable App Shell. This does _not_ yet implement shell extraction for per-segment prefetch responses. Implementing this adds an additional layer of complexity, because those responses are generated during a separate phase of the build process. We do intend to implement this, but it's a non-essential optimization that can come later. This also does not yet implement shell extraction from a navigation response (the Cached Navigations feature), though both features are based on essentially the same mechanism. I'm deferring this to a subsequent PR because some of the existing implementation needs to be rethought in light of the new App Shells based model; for example, the "static stage" boundary might not make sense to track separately from the App Shell. The main practical upshot of the PR is that if you have a fully statically prerendered page with no dynamic holes, that page's App Shell can now be fetched by the client without incurring any runtime server execution cost: the server will return the full static page, and the client will extract the App Shell from that concrete response. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 2aea494e · 2026-06-05
- 0.5ETVUnify RouteTree and CacheNodeSeedData on the client (#96406) The client used to represent a server response as two parallel trees: a RouteTree describing the route structure, and a CacheNodeSeedData tree carrying the rendered output for each segment. The two are meant to be isomorphic, but nothing enforced that — every consumer walked them in lockstep and had to defend against mismatches between them. This PR adds a `data` field to the RouteTree type and makes it generic over a per-segment payload. For a server response, the type is RouteTree<RSCSegmentData | null>. This removes the need to pass a separate CacheNodeSeedData through the client navigation algorithm in ppr-navigations.ts. This is a step toward replacing the RSC response transport format: with the client consuming a single unified tree, the wire format can move to one as well.github.com-vercel-next.js · 4ce4c519 · 2026-08-07
- 0.5ETVRefactor server from CacheNodeSeedData -> TransportData (#96679) The previous PR in this stack introduced TransportData, the unified response format, but produced it via a temporary adapter layer that converted from the old data formats — FlightRouterState and CacheNodeSeedData trees, encoded as FlightDataPath entries. As promised there, this PR updates the server to produce the new format directly and deletes the adapter. Neither FlightRouterState nor CacheNodeSeedData appears in a rendered response anymore; FlightRouterState survives only on the client (router state, history) and as the request tree the client sends to the server, and CacheNodeSeedData is deleted from the codebase entirely. createComponentTree now returns the response's transport tree: each node carries its segment identity, its prefetch hints, and its render output, constructed in place as the tree renders. When a non-PPR prefetch stops at a loading boundary, the subtree below the cut is emitted as structure-only nodes with no render output — the same shape the client already interprets as "fetch lazily". createFlightRouterStateFromLoaderTree is replaced by createTransportTreeFromLoaderTree, which covers the cases where nothing is rendered: router-state-only responses, route tree prefetches, the structure below a loading-boundary cut, and error payloads. The prefetch hints computation is shared with createComponentTree through computeSegmentPrefetchHints so the two producers cannot drift. walkTreeWithFlightRouterState returns the transport tree those producers build: each emit is a transport node, and the levels above it become "skipped" nodes (position acknowledged, no output attached). The FlightDataPath encoding this replaces — repeating [segment, parallelRouteKey] prefixes terminated by a positional data tuple, plus the hack in generateDynamicRSCPayload that sliced the root segment off every path — is deleted, along with the FlightData, FlightDataPath, and FlightDataSegment types. This also deletes the overriddenSegment / canSegmentBeOverridden mechanism, which turned out to be dead code: it could only trigger if a dynamic segment reached the walk as an uninterpolated string, but getDynamicParam throws in that case rather than returning null. Instant validation rebuilds its payload tree natively as well: the builders walk the original payload's transport tree in parallel with the loader tree, taking structure from the former and render output from the validation segment cache.github.com-vercel-next.js · c713d487 · 2026-08-07
- 0.5ETVUnify appShells flag with Partial Prefetching (#95415) The experimental `appShells` flag was added while the feature was being developed. It was not meant to be a public-facing flag. Some of the behavior that was gated behind the `appShells` flag is purely an optimization and can be landed without any gate. There are also some behaviors that we don't want to turn on unless you've opted into the new prefetching model via Partial Prefetching. Arguably these too are internal optimizations, but because they can change the number of requests that are made to the server (e.g. one request for the shell, another for the page data), we will gate these behind Partial Prefetching to minimize the impact on existing applications. The result is that until you opt into Partial Prefetching, you should not see any meaningfully detrimental impact to prefetching costs, regardless of how your app is structured.github.com-vercel-next.js · bc48ef76 · 2026-07-16
- 0.5ETVUnify 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.github.com-vercel-next.js · 3de2d1a2 · 2026-07-28
- 0.4ETVAdd global config to enable Partial Prefetching (#94448) Adds a global config option called `partialPrefetching` that changes the behavior of `<Link prefetch={true}>`. When Partial Prefetching is enabled, only Cache Components are included in a prefetch response. Dynamic data is omitted. This is the same idea as Partial Prerendering for initial page loads, now applied to client navigations. In the initial Cache Components release, Partial Prefetching was enabled for any Link that did not explicitly set a `prefetch` prop. However, if the `prefetch` prop was set to `true`, then the Link retained the old behavior where the _entire_ page was prefetched, including dynamic data. This was to ease the migration path for existing apps that already relied on dynamic data being included in the prefetch. For new apps that have Cache Components enabled from the start, it's suggested to also enable `partialPrefetching`. This PR also adds a per-segment opt-in: a route segment can set `unstable_prefetch = 'partial'` to enable Partial Prefetching just for that segment, so apps can migrate gradually before enabling it globally. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · f5af2845 · 2026-06-08
- 0.3ETVFix metadata title dropped on soft navigation with Cache Components (#95315) With `cacheComponents` + `partialPrefetching`, navigating to an already-prefetched route with a dynamic `generateMetadata` left `document.title` permanently empty until a hard reload. The prefetch cached the head as complete using the server's `isHeadPartial` flag, which is unreliable under Cache Components. Derive the head's partiality from `isResponsePartial` instead, as segment data already does, so a dynamic head is fetched and applied on navigation while a fully static head stays complete. Fixing the flag exposed a latent issue in the prefetch scheduler: during a speculative prefetch, the head was unconditionally runtime-prefetched, which previously went unnoticed because the head was mismarked as complete. The head is now runtime-prefetched only when a segment in the new part of the tree is a candidate for runtime prefetching — it rides along with that request rather than spawning a standalone one. If nothing in the new part of the tree is a runtime prefetch candidate, the head is fetched during the navigation instead. This makes the scheduler's metadata-only request path dead code, so it's removed. Fixes #95268. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 27e225f4 · 2026-07-02
- 0.3ETVAllow root params in App Shell (client) The App Shell is the generic loading state for a route. It does not depend on concrete param values, so it can be used for every navigation to a route before its params are known. We've decided to special case root params: those that appear above the topmost layout for a given route. These are typically used for params that are low cardinality for a given session, such as the locale. So we allow them to appear in the generic part of the UI, even though they technically might change across pages. We already treat root params as special during static generation, by always including them during fallback shell generation. So this decision is consistent with our existing treatment. This PR updates the client to key App Shells by their root params. Currently, the client assumes that the shell never varies on _any_ params. This updates the assumption so that it only applies to non-root params.github.com-vercel-next.js · 643e34a5 · 2026-06-12