Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move tracking of revalidate and tags from WorkStore to WorkUnitStore #70940

Draft
wants to merge 4 commits into
base: canary
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1336,7 +1336,7 @@ export async function buildAppStaticPaths({
store.fetchCache = current.config.fetchCache
}
if (typeof current.config?.revalidate !== 'undefined') {
store.revalidate = current.config.revalidate
store.revalidateSegmentConfig = current.config.revalidate
}
if (current.config?.dynamic === 'force-dynamic') {
store.forceDynamic = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface WorkStore {
forceDynamic?: boolean
fetchCache?: AppSegmentConfig['fetchCache']

revalidate?: Revalidate
revalidateSegmentConfig?: Revalidate
forceStatic?: boolean
dynamicShouldError?: boolean
pendingRevalidates?: Record<string, Promise<any>>
Expand All @@ -53,8 +53,6 @@ export interface WorkStore {
nextFetchId?: number
pathWasRevalidated?: boolean

tags?: string[]

revalidatedTags?: string[]
fetchMetrics?: FetchMetrics

Expand Down
8 changes: 5 additions & 3 deletions packages/next/src/export/routes/app-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { IncrementalCache } from '../../server/lib/incremental-cache'

import { join } from 'path'
import {
INFINITE_CACHE,
NEXT_BODY_SUFFIX,
NEXT_CACHE_TAGS_HEADER,
NEXT_META_SUFFIX,
Expand Down Expand Up @@ -119,12 +120,13 @@ export async function exportAppRoute(

const blob = await response.blob()
const revalidate =
typeof context.renderOpts.store?.revalidate === 'undefined'
typeof (context.renderOpts as any).collectedRevalidate === 'undefined' ||
(context.renderOpts as any).collectedRevalidate >= INFINITE_CACHE
? false
: context.renderOpts.store.revalidate
: (context.renderOpts as any).collectedRevalidate

const headers = toNodeOutgoingHttpHeaders(response.headers)
const cacheTags = (context.renderOpts as any).fetchTags
const cacheTags = (context.renderOpts as any).collectedTags

if (cacheTags) {
headers[NEXT_CACHE_TAGS_HEADER] = cacheTags
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export const NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_'
// in seconds
export const CACHE_ONE_YEAR = 31536000

// in seconds, represents revalidate=false. I.e. never revaliate.
// We use this value since it can be represented as a V8 SMI for optimal performance.
// It can also be serialized as JSON if it ever leaks accidentally as an actual value.
export const INFINITE_CACHE = 0xfffffffe

// Patterns to detect middleware files
export const MIDDLEWARE_FILENAME = 'middleware'
export const MIDDLEWARE_LOCATION_REGEXP = `(?:src/)?${MIDDLEWARE_FILENAME}`
Expand Down
83 changes: 62 additions & 21 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ import { CacheSignal } from './cache-signal'
import { getTracedMetadata } from '../lib/trace/utils'

import './clean-async-snapshot.external'
import { INFINITE_CACHE } from '../../lib/constants'

export type GetDynamicParamFromSegment = (
// [slug] / [[slug]] / [...slug]
Expand Down Expand Up @@ -913,14 +914,14 @@ async function renderToHTMLOrFlightImpl(
renderOpts.setAppIsrStatus?.(pathname, null)
}

let collectedRevalidate: number = 0
if (
// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME !== 'edge' &&
isNodeNextRequest(req)
) {
req.originalRequest.on('end', () => {
const staticGenStore = ComponentMod.workAsyncStorage.getStore()
const prerenderStore = workUnitAsyncStorage.getStore()
const isPPR =
prerenderStore && prerenderStore.type === 'prerender'
Expand All @@ -929,19 +930,16 @@ async function renderToHTMLOrFlightImpl(

if (
process.env.NODE_ENV === 'development' &&
staticGenStore &&
renderOpts.setAppIsrStatus &&
!isPPR
!isPPR &&
collectedRevalidate > 0
) {
// only node can be ISR so we only need to update the status here
const { pathname } = new URL(req.url || '/', 'http://n')
let { revalidate } = staticGenStore
if (typeof revalidate === 'undefined') {
revalidate = false
}
if (revalidate === false || revalidate > 0) {
renderOpts.setAppIsrStatus(pathname, revalidate)
}
renderOpts.setAppIsrStatus(
pathname,
collectedRevalidate >= INFINITE_CACHE ? false : collectedRevalidate
)
}

requestEndedState.ended = true
Expand Down Expand Up @@ -1130,19 +1128,22 @@ async function renderToHTMLOrFlightImpl(

addImplicitTags(workStore, requestStore)

if (workStore.tags) {
metadata.fetchTags = workStore.tags.join(',')
if (response.collectedTags) {
metadata.fetchTags = response.collectedTags.join(',')
}

// If force static is specifically set to false, we should not revalidate
// the page.
if (workStore.forceStatic === false) {
workStore.revalidate = 0
if (workStore.forceStatic === false || response.collectedRevalidate === 0) {
metadata.revalidate = 0
} else {
// Copy the revalidation value onto the render result metadata.
metadata.revalidate =
response.collectedRevalidate ?? ctx.defaultRevalidate // TODO: No default
// For debug.
collectedRevalidate = response.collectedRevalidate
}

// Copy the revalidation value onto the render result metadata.
metadata.revalidate = workStore.revalidate ?? ctx.defaultRevalidate

// provide bailout info for debugging
if (metadata.revalidate === 0) {
metadata.staticBailoutInfo = {
Expand Down Expand Up @@ -1239,10 +1240,6 @@ async function renderToHTMLOrFlightImpl(

addImplicitTags(workStore, requestStore)

if (workStore.tags) {
metadata.fetchTags = workStore.tags.join(',')
}

// Create the new render result for the response.
return new RenderResult(stream, options)
}
Expand Down Expand Up @@ -1717,6 +1714,8 @@ type PrerenderToStreamResult = {
digestErrorsMap: Map<string, DigestedError>
ssrErrors: Array<unknown>
dynamicTracking?: null | DynamicTrackingState
collectedRevalidate: number
collectedTags: null | string[]
}

/**
Expand Down Expand Up @@ -1867,6 +1866,8 @@ async function prerenderToStream(
// because we will always do a final render after caches have filled and we
// will track it again there
dynamicTracking: null,
revalidate: INFINITE_CACHE,
tags: null,
}

let flightController = new AbortController()
Expand Down Expand Up @@ -1956,6 +1957,8 @@ async function prerenderToStream(
// include the flight controller in the store.
controller: flightController,
dynamicTracking,
revalidate: INFINITE_CACHE,
tags: null,
}

function onPostpone(reason: string) {
Expand Down Expand Up @@ -2014,6 +2017,8 @@ async function prerenderToStream(
// We do track dynamic access because searchParams and certain hooks can still be
// dynamic during SSR
dynamicTracking,
revalidate: INFINITE_CACHE,
tags: null,
}
let SSRIsDynamic = false
function SSROnError(err: unknown, errorInfo: ErrorInfo) {
Expand Down Expand Up @@ -2115,6 +2120,9 @@ async function prerenderToStream(
getServerInsertedHTML,
}),
dynamicTracking,
// TODO: Should this include the SSR pass?
collectedRevalidate: finalRenderPrerenderStore.revalidate,
collectedTags: finalRenderPrerenderStore.tags,
}
} else {
// Static case
Expand Down Expand Up @@ -2167,6 +2175,9 @@ async function prerenderToStream(
getServerInsertedHTML,
}),
dynamicTracking,
// TODO: Should this include the SSR pass?
collectedRevalidate: finalRenderPrerenderStore.revalidate,
collectedTags: finalRenderPrerenderStore.tags,
}
}
} else {
Expand Down Expand Up @@ -2217,6 +2228,8 @@ async function prerenderToStream(
// consider the route dynamic.
controller: flightController,
dynamicTracking,
revalidate: INFINITE_CACHE,
tags: null,
}

const firstAttemptRSCPayload = await workUnitAsyncStorage.run(
Expand Down Expand Up @@ -2294,6 +2307,8 @@ async function prerenderToStream(
cacheSignal: null,
controller: flightController,
dynamicTracking,
revalidate: INFINITE_CACHE,
tags: null,
}

const SSRController = new AbortController()
Expand All @@ -2308,6 +2323,8 @@ async function prerenderToStream(
// We do track dynamic access because searchParams and certain hooks can still be
// dynamic during SSR
dynamicTracking,
revalidate: INFINITE_CACHE,
tags: null,
}

const finalAttemptRSCPayload = await workUnitAsyncStorage.run(
Expand Down Expand Up @@ -2471,6 +2488,9 @@ async function prerenderToStream(
validateRootLayout,
}),
dynamicTracking,
// TODO: Should this include the SSR pass?
collectedRevalidate: finalRenderPrerenderStore.revalidate,
collectedTags: finalRenderPrerenderStore.tags,
}
}
} else if (renderOpts.experimental.isRoutePPREnabled) {
Expand All @@ -2484,6 +2504,8 @@ async function prerenderToStream(
cacheSignal: null,
controller: null,
dynamicTracking,
revalidate: INFINITE_CACHE,
tags: null,
}
const RSCPayload = await workUnitAsyncStorage.run(
reactServerPrerenderStore,
Expand Down Expand Up @@ -2512,6 +2534,8 @@ async function prerenderToStream(
cacheSignal: null,
controller: null,
dynamicTracking,
revalidate: INFINITE_CACHE,
tags: null,
}

const prerender = require('react-dom/static.edge')
Expand Down Expand Up @@ -2596,6 +2620,9 @@ async function prerenderToStream(
getServerInsertedHTML,
}),
dynamicTracking,
// TODO: Should this include the SSR pass?
collectedRevalidate: reactServerPrerenderStore.revalidate,
collectedTags: reactServerPrerenderStore.tags,
}
} else if (fallbackRouteParams && fallbackRouteParams.size > 0) {
// Rendering the fallback case.
Expand All @@ -2608,6 +2635,9 @@ async function prerenderToStream(
getServerInsertedHTML,
}),
dynamicTracking,
// TODO: Should this include the SSR pass?
collectedRevalidate: reactServerPrerenderStore.revalidate,
collectedTags: reactServerPrerenderStore.tags,
}
} else {
// Static case
Expand Down Expand Up @@ -2661,12 +2691,17 @@ async function prerenderToStream(
getServerInsertedHTML,
}),
dynamicTracking,
// TODO: Should this include the SSR pass?
collectedRevalidate: reactServerPrerenderStore.revalidate,
collectedTags: reactServerPrerenderStore.tags,
}
}
} else {
const prerenderLegacyStore: PrerenderStore = {
type: 'prerender-legacy',
pathname: ctx.requestStore.url.pathname,
revalidate: INFINITE_CACHE,
tags: null,
}
// This is a regular static generation. We don't do dynamic tracking because we rely on
// the old-school dynamic error handling to bail out of static generation
Expand Down Expand Up @@ -2736,6 +2771,9 @@ async function prerenderToStream(
getServerInsertedHTML,
serverInsertedHTMLToHead: true,
}),
// TODO: Should this include the SSR pass?
collectedRevalidate: prerenderLegacyStore.revalidate,
collectedTags: prerenderLegacyStore.tags,
}
}
} catch (err) {
Expand Down Expand Up @@ -2875,6 +2913,9 @@ async function prerenderToStream(
validateRootLayout,
}),
dynamicTracking: null,
// TODO: What should error state have as revalidate?
collectedRevalidate: 0,
collectedTags: null,
}
} catch (finalErr: any) {
if (process.env.NODE_ENV === 'development' && isNotFoundError(finalErr)) {
Expand Down
8 changes: 4 additions & 4 deletions packages/next/src/server/app-render/create-component-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,11 @@ async function createComponentTreeInternal({
ctx.defaultRevalidate = layoutOrPageMod.revalidate as number

if (
typeof workStore.revalidate === 'undefined' ||
(typeof workStore.revalidate === 'number' &&
workStore.revalidate > ctx.defaultRevalidate)
typeof workStore.revalidateSegmentConfig === 'undefined' ||
(typeof workStore.revalidateSegmentConfig === 'number' &&
workStore.revalidateSegmentConfig > ctx.defaultRevalidate)
) {
workStore.revalidate = ctx.defaultRevalidate
workStore.revalidateSegmentConfig = ctx.defaultRevalidate
}

if (
Expand Down
Loading
Loading