Andrew Clark
git@andrewclark.io
90d · built 2026-05-28
90-day totals
- Commits
- 36
- Grow
- 4.2
- Maintenance
- 7.0
- Fixes
- 1.1
- Total ETV
- 12.3
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 90 %
- By Growth share
- Top 53 %
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).
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'.
Bug flow over time
Monthly bug flow attributed to this developer. The left bar (red) is bug impact this dev authored that was addressed in the given month — combining bugs others fixed for them and bugs they fixed themselves. The right bar is fixes they personally shipped that month, split between self-fixes (overlap with the red bar) and fixes done for someone else. X-axis is fix-time, not introduction-time — the Navigara API attributes bugs backward to the author at the moment the fix lands.
- Self-fix share
- 45%
- Bugs you introduced
- 10.0
- Bugs you fixed
- 6.2
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.
- 1.0ETV[Prefetch Inlining] Generate size-based hints on server (#90891) Part 1 of 2. This commit adds the server-side infrastructure for size-based segment bundling but does not change any observable behavior. The client-side changes that actually consume bundled responses are in the next commit. At build time, a measurement pass renders each segment's prefetch response, measures its gzip size, and decides which segments should be bundled together vs fetched separately. The decisions are persisted to a manifest and embedded into the route tree prefetch response so the client can act on them. The decisions are computed once at build and remain fixed for the lifetime of the deployment. They are not recomputed during ISR/revalidation — if they could change, the client would need to re-fetch the route tree after every revalidation, defeating the purpose of caching it independently. Refer to the next commit for a full description of the design and motivation. ## Config experimental.prefetchInlining accepts either a boolean or an object with threshold overrides (maxSize, maxBundleSize). When true, the default thresholds are used (2KB per-segment, 10KB total budget). The auto behavior will eventually become the default. The config will remain available for overriding thresholds.github.com-vercel-next.js · 0e6ab3f5 · 2026-03-13
- 1.0ETVHydrate page loads during instant navigation testing (#91207) Previously, MPA page loads (reload, anchor navigation) while the instant navigation lock was held would skip hydration entirely and hard-reload when the lock released. This meant the page was non-interactive during the locked period and tests couldn't assert on the hydrated React tree. Now the page actually hydrates from the static shell. The server injects a script that fetches the static RSC payload, and the client uses createFromFetch with allowPartialStream so React can hydrate without erroring on the incomplete Suspense boundaries. When the lock is released, a soft refresh fetches the dynamic data (falling back to hard reload if the router hasn't initialized yet). Also refactors the cookie protocol to encode the lock state as a JSON array (`[0]` = pending, `[1, null]` = captured MPA, `[1, {}]` = captured SPA) for better integration with the devtools UI. This is somewhat entangled with the hydration changes so they're grouped together, but can be reviewed as atomic commits.github.com-vercel-next.js · a65c3c86 · 2026-03-12
- 0.9ETV[Segment Bundling] Bundle static prefetches based on size (#91439) This is the final step in the segment bundling system. The first commit ensured correctness when inlining hints are stale or unavailable. The second taught the build-time hint computation to identify which segments can be omitted and how parent data flows through disabled segments to static descendants. This commit makes the server and client act on those decisions: small static segments are combined into a single prefetch response rather than fetched individually. This step is inherently larger than the previous ones. We moved as much complexity as possible into the earlier commits — the hint bits, the pass-through logic, the metadata assignment, the safety invariants — but the server output changes and the client scheduling changes need to land together to keep behavior coherent. ### Unified response model The brute-force inlining path that previously existed is removed. Both the old per-segment behavior and the brute-force "inline everything" mode are now modeled in terms of the size-based system: - **Per-segment (flag off):** each segment's response contains only its own data. This is the behavior when `prefetchInlining` is not enabled, or when a segment exceeds the size threshold. - **Size-based (flag on):** setting `prefetchInlining: true` uses default thresholds (2KB per-segment, 10KB total budget). Segments below the threshold are bundled into their children's responses. Segments above it get standalone responses. - **Brute-force (infinite thresholds):** setting the thresholds to infinity means every segment is bundled into a single response for the entire route. The size-based heuristics should make this unnecessary. There is no separate code path for any of these modes. The same server and client logic handles all three — the only difference is the hint bits computed at build time. ### Metadata bundling The metadata (head/viewport) is bundled into the response of whichever segment is responsible for it, avoiding a separate metadata fetch in most cases. On routes with runtime prefetch, the first runtime segment is responsible — its response already includes the metadata. On purely static routes, the first page terminal with budget room gets the metadata. If no terminal has room, the metadata is fetched separately. ### Independent metadata prefetching When navigating between sibling routes under a shared runtime layout, the layout is already cached and no runtime segment request is needed. But the metadata (head) may differ between siblings. This commit ensures the metadata is always prefetched independently via a runtime request when `SubtreeHasRuntimePrefetch` is set on the route tree, even when no runtime segments need fetching. ### Skipping unnecessary static output Segments with `instant = false` or runtime prefetch enabled don't benefit from static prefetch responses. Now the server skips generating static output for these segments entirely, reducing build output size. The client also skips creating cache entries for them. They still participate in the bundle chain as null placeholders — parent data flows through them to static descendants — but no actual data is produced or fetched. This skipping is NOT gated by the `prefetchInlining` feature flag. The hints that drive it (`HasRuntimePrefetch`, `PrefetchDisabled`) are computed at build time and embedded in the route tree regardless of bundling. ### Test plan The prefetch-inlining test suite covers: basic inlining chains, large segments that break chains, deep chains, parallel routes, dynamic routes with concrete params, runtime prefetch boundaries (both leaf and pass-through), `instant = false` segments, stale hint recovery, metadata assignment, the `instant = false` at root fallback, independent metadata prefetching for routes with runtime data in the head, and runtime parallel slot pass-through. Each test verifies both the hint computation (via snapshot assertions) and the actual navigation behavior (prefetch via link accordion, then navigate to confirm data was prefetched).github.com-vercel-next.js · a0446c5a · 2026-03-26
- 0.8ETVFix: Improved rewrite detection during optimistic routing (#93619) When optimistic routing is enabled, the client uses responses it receives from the server (during a navigation or a prefetch) to resolve the routes of structurally similar URLs. However, if the server rewrites a URL to a different path than the one requested by the client, then we disable optimistic routing for that pattern. We detect a rewrite by comparing the route tree returned from the server to the requested URL. If the route tree does not match, according to the Next.js routing algorithm, then the route must have been rewritten; we do not write this pattern into the client's route table. The basic mechanism for this was already implemented, but there were some cases we weren't handling correctly: - A static or non-optional dynamic segment is reached with no URL part to consume. - A page leaf is reached with URL parts still pending. - A dynamic segment whose URL part matches one of its known static siblings (which would have taken precedence under normal routing). These cases have been fixed. Regression tests live in `optimistic-routing-rewrite-detection-regression/` and `optimistic-routing-parallel-slot-catchall-regression/`. --- Replaces #93578 (re-opened from an `origin` branch to satisfy CI requirements).github.com-vercel-next.js · 8980f3a7 · 2026-05-08
- 0.8ETVAdd `unstable_dynamicStaleTime` route segment config (#91437) ## Background Next.js has an existing global config, `experimental.staleTimes.dynamic`, that controls how long the client-side router cache retains data from dynamic navigation responses. By default this is 0 — meaning dynamic data is always refetched on every navigation. Some users set this to a higher value (e.g. 30 seconds) so that navigating back to a recently visited page reuses the cached response instead of making a new request. The limitation of the global config is that it applies uniformly to every page in the app. Users have asked for the ability to set different stale times for different pages — for example, a dashboard page that should always show fresh data vs. a product listing page where it's acceptable to show slightly stale data for a smoother navigation experience. ## What this PR adds `export const unstable_dynamicStaleTime` is a per-page version of `staleTimes.dynamic`. It accepts a number in seconds with the same unit and semantics, but scoped to a single page. When present, it overrides the global config for that page — whether the per-page value is larger or smaller than the global default. Static and cached responses are unaffected. When parallel routes render multiple pages in a single response, the minimum value across all parallel slots wins, consistent with how stale times compose generally. Exporting this from a layout is a build error (pages only). ## Relationship to Cache Components The recommended way to control data freshness in Next.js is to use Cache Components (`"use cache"`) with `cacheLife`. This API exists as a simpler migration path for apps that are already using `staleTimes.dynamic` and haven't yet adopted Cache Components. It's prefixed with `unstable_` not because the implementation is unstable, but because it's not the recommended long-term idiom — apps should migrate to Cache Components for more granular and composable control over data freshness. Based on the design from #88609. Co-authored-by: Jimmy Lai <laijimmy0@gmail.com> Co-authored-by: Sam Selikoff <sam.selikoff@gmail.com>github.com-vercel-next.js · 5dd48e35 · 2026-03-16
- 0.6ETVSimplify scroll restoration with shared ScrollRef on CacheNode (#91348) Replaces the segment-path-matching scroll system with a simpler model based on a shared mutable ScrollRef on CacheNode. The old system accumulated segment paths during navigation and matched them in layout-router to decide which segments should scroll. This was necessary when CacheNodes were created lazily during render. Now that we construct the entire CacheNode tree immediately upon navigation, we can assign a shared ScrollRef directly to each new leaf node. When any segment scrolls, it flips the ref to false, preventing other segments from also scrolling. This removes all the segment path accumulation and matching logic. Fixes a regression where calling `refresh()` from a server action scrolled the page to the top. The old system had a semantic gap between `null` (no segments) and `[]` (scroll everything) — a server action refresh with no new segments fell through to a path that scrolled unconditionally. The new model avoids this: refresh creates no new CacheNodes, so no ScrollRef is assigned, and nothing scrolls. Repro: https://github.com/stipsan/nextjs-refresh-regression-repro There is extensive existing test coverage for scroll restoration behavior. This adds one additional test for the server action refresh bug.github.com-vercel-next.js · 83e47acd · 2026-03-14
- 0.6ETV[Segment Bundling] [Scaffolding] Ensure inlining hint correctness (#91320) Inlining hints (which segments to bundle together) are computed once at build time by measuring gzip sizes, then persisted to `prefetch-hints.json`. There are several scenarios where these hints may not be available at render time. This commit addresses each scenario to ensure the client always receives correct hints and never enters a bad state. ### Build-time static pages (stale hints) The initial RSC payload baked into the HTML is generated before hints are computed, since hint computation requires at least one completed render to measure sizes. The server marks these trees with `InliningHintsStale`. The client immediately expires the route cache entry so that the next prefetch re-fetches the correct tree from the `/_tree` response. The segment data doesn't need re-fetching since it was already cached from the initial HTML payload. ### ISR / revalidation Hints are always available from the manifest. A missing entry is an internal error — it means the build pipeline failed to produce hints for a route that needs them. ### Fully dynamic routes at runtime No hints exist and none will ever be computed. Every segment gets `PrefetchDisabled`, which tells the client not to attempt prefetching. This avoids an infinite re-fetch loop that would occur if the client kept trying to fetch "correct" hints. ### Unified response format Also simplifies the segment prefetch response format: every response is now a `SegmentPrefetchResponse` with a top-level `buildId` and a `data` array, treating a single-segment response as a bundle that happens to have length one. This unifies the format so that bundled multi-segment responses (added in a follow-up) require no additional client parsing logic.github.com-vercel-next.js · 13809269 · 2026-03-23
- 0.6ETVexperimental.prefetchInlining: bundle segment prefetches into a single response (#90555) ## Background Next.js 16 introduced per-segment prefetching through the Client Segment Cache. Rather than fetching all data for a route in a single request, the client issues individual requests for each segment in the route tree. This design improves cache efficiency: shared layouts between sibling routes (e.g., /dashboard/settings and /dashboard/profile sharing a /dashboard layout) are fetched once and reused from the client cache, avoiding redundant data transfer. The trade-off is request volume. A route with N segments now produces N prefetch requests instead of one. Users upgrading from older versions notice significantly more network activity in their devtools, even though the total bytes transferred may be similar or lower due to deduplication. Per-segment fetching is still a reasonable default for many sites. These prefetch requests are served from cache, they're fast, and they run in parallel. The main scenario where the trade-off breaks down is deployment environments that charge per-request. But even setting aside cost, there is a theoretical performance threshold where very small segments are better off inlined — the per-request overhead (connection setup, headers, scheduling) exceeds the cost of transferring duplicate bytes. This is analogous to JS bundlers, which inline small modules rather than creating separate chunks, because the overhead of an additional script tag or dynamic import outweighs the bytes saved. ## What this change does This adds `experimental.prefetchInlining`, a boolean option in next.config.js. When enabled, the server bundles all segment data for a route into a single `/_inlined` response rather than serving each segment individually. The tree prefetch (`/_tree`), which provides route structure metadata, remains a separate request — but optimistic routing (#88965) eliminates that request entirely by predicting the route structure client-side. With both features enabled, prefetching is effectively one request per link. The fundamental trade-off is straightforward: inlining reduces request count at the cost of deduplication. Each inlined response includes its own copy of any shared layout data, so two sibling routes will each transfer the shared layout rather than sharing a single cached copy. This is the same trade-off that compilers and bundlers face when deciding whether to inline a function: inlining eliminates the overhead of indirection (here, extra HTTP requests) but increases total size when the same data appears in multiple call sites. ## Future direction The boolean flag is a stepping stone. The observation that there is a natural size threshold below which inlining is strictly better — where per-request overhead dominates the cost of any duplicate bytes — points toward a size-based heuristic, analogous to how compilers choose an inlining threshold. Small segments would be inlined automatically; segments exceeding a byte threshold would be "outlined" into separate requests where deduplication can take effect. For most applications, this would require no configuration. For applications with specific latency or bandwidth constraints, an option to adjust the threshold would let developers tune their position on the requests-vs-bytes curve. Adaptive heuristics based on network conditions are also possible, though further out.github.com-vercel-next.js · 9ce8d136 · 2026-03-01
- 0.6ETVTrack vary params during runtime prefetches (#89297) **Previous:** 1. https://github.com/vercel/next.js/pull/91487 2. https://github.com/vercel/next.js/pull/91488 **Current:** 3. https://github.com/vercel/next.js/pull/89297 --- Most of the vary params infrastructure was already implemented in previous PRs for static prerenders. This wires up the remaining pieces for runtime prefetches — creating the accumulator, setting it on the prerender store, and resolving it before abort — and adds additional test cases covering empty/full vary sets, searchParams, metadata, and per-segment layout/page splits with runtime prefetching.github.com-vercel-next.js · 568c7029 · 2026-03-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.5ETVFix prefetch hints not being loaded during revalidation (#92625) The prefetch hints manifest was not loaded during revalidation renders, causing all segments to fall back to PrefetchDisabled — effectively disabling prefetching for that route until the next full build. The app-page template handler constructs its own renderOpts from scratch without including prefetch-hints.json. Normal requests go through the server's renderOpts (which loads the manifest at startup), but background revalidation renders go through the template handler, so the hints were always empty. Load the manifest in the route module's loadManifests() and pass it through to renderOpts. Closes #92255 --------- Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>github.com-vercel-next.js · d803902d · 2026-04-13
- 0.5ETV[Segment Bundling] [Scaffolding] Track which segments can be omitted from prefetch (#91438) Some segments in a route don't benefit from static prefetching. A segment with runtime prefetching enabled will be fetched and computed at runtime instead, and a segment with `instant = false` is not prefetchable at all. There's no reason to pay the prefetch cost for these segments — neither the server (measuring sizes, generating responses) nor the client (fetching, caching) gains anything from including them. ### Pass-through segments This commit teaches the hint computation pass to identify these segments and mark them accordingly. During the size measurement traversal, disabled segments are skipped (zero size contribution) and act as transparent pass-throughs: parent data flows through them to the nearest static descendant rather than being blocked. This means a small static layout can still be bundled into a grandchild's response even when the intermediate segment is dynamic. ### No behavioral changes yet The actual server output and client fetching behavior don't change yet — disabled segments still produce standalone responses for now. A follow-up commit will use these hints to actually omit the segments from the output and teach the client to handle the gaps. ### Head (metadata) inlining The head (metadata/viewport) inlining logic is also updated: the head is only inlined into page segments (not layouts, since pages may access additional params like `searchParams`), and routes with runtime prefetch skip static head inlining entirely since the runtime response already includes the head.github.com-vercel-next.js · 6c3295f9 · 2026-03-23
- 0.4ETV[experiment] Add useOffline flag with offline retry behavior (#92011) **Current:** 1. https://github.com/vercel/next.js/pull/92011 **Up next:** 2. https://github.com/vercel/next.js/pull/92012 --- When a navigation, server action, or prefetch fails due to a network error, instead of falling back to MPA navigation or surfacing an error, the request blocks until connectivity is restored and then retries automatically. Offline detection works by treating any `fetch()` rejection as a network error (server errors resolve with a status code, they don't reject). Once offline, a polling loop does HEAD requests with exponential backoff to detect when connectivity returns. Browser offline/online events also feed into this loop. Successful fetches from other code paths can short-circuit the loop if they happen to land first. Follow-up work will add a `useOffline` hook so apps can show an offline indicator to the user, and allow navigations to read from stale cache entries while offline. Gated behind `experimental.useOffline`.github.com-vercel-next.js · 90450112 · 2026-03-28
- 0.3ETVfix: Don't fall through to catch-all when static child subtree doesn't match (#90957) When matching a URL against the known route trie, if a URL part matches a static child but the remaining parts don't resolve within that child's subtree, the algorithm incorrectly falls back to a dynamic catch-all sibling at the same level. This causes the catch-all to consume the already-matched static part plus the remaining parts, producing a synthetic entry for the wrong route. For example, given routes `/dashboard/settings/profile` and `/dashboard/[...catchall]`, navigating to `/dashboard/settings/profile` could incorrectly match the catch-all with params `["settings", "profile"]` instead of the intended static route. The fix treats the static child match as authoritative: if a real (non-placeholder) static child exists but its subtree can't resolve the remaining URL parts, bail out to server resolution rather than trying the dynamic sibling. This is safe because it just means an extra network request for routes whose subtree hasn't been fully discovered.github.com-vercel-next.js · f27b4928 · 2026-03-06
- 0.3ETVinstant(): fix cookie handling for fresh page loads (#90613) Previously, instant() used page.evaluate() to set the cookie via document.cookie, which required a page to already be loaded. This meant the cookie wasn't present on the very first navigation. Switch to Playwright's browser context cookie API so the cookie is set before any page loads. If the page is already loaded, the base URL is inferred automatically. Otherwise, a new baseURL option allows setting the cookie before the first navigation.github.com-vercel-next.js · a362fd27 · 2026-03-01
- 0.3ETVSuspend dynamic route params in dev instant shell (#93108) In dev with cacheComponents, the instant-nav prefetch for a dynamic route resolved the slug into the cached shell instead of returning the generic fallback, so `params` never suspended under a locked instant scope. Existing e2e coverage missed this because the fixture's dynamic-params page had `generateStaticParams` defined. That populates the dev prerender manifest and masks the bug — the broken path is the one where no `generateStaticParams` is defined. The regression test uses a new `ungenerated-params/[slug]` fixture that leaves it out.github.com-vercel-next.js · 16e5f9e6 · 2026-04-25
- 0.2ETVinstant(): Block out-of-band client fetches (#93099) The Instant Navigation Testing API simulates what a user would see on an arbitrarily slow connection — like an infinitely slow network — when the app is at rest and the prefetch cache is completely warm. It works by blocking any dynamic RSC data fetches until the end of the testing scope (i.e. the callback passed to instant()). It's designed to test the behavior of data fetched by Next.js, via a Server Component. So currently, it does not affect the behavior out-of-band client fetches — e.g. fetch('/api/...') in useEffect or an external data fetching library. Those requests proceed without being blocked by the `instant()` scope. But this is misleading for testing purposes, because it implies that client-side data fetches are not affected by slow network conditions. To address this, this PR overrides the global `fetch` API and blocks out-of-band client fetches until the lock is released. The change also impacts the behavior of the Instant DevTools panel (since both tools use the same underlying mechanism). The change only affects environments where the Instant Navigation Testing API is enabled. It has no impact on live production behavior. It does not account for other sources of I/O, like images, scripts, fonts, etc. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 854fdb03 · 2026-04-25
- 0.2ETV[experiment] Add useOffline hook to expose offline state to userland (#92012) **Previous:** 1. https://github.com/vercel/next.js/pull/92011 **Current:** 2. (this PR) --- Adds a `useOffline()` hook exported from `next/navigation` that returns `true` when the app is offline. The state is owned by a provider component rendered in the app router, using `useState` + `useOptimistic` so the value can update even during blocked transitions (e.g., a navigation that's waiting for connectivity). Gated behind `experimental.useOffline`.github.com-vercel-next.js · 7bce97d6 · 2026-03-28
- 0.2ETVBuffer prefetch response before passing to Flight client (#91487) **Current:** 1. https://github.com/vercel/next.js/pull/91487 **Up next:** 2. https://github.com/vercel/next.js/pull/91488 3. https://github.com/vercel/next.js/pull/89297 --- Prefetch responses include metadata (in the Flight stream sense, not HTML document metadata) that describes properties of the overall response — things like the stale time and the set of params that were accessed during rendering. Conceptually these are like late HTTP headers: information that's only known once the response is complete. Since we can't rely on actual HTTP late headers being supported everywhere, we encode this metadata in the body of the Flight response. The mechanism works by including an unresolved thenable in the Flight payload, then resolving it just before closing the stream. On the client, after the stream is fully received, we unwrap the thenable synchronously. This synchronous unwrap relies on the assumption that the server resolved the thenable before closing the stream. The server already buffers prefetch responses before sending them, so the resolved thenable data is always present in the response. However, HTTP chunking in the browser layer can introduce taskiness when processing the response, which could prevent Flight from decoding the full payload synchronously. The existing code includes fallback behavior for this case (e.g. treating the vary params as unknown), so this doesn't fix a semantic issue — it strengthens the guarantee so that the fallback path is never reached. To do this, we buffer the full response on the client and concatenate it into a single chunk before passing it to Flight. A single chunk is necessary because Flight's `processBinaryChunk` processes all rows synchronously within one call. Multiple chunks would not be sufficient even if pre-enqueued: the `await` continuation from `createFromReadableStream` can interleave between chunks, causing promise value rows to be processed after the root model initializes, which leaves thenables in a pending state. Since the server already buffers these responses and they complete during a prefetch (not during a navigation), this is not a performance consideration. Full (dynamic) prefetches are not affected by this change. These are streaming responses — even though they are cached, they are a special case where dynamic data is treated as if it were cached. They don't need to be buffered on either the server or the client the way normal cached responses are.github.com-vercel-next.js · d50bef07 · 2026-03-17