Decouple route and segment cache lifecycles#88989
Merged
acdlite merged 3 commits intovercel:canaryfrom Jan 28, 2026
Merged
Conversation
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.
606643a to
538fa28
Compare
Collaborator
Tests Passed |
ztanner
approved these changes
Jan 27, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
fulfillRouteCacheEntryfunction now computes the stale time internally from anowtimestamp parameter, following the convention used elsewhere in the codebase.