Skip to content

Decouple route and segment cache lifecycles#88989

Merged
acdlite merged 3 commits intovercel:canaryfrom
acdlite:route-static-staletime
Jan 28, 2026
Merged

Decouple route and segment cache lifecycles#88989
acdlite merged 3 commits intovercel:canaryfrom
acdlite:route-static-staletime

Conversation

@acdlite
Copy link
Contributor

@acdlite acdlite commented Jan 24, 2026

Based on:


Follows from the previous commits that decoupled route stale time from segment data. Now that routes have their own lifecycle, we can simplify the implementation: route stale times are always the static constant, rather than being derived from server-provided values.

Route structure is essentially static — it only changes on deploy. The exception is rewrites/redirects in middleware or proxy, which can change routes dynamically based on request state. But we have other strategies for handling those cases (e.g., route interception headers, cache invalidation on auth state changes).

This simplification removes the need to thread stale time through various call sites. The fulfillRouteCacheEntry function now computes the stale time internally from a now timestamp parameter, following the convention used elsewhere in the codebase.

@acdlite acdlite marked this pull request as ready for review January 24, 2026 18:10
@acdlite acdlite requested a review from ztanner January 24, 2026 18:10
Refactors segment cache entries to receive their stale time from the
server response rather than inheriting from the parent route cache
entry. This decouples the lifetime of segment data from the route tree,
preparing for future optimizations.

When I say "route" here, what I'm referring to is the mapping of URL ->
layout/ page hierarchy. Typically this is a predictable mapping based on
the App Router's file-based routing conventions; however, it's possible
to rewrite the URL to a different internal route path.

Conceptually, the cache life of a route corresponds to how often a proxy
is expected to dynamically rewrite or redirect a URL. We don't currently
expose a way to directly control this, but if we did, you could think of
it as being equivalent to adding "use cache" (and cacheLife, cacheTag,
etc.) to the `proxy()` function.

The point, though, is that the route tree has a different lifetime than
data rendered on the page. Route trees change relatively infrequently,
like due to a logged in/out state change, or a feature flag change. So
we can/should cache them fairly aggressively by default.

This is related to an upcoming change where we'll go even further and
_predict_ routes we haven't visited yet based on structurally similar
routes we've already seen.

This PR itself, though, makes no behavioral changes; it just sets up
the subsequent steps.
More preparation for optimistic routing.

Adds the ability to clear the client cache of data in the UI without
also clearing the route information (URL -> layout/page
hierarchy mapping), and vice versa.

This works by splitting the single `currentCacheVersion` counter into
separate `currentRouteCacheVersion` and
`currentSegmentCacheVersion` counters. When one version or the other
is incremented, all the entries in the corresponding cache are
(lazily) evicted.

This is relevant to optimistic routing because it will allow us to
clear the route cache without also evicting segment data. Similarly,
during a client-side refresh, we should be able to evict the segment
data without losing route information.
Follows from the previous commits that decoupled route stale time from
segment data. Now that routes have their own lifecycle, we can simplify
the implementation: route stale times are always the static constant,
rather than being derived from server-provided values.

Route structure is essentially static — it only changes on deploy. The
exception is rewrites/redirects in middleware or proxy, which can change
routes dynamically based on request state. But we have other strategies
for handling those cases (e.g., route interception headers, cache
invalidation on auth state changes).

This simplification removes the need to thread stale time through
various call sites. The `fulfillRouteCacheEntry` function now computes
the stale time internally from a `now` timestamp parameter, following
the convention used elsewhere in the codebase.
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Jan 25, 2026

Tests Passed

@acdlite acdlite merged commit 244fac6 into vercel:canary Jan 28, 2026
264 of 272 checks passed
acdlite added a commit that referenced this pull request Jan 29, 2026
Based on:

- #88834
- #88989

---

This implements a partial "vary params" optimization for the segment
cache. When a segment doesn't access params on the server, its
prefetched data can be reused across different param values.

Two cases are handled:
1. Segments without a user-provided layout (only loading.tsx or nothing)
2. Segments with a 'use client' layout

The server now includes a varyParams field in segment prefetch
responses. When varyParams is an empty Set, the client re-keys the cache
entry with Fallback for all param values, making it reusable across
different params.

This is a stepping stone toward full vary params tracking where Server
Components would track which specific params they access during
rendering.
acdlite added a commit that referenced this pull request Jan 29, 2026
Based on:

- #88834
- #88989
- #88998

---

Adds optimistic routing, enabling the client to predict route structure
for URLs that haven't been prefetched yet. When navigating to a URL like
/blog/post-2, if we've previously learned the pattern from /blog/post-1,
we can predict the route structure without waiting for a server
response.

The client learns route patterns from server responses and builds a trie
indexed by URL parts. When a cache miss occurs, we check if the URL
matches a known pattern. If so, we create a synthetic cache entry from
the template, allowing immediate rendering of loading boundaries.

Static siblings (like /blog/featured alongside /blog/[slug]) are tracked
to avoid incorrect predictions — we only predict dynamic routes when
we're confident no static sibling matches.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 12, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants