Skip to content

Commit

Permalink
add experimental client router cache config
Browse files Browse the repository at this point in the history
  • Loading branch information
ztanner committed Mar 28, 2024
1 parent 96978d9 commit bd3da32
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 6 deletions.
7 changes: 7 additions & 0 deletions packages/next/src/build/webpack/plugins/define-env-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ export function getDefineEnv({
'process.env.__NEXT_MIDDLEWARE_MATCHERS': middlewareMatchers ?? [],
'process.env.__NEXT_MANUAL_CLIENT_BASE_PATH':
config.experimental.manualClientBasePath ?? false,
'process.env.__NEXT_CLIENT_ROUTER_CACHE_STALETIME_MS': JSON.stringify(
config.experimental.clientRouterCache
? 31556952000 // 1 year (ms)
: config.experimental.clientRouterCache === false
? 0
: 30000 // 30 seconds (ms)
),
'process.env.__NEXT_CLIENT_ROUTER_FILTER_ENABLED':
config.experimental.clientRouterFilter ?? true,
'process.env.__NEXT_CLIENT_ROUTER_S_FILTER':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type PrefetchCacheEntry,
PrefetchKind,
type ReadonlyReducerState,
PREFETCH_STALE_TIME,
} from './router-reducer-types'
import { prefetchQueue } from './reducers/prefetch-reducer'

Expand Down Expand Up @@ -113,7 +114,8 @@ export function getOrCreatePrefetchCacheEntry({
kind:
kind ||
// in dev, there's never gonna be a prefetch entry so we want to prefetch here
(process.env.NODE_ENV === 'development'
// when staletime is 0, there'll never be a "FULL" prefetch kind, so we default to auto
(process.env.NODE_ENV === 'development' || PREFETCH_STALE_TIME === 0
? PrefetchKind.AUTO
: PrefetchKind.TEMPORARY),
})
Expand Down Expand Up @@ -244,15 +246,21 @@ export function prunePrefetchCache(
}

const FIVE_MINUTES = 5 * 60 * 1000
const THIRTY_SECONDS = 30 * 1000

function getPrefetchEntryCacheStatus({
kind,
prefetchTime,
lastUsedTime,
}: PrefetchCacheEntry): PrefetchCacheEntryStatus {
// if the cache entry was prefetched or read less than 30s ago, then we want to re-use it
if (Date.now() < (lastUsedTime ?? prefetchTime) + THIRTY_SECONDS) {
if (PREFETCH_STALE_TIME === 0) {
// a value of 0 means we never want to use the prefetch data, only the prefetched loading state (if it exists)
// we mark it stale here so that the router will not attempt to apply the cache node data and will instead know to lazily
// fetch the full data
return PrefetchCacheEntryStatus.stale
}

// if the cache entry was prefetched or read less than the specified staletime window, then we want to re-use it
if (Date.now() < (lastUsedTime ?? prefetchTime) + PREFETCH_STALE_TIME) {
return lastUsedTime
? PrefetchCacheEntryStatus.reusable
: PrefetchCacheEntryStatus.fresh
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,11 @@ export function isThenable(value: any): value is Promise<AppRouterState> {
typeof value.then === 'function'
)
}

/**
* Time (in ms) that a prefetch entry can be reused by the client router cache.
*/
export const PREFETCH_STALE_TIME =
typeof process.env.__NEXT_CLIENT_ROUTER_CACHE_STALETIME_MS !== 'undefined'
? parseInt(process.env.__NEXT_CLIENT_ROUTER_CACHE_STALETIME_MS, 10)
: 30 * 1000 // thirty seconds (in ms)
11 changes: 9 additions & 2 deletions packages/next/src/client/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import type {
import { useIntersection } from './use-intersection'
import { getDomainLocale } from './get-domain-locale'
import { addBasePath } from './add-base-path'
import { PrefetchKind } from './components/router-reducer/router-reducer-types'
import {
PREFETCH_STALE_TIME,
PrefetchKind,
} from './components/router-reducer/router-reducer-types'

type Url = string | UrlObject
type RequiredKeys<T> = {
Expand Down Expand Up @@ -307,7 +310,11 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
* - false: we will not prefetch if in the viewport at all
*/
const appPrefetchKind =
prefetchProp === null ? PrefetchKind.AUTO : PrefetchKind.FULL
// If the prefetch staletime is 0, then a full prefetch would be wasteful, as it'd never get used.
// These get switched into "auto" so at least the loading state can be re-used.
prefetchProp === null || PREFETCH_STALE_TIME === 0
? PrefetchKind.AUTO
: PrefetchKind.FULL

if (process.env.NODE_ENV !== 'production') {
function createPropError(args: {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
validator: z.string().optional(),
})
.optional(),
clientRouterCache: z.boolean().optional(),
clientRouterFilter: z.boolean().optional(),
clientRouterFilterRedirects: z.boolean().optional(),
clientRouterFilterAllowedRate: z.number().optional(),
Expand Down
8 changes: 8 additions & 0 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ export interface ExperimentalConfig {
strictNextHead?: boolean
clientRouterFilter?: boolean
clientRouterFilterRedirects?: boolean
/**
* Used to control the router cache "stale time" value. This value is used to determine
* if a cache entry can be re-used, and for how long. `true` means "forever" (implemented as
* one year), `false` means "never" (will always fetch from the server), and undefined will use
* the existing heuristics (30s for dynamic routes, 5min for static routes)
*/
clientRouterCache?: boolean
// decimal for percent for possible false positives
// e.g. 0.01 for 10% potential false matches lower
// percent increases size of the filter
Expand Down Expand Up @@ -926,6 +933,7 @@ export const defaultConfig: NextConfig = {
missingSuspenseWithCSRBailout: true,
optimizeServerReact: true,
useEarlyImport: false,
clientRouterCache: undefined,
},
}

Expand Down

0 comments on commit bd3da32

Please sign in to comment.