From 45695b5a89f9760da2009645be5c38ac8f949786 Mon Sep 17 00:00:00 2001 From: Zack Tanner <1939140+ztanner@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:49:42 -0700 Subject: [PATCH] move segment cache entries to top level segment-cache dir --- packages/next/src/client/app-dir/form.tsx | 2 +- packages/next/src/client/app-dir/link.tsx | 2 +- .../client/components/app-router-instance.ts | 4 +- packages/next/src/client/components/links.ts | 10 +- .../router-reducer/fetch-server-response.ts | 2 +- .../reducers/navigate-reducer.ts | 6 +- .../reducers/refresh-reducer.ts | 2 +- .../reducers/server-action-reducer.ts | 2 +- .../src/client/components/segment-cache.ts | 151 ------------------ .../cache-key.ts | 0 .../cache-map.ts | 0 .../cache.ts | 10 +- .../lru.ts | 0 .../navigation.ts | 2 +- .../prefetch.ts | 5 +- .../scheduler.ts | 4 +- .../client/components/segment-cache/types.ts | 53 ++++++ .../next/src/client/flight-data-helpers.ts | 2 +- packages/next/src/client/route-params.ts | 2 +- .../segment-cache/segment-value-encoding.ts | 2 +- 20 files changed, 84 insertions(+), 177 deletions(-) delete mode 100644 packages/next/src/client/components/segment-cache.ts rename packages/next/src/client/components/{segment-cache-impl => segment-cache}/cache-key.ts (100%) rename packages/next/src/client/components/{segment-cache-impl => segment-cache}/cache-map.ts (100%) rename packages/next/src/client/components/{segment-cache-impl => segment-cache}/cache.ts (99%) rename packages/next/src/client/components/{segment-cache-impl => segment-cache}/lru.ts (100%) rename packages/next/src/client/components/{segment-cache-impl => segment-cache}/navigation.ts (99%) rename packages/next/src/client/components/{segment-cache-impl => segment-cache}/prefetch.ts (95%) rename packages/next/src/client/components/{segment-cache-impl => segment-cache}/scheduler.ts (99%) create mode 100644 packages/next/src/client/components/segment-cache/types.ts diff --git a/packages/next/src/client/app-dir/form.tsx b/packages/next/src/client/app-dir/form.tsx index ad4aecf2fefe0..2b2e3de20d11a 100644 --- a/packages/next/src/client/app-dir/form.tsx +++ b/packages/next/src/client/app-dir/form.tsx @@ -19,7 +19,7 @@ import { mountFormInstance, unmountPrefetchableInstance, } from '../components/links' -import { FetchStrategy } from '../components/segment-cache' +import { FetchStrategy } from '../components/segment-cache/types' export type { FormProps } diff --git a/packages/next/src/client/app-dir/link.tsx b/packages/next/src/client/app-dir/link.tsx index a20a2e6261ebb..70ada01690153 100644 --- a/packages/next/src/client/app-dir/link.tsx +++ b/packages/next/src/client/app-dir/link.tsx @@ -21,7 +21,7 @@ import { isLocalURL } from '../../shared/lib/router/utils/is-local-url' import { FetchStrategy, type PrefetchTaskFetchStrategy, -} from '../components/segment-cache' +} from '../components/segment-cache/types' import { errorOnce } from '../../shared/lib/utils/error-once' type Url = string | UrlObject diff --git a/packages/next/src/client/components/app-router-instance.ts b/packages/next/src/client/components/app-router-instance.ts index 6874a6aaeed0a..598e9018e9806 100644 --- a/packages/next/src/client/components/app-router-instance.ts +++ b/packages/next/src/client/components/app-router-instance.ts @@ -16,9 +16,9 @@ import { startTransition } from 'react' import { isThenable } from '../../shared/lib/is-thenable' import { FetchStrategy, - prefetch as prefetchWithSegmentCache, type PrefetchTaskFetchStrategy, -} from './segment-cache' +} from './segment-cache/types' +import { prefetch as prefetchWithSegmentCache } from './segment-cache/prefetch' import { dispatchAppRouterAction } from './use-action-queue' import { addBasePath } from '../add-base-path' import { isExternalURL } from './app-router-utils' diff --git a/packages/next/src/client/components/links.ts b/packages/next/src/client/components/links.ts index 4d281baa8da46..68d155c362456 100644 --- a/packages/next/src/client/components/links.ts +++ b/packages/next/src/client/components/links.ts @@ -2,17 +2,17 @@ import type { FlightRouterState } from '../../shared/lib/app-router-types' import type { AppRouterInstance } from '../../shared/lib/app-router-context.shared-runtime' import { FetchStrategy, - isPrefetchTaskDirty, type PrefetchTaskFetchStrategy, -} from './segment-cache' -import { createCacheKey } from './segment-cache' + PrefetchPriority, +} from './segment-cache/types' +import { createCacheKey } from './segment-cache/cache-key' import { type PrefetchTask, - PrefetchPriority, schedulePrefetchTask as scheduleSegmentPrefetchTask, cancelPrefetchTask, reschedulePrefetchTask, -} from './segment-cache' + isPrefetchTaskDirty, +} from './segment-cache/scheduler' import { startTransition } from 'react' type LinkElement = HTMLAnchorElement | SVGAElement diff --git a/packages/next/src/client/components/router-reducer/fetch-server-response.ts b/packages/next/src/client/components/router-reducer/fetch-server-response.ts index 1ba1742fe21cd..8274c04d706da 100644 --- a/packages/next/src/client/components/router-reducer/fetch-server-response.ts +++ b/packages/next/src/client/components/router-reducer/fetch-server-response.ts @@ -40,7 +40,7 @@ import { getRenderedSearch, urlToUrlWithoutFlightMarker, } from '../../route-params' -import type { NormalizedSearch } from '../segment-cache' +import type { NormalizedSearch } from '../segment-cache/cache-key' const createFromReadableStream = createFromReadableStreamBrowser as (typeof import('react-server-dom-webpack/client.browser'))['createFromReadableStream'] diff --git a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts index fd21877290c3b..c4ba063bd1bc4 100644 --- a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts @@ -13,10 +13,10 @@ import { handleMutable } from '../handle-mutable' import { navigate as navigateUsingSegmentCache, - NavigationResultTag, type NavigationResult, - getStaleTimeMs, -} from '../../segment-cache' +} from '../../segment-cache/navigation' +import { NavigationResultTag } from '../../segment-cache/types' +import { getStaleTimeMs } from '../../segment-cache/cache' // These values are set by `define-env-plugin` (based on `nextConfig.experimental.staleTimes`) // and default to 5 minutes (static) / 0 seconds (dynamic) diff --git a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts index 533ac60ff4631..0d9fb7581fac7 100644 --- a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts @@ -19,7 +19,7 @@ import { createEmptyCacheNode } from '../../app-router' import { handleSegmentMismatch } from '../handle-segment-mismatch' import { hasInterceptionRouteInCurrentTree } from './has-interception-route-in-current-tree' import { refreshInactiveParallelSegments } from '../refetch-inactive-parallel-segments' -import { revalidateEntireCache } from '../../segment-cache' +import { revalidateEntireCache } from '../../segment-cache/cache' export function refreshReducer( state: ReadonlyReducerState, diff --git a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts index b5881bc67020a..c2bf9892daa80 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts @@ -55,7 +55,7 @@ import { extractInfoFromServerReferenceId, omitUnusedArgs, } from '../../../../shared/lib/server-reference-info' -import { revalidateEntireCache } from '../../segment-cache' +import { revalidateEntireCache } from '../../segment-cache/cache' const createFromFetch = createFromFetchBrowser as (typeof import('react-server-dom-webpack/client.browser'))['createFromFetch'] diff --git a/packages/next/src/client/components/segment-cache.ts b/packages/next/src/client/components/segment-cache.ts deleted file mode 100644 index 0047b69f5123b..0000000000000 --- a/packages/next/src/client/components/segment-cache.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Entry point to the Segment Cache implementation. - * - * All code related to the Segment Cache lives `segment-cache-impl` directory. - * Callers access it through this indirection. - * - * This is to ensure the code is dead code eliminated from the bundle if the - * flag is disabled. - * - * TODO: This is super tedious. Since experimental flags are an essential part - * of our workflow, we should establish a better pattern for dead code - * elimination. Ideally it would be done at the bundler level, like how React's - * build process works. In the React repo, you don't even need to add any extra - * configuration per experiment — if the code is not reachable, it gets stripped - * from the build automatically by Rollup. Or, shorter term, we could stub out - * experimental modules at build time by updating the build config, i.e. a more - * automated version of what I'm doing manually in this file. - */ - -export type { NavigationResult } from './segment-cache-impl/navigation' -export type { PrefetchTask } from './segment-cache-impl/scheduler' -export type { - NormalizedPathname, - NormalizedSearch, -} from './segment-cache-impl/cache-key' - -export const prefetch: typeof import('./segment-cache-impl/prefetch').prefetch = - function (...args) { - return ( - require('./segment-cache-impl/prefetch') as typeof import('./segment-cache-impl/prefetch') - ).prefetch(...args) - } - -export const navigate: typeof import('./segment-cache-impl/navigation').navigate = - function (...args) { - return ( - require('./segment-cache-impl/navigation') as typeof import('./segment-cache-impl/navigation') - ).navigate(...args) - } - -export const revalidateEntireCache: typeof import('./segment-cache-impl/cache').revalidateEntireCache = - function (...args) { - return ( - require('./segment-cache-impl/cache') as typeof import('./segment-cache-impl/cache') - ).revalidateEntireCache(...args) - } - -export const getCurrentCacheVersion: typeof import('./segment-cache-impl/cache').getCurrentCacheVersion = - function (...args) { - return ( - require('./segment-cache-impl/cache') as typeof import('./segment-cache-impl/cache') - ).getCurrentCacheVersion(...args) - } - -export const schedulePrefetchTask: typeof import('./segment-cache-impl/scheduler').schedulePrefetchTask = - function (...args) { - return ( - require('./segment-cache-impl/scheduler') as typeof import('./segment-cache-impl/scheduler') - ).schedulePrefetchTask(...args) - } - -export const cancelPrefetchTask: typeof import('./segment-cache-impl/scheduler').cancelPrefetchTask = - function (...args) { - return ( - require('./segment-cache-impl/scheduler') as typeof import('./segment-cache-impl/scheduler') - ).cancelPrefetchTask(...args) - } - -export const reschedulePrefetchTask: typeof import('./segment-cache-impl/scheduler').reschedulePrefetchTask = - function (...args) { - return ( - require('./segment-cache-impl/scheduler') as typeof import('./segment-cache-impl/scheduler') - ).reschedulePrefetchTask(...args) - } - -export const isPrefetchTaskDirty: typeof import('./segment-cache-impl/scheduler').isPrefetchTaskDirty = - function (...args) { - return ( - require('./segment-cache-impl/scheduler') as typeof import('./segment-cache-impl/scheduler') - ).isPrefetchTaskDirty(...args) - } - -export const createCacheKey: typeof import('./segment-cache-impl/cache-key').createCacheKey = - function (...args) { - return ( - require('./segment-cache-impl/cache-key') as typeof import('./segment-cache-impl/cache-key') - ).createCacheKey(...args) - } - -/** - * Below are public constants. They're small enough that we don't need to - * DCE them. - */ - -export const enum NavigationResultTag { - MPA, - Success, - NoOp, - Async, -} - -/** - * The priority of the prefetch task. Higher numbers are higher priority. - */ -export const enum PrefetchPriority { - /** - * Assigned to the most recently hovered/touched link. Special network - * bandwidth is reserved for this task only. There's only ever one Intent- - * priority task at a time; when a new Intent task is scheduled, the previous - * one is bumped down to Default. - */ - Intent = 2, - /** - * The default priority for prefetch tasks. - */ - Default = 1, - /** - * Assigned to tasks when they spawn non-blocking background work, like - * revalidating a partially cached entry to see if more data is available. - */ - Background = 0, -} - -export const enum FetchStrategy { - // Deliberately ordered so we can easily compare two segments - // and determine if one segment is "more specific" than another - // (i.e. if it's likely that it contains more data) - LoadingBoundary = 0, - PPR = 1, - PPRRuntime = 2, - Full = 3, -} - -/** - * A subset of fetch strategies used for prefetch tasks. - * A prefetch task can't know if it should use `PPR` or `LoadingBoundary` - * until we complete the initial tree prefetch request, so we use `PPR` to signal both cases - * and adjust it based on the route when actually fetching. - * */ -export type PrefetchTaskFetchStrategy = - | FetchStrategy.PPR - | FetchStrategy.PPRRuntime - | FetchStrategy.Full - -/** - * Ensures a minimum stale time of 30s to avoid issues where the server sends a too - * short-lived stale time, which would prevent anything from being prefetched. - */ -export function getStaleTimeMs(staleTimeSeconds: number): number { - return Math.max(staleTimeSeconds, 30) * 1000 -} diff --git a/packages/next/src/client/components/segment-cache-impl/cache-key.ts b/packages/next/src/client/components/segment-cache/cache-key.ts similarity index 100% rename from packages/next/src/client/components/segment-cache-impl/cache-key.ts rename to packages/next/src/client/components/segment-cache/cache-key.ts diff --git a/packages/next/src/client/components/segment-cache-impl/cache-map.ts b/packages/next/src/client/components/segment-cache/cache-map.ts similarity index 100% rename from packages/next/src/client/components/segment-cache-impl/cache-map.ts rename to packages/next/src/client/components/segment-cache/cache-map.ts diff --git a/packages/next/src/client/components/segment-cache-impl/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts similarity index 99% rename from packages/next/src/client/components/segment-cache-impl/cache.ts rename to packages/next/src/client/components/segment-cache/cache.ts index 46b09518b8847..7fde00c9a983e 100644 --- a/packages/next/src/client/components/segment-cache-impl/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -93,9 +93,17 @@ import { DOC_PREFETCH_RANGE_HEADER_VALUE, doesExportedHtmlMatchBuildId, } from '../../../shared/lib/segment-cache/output-export-prefetch-encoding' -import { FetchStrategy, getStaleTimeMs } from '../segment-cache' +import { FetchStrategy } from './types' import { createPromiseWithResolvers } from '../../../shared/lib/promise-with-resolvers' +/** + * Ensures a minimum stale time of 30s to avoid issues where the server sends a too + * short-lived stale time, which would prevent anything from being prefetched. + */ +export function getStaleTimeMs(staleTimeSeconds: number): number { + return Math.max(staleTimeSeconds, 30) * 1000 +} + // A note on async/await when working in the prefetch cache: // // Most async operations in the prefetch cache should *not* use async/await, diff --git a/packages/next/src/client/components/segment-cache-impl/lru.ts b/packages/next/src/client/components/segment-cache/lru.ts similarity index 100% rename from packages/next/src/client/components/segment-cache-impl/lru.ts rename to packages/next/src/client/components/segment-cache/lru.ts diff --git a/packages/next/src/client/components/segment-cache-impl/navigation.ts b/packages/next/src/client/components/segment-cache/navigation.ts similarity index 99% rename from packages/next/src/client/components/segment-cache-impl/navigation.ts rename to packages/next/src/client/components/segment-cache/navigation.ts index ba0cdf0450609..4d4d4c6fe5e9a 100644 --- a/packages/next/src/client/components/segment-cache-impl/navigation.ts +++ b/packages/next/src/client/components/segment-cache/navigation.ts @@ -28,7 +28,7 @@ import { } from './cache' import { createCacheKey } from './cache-key' import { addSearchParamsIfPageSegment } from '../../../shared/lib/segment' -import { NavigationResultTag } from '../segment-cache' +import { NavigationResultTag } from './types' type MPANavigationResult = { tag: NavigationResultTag.MPA diff --git a/packages/next/src/client/components/segment-cache-impl/prefetch.ts b/packages/next/src/client/components/segment-cache/prefetch.ts similarity index 95% rename from packages/next/src/client/components/segment-cache-impl/prefetch.ts rename to packages/next/src/client/components/segment-cache/prefetch.ts index 49ce56baa695c..298e7c1b70eab 100644 --- a/packages/next/src/client/components/segment-cache-impl/prefetch.ts +++ b/packages/next/src/client/components/segment-cache/prefetch.ts @@ -2,10 +2,7 @@ import type { FlightRouterState } from '../../../shared/lib/app-router-types' import { createPrefetchURL } from '../app-router-utils' import { createCacheKey } from './cache-key' import { schedulePrefetchTask } from './scheduler' -import { - PrefetchPriority, - type PrefetchTaskFetchStrategy, -} from '../segment-cache' +import { PrefetchPriority, type PrefetchTaskFetchStrategy } from './types' /** * Entrypoint for prefetching a URL into the Segment Cache. diff --git a/packages/next/src/client/components/segment-cache-impl/scheduler.ts b/packages/next/src/client/components/segment-cache/scheduler.ts similarity index 99% rename from packages/next/src/client/components/segment-cache-impl/scheduler.ts rename to packages/next/src/client/components/segment-cache/scheduler.ts index 15070bf25e78e..9885216b64f1b 100644 --- a/packages/next/src/client/components/segment-cache-impl/scheduler.ts +++ b/packages/next/src/client/components/segment-cache/scheduler.ts @@ -33,9 +33,9 @@ import { createCacheKey } from './cache-key' import { FetchStrategy, type PrefetchTaskFetchStrategy, - getCurrentCacheVersion, PrefetchPriority, -} from '../segment-cache' +} from './types' +import { getCurrentCacheVersion } from './cache' import { addSearchParamsIfPageSegment, PAGE_SEGMENT_KEY, diff --git a/packages/next/src/client/components/segment-cache/types.ts b/packages/next/src/client/components/segment-cache/types.ts new file mode 100644 index 0000000000000..09d00bf3d17ae --- /dev/null +++ b/packages/next/src/client/components/segment-cache/types.ts @@ -0,0 +1,53 @@ +/** + * Shared types and constants for the Segment Cache. + */ + +export const enum NavigationResultTag { + MPA, + Success, + NoOp, + Async, +} + +/** + * The priority of the prefetch task. Higher numbers are higher priority. + */ +export const enum PrefetchPriority { + /** + * Assigned to the most recently hovered/touched link. Special network + * bandwidth is reserved for this task only. There's only ever one Intent- + * priority task at a time; when a new Intent task is scheduled, the previous + * one is bumped down to Default. + */ + Intent = 2, + /** + * The default priority for prefetch tasks. + */ + Default = 1, + /** + * Assigned to tasks when they spawn non-blocking background work, like + * revalidating a partially cached entry to see if more data is available. + */ + Background = 0, +} + +export const enum FetchStrategy { + // Deliberately ordered so we can easily compare two segments + // and determine if one segment is "more specific" than another + // (i.e. if it's likely that it contains more data) + LoadingBoundary = 0, + PPR = 1, + PPRRuntime = 2, + Full = 3, +} + +/** + * A subset of fetch strategies used for prefetch tasks. + * A prefetch task can't know if it should use `PPR` or `LoadingBoundary` + * until we complete the initial tree prefetch request, so we use `PPR` to signal both cases + * and adjust it based on the route when actually fetching. + * */ +export type PrefetchTaskFetchStrategy = + | FetchStrategy.PPR + | FetchStrategy.PPRRuntime + | FetchStrategy.Full diff --git a/packages/next/src/client/flight-data-helpers.ts b/packages/next/src/client/flight-data-helpers.ts index 9e9470ba7d535..6dea5c8b0ed50 100644 --- a/packages/next/src/client/flight-data-helpers.ts +++ b/packages/next/src/client/flight-data-helpers.ts @@ -9,7 +9,7 @@ import type { InitialRSCPayload, } from '../shared/lib/app-router-types' import { PAGE_SEGMENT_KEY } from '../shared/lib/segment' -import type { NormalizedSearch } from './components/segment-cache' +import type { NormalizedSearch } from './components/segment-cache/cache-key' import { getCacheKeyForDynamicParam, parseDynamicParamFromURLPart, diff --git a/packages/next/src/client/route-params.ts b/packages/next/src/client/route-params.ts index 9d0eca8a261f6..bdba64c773437 100644 --- a/packages/next/src/client/route-params.ts +++ b/packages/next/src/client/route-params.ts @@ -13,7 +13,7 @@ import { import type { NormalizedPathname, NormalizedSearch, -} from './components/segment-cache' +} from './components/segment-cache/cache-key' import type { RSCResponse } from './components/router-reducer/fetch-server-response' import type { ParsedUrlQuery } from 'querystring' diff --git a/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts b/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts index ab9c90a4c7505..21dcbeca6ed12 100644 --- a/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts +++ b/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts @@ -1,6 +1,6 @@ import { PAGE_SEGMENT_KEY } from '../segment' import type { Segment as FlightRouterStateSegment } from '../app-router-types' -import type { NormalizedPathname } from '../../../client/components/segment-cache' +import type { NormalizedPathname } from '../../../client/components/segment-cache/cache-key' // TypeScript trick to simulate opaque types, like in Flow. type Opaque = T & { __brand: K }