Skip to content

Commit

Permalink
refactor: update fallbacks to use response cache
Browse files Browse the repository at this point in the history
  • Loading branch information
wyattjoh committed Aug 16, 2024
1 parent dfa51e9 commit 5aae257
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 111 deletions.
90 changes: 52 additions & 38 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,6 @@ export default abstract class Server<
req: ServerRequest,
parsedUrl: NextUrlWithParsedQuery
): void
protected abstract getFallback(page: string): Promise<string>
protected abstract hasPage(pathname: string): Promise<boolean>

protected abstract sendRenderResult(
Expand Down Expand Up @@ -2313,7 +2312,7 @@ export default abstract class Server<
} catch {}

// use existing incrementalCache instance if available
const incrementalCache =
const incrementalCache: import('./lib/incremental-cache').IncrementalCache =
(globalThis as any).__incrementalCache ||
(await this.getIncrementalCache({
requestHeaders: Object.assign({}, req.headers),
Expand All @@ -2331,12 +2330,17 @@ export default abstract class Server<
* a render that has been postponed.
*/
postponed: string | undefined

/**
* Whether or not this render is a fallback render.
*/
isFallback: boolean | undefined
}
type Renderer = (
context: RendererContext
) => Promise<ResponseCacheEntry | null>

const doRender: Renderer = async ({ postponed }) => {
const doRender: Renderer = async ({ postponed, isFallback }) => {
// In development, we always want to generate dynamic HTML.
let supportsDynamicResponse: boolean =
// If we're in development, we always support dynamic HTML, unless it's
Expand Down Expand Up @@ -2500,6 +2504,7 @@ export default abstract class Server<
body: Buffer.from(await blob.arrayBuffer()),
headers,
},
isFallback: false,
revalidate,
}

Expand Down Expand Up @@ -2657,7 +2662,11 @@ export default abstract class Server<

// Handle `isNotFound`.
if ('isNotFound' in metadata && metadata.isNotFound) {
return { value: null, revalidate: metadata.revalidate }
return {
value: null,
revalidate: metadata.revalidate,
isFallback: false,
}
}

// Handle `isRedirect`.
Expand All @@ -2668,6 +2677,7 @@ export default abstract class Server<
props: metadata.pageData ?? metadata.flightData,
} satisfies CachedRedirectValue,
revalidate: metadata.revalidate,
isFallback: false,
}
}

Expand All @@ -2688,6 +2698,7 @@ export default abstract class Server<
status: res.statusCode,
} satisfies CachedAppPageValue,
revalidate: metadata.revalidate,
isFallback,
}
}

Expand All @@ -2700,14 +2711,15 @@ export default abstract class Server<
status: isAppPath ? res.statusCode : undefined,
} satisfies CachedPageValue,
revalidate: metadata.revalidate,
isFallback,
}
}

const responseGenerator: ResponseGenerator = async (
const responseGenerator: ResponseGenerator = async ({
hasResolved,
previousCacheEntry,
isRevalidating
): Promise<ResponseCacheEntry | null> => {
isRevalidating,
}): Promise<ResponseCacheEntry | null> => {
const isProduction = !this.renderOpts.dev
const didRespond = hasResolved || res.sent

Expand Down Expand Up @@ -2817,37 +2829,33 @@ export default abstract class Server<
throw new NoFallbackError()
}

if (!isNextDataRequest) {
// Production already emitted the fallback as static HTML.
if (isProduction) {
const html = await this.getFallback(
locale ? `/${locale}${pathname}` : pathname
)

return {
value: {
kind: CachedRouteKind.PAGES,
html: RenderResult.fromStatic(html),
pageData: {},
status: undefined,
headers: undefined,
},
}
}
// We need to generate the fallback on-demand for development.
else {
query.__nextFallback = 'true'

// We pass `undefined` as there cannot be a postponed state in
// development.
const result = await doRender({ postponed: undefined })
if (!result) {
return null
if (!isNextDataRequest && !isRSCRequest) {
const key = isProduction
? isAppPath
? pathname
: locale
? `/${locale}${pathname}`
: pathname
: null

return this.responseCache.get(
key,
async () => {
query.__nextFallback = 'true'

return doRender({
// We pass `undefined` as rendering a fallback isn't resumed here.
postponed: undefined,
isFallback: true,
})
},
{
routeKind: isAppPath ? RouteKind.APP_PAGE : RouteKind.PAGES,
incrementalCache,
isRoutePPREnabled,
isFallback: true,
}
// Prevent caching this result
delete result.revalidate
return result
}
)
}
}

Expand All @@ -2858,6 +2866,7 @@ export default abstract class Server<
!isOnDemandRevalidate && !isRevalidating && minimalPostponed
? minimalPostponed
: undefined,
isFallback: false,
}

// When we're in minimal mode, if we're trying to debug the static shell,
Expand All @@ -2868,6 +2877,7 @@ export default abstract class Server<
) {
return {
revalidate: 1,
isFallback: false,
value: {
kind: CachedRouteKind.PAGES,
html: RenderResult.fromStatic(''),
Expand Down Expand Up @@ -2904,6 +2914,7 @@ export default abstract class Server<
isOnDemandRevalidate,
isPrefetch: req.headers.purpose === 'prefetch',
isRoutePPREnabled,
isFallback: false,
}
)

Expand Down Expand Up @@ -3246,7 +3257,10 @@ export default abstract class Server<
// Perform the render again, but this time, provide the postponed state.
// We don't await because we want the result to start streaming now, and
// we've already chained the transformer's readable to the render result.
doRender({ postponed: cachedData.postponed })
doRender({
postponed: cachedData.postponed,
isFallback: false,
})
.then(async (result) => {
if (!result) {
throw new Error('Invariant: expected a result to be returned')
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/image-optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ export class ImageOptimizerCache {
revalidateAfter:
Math.max(maxAge, this.nextConfig.images.minimumCacheTTL) * 1000 +
Date.now(),
isFallback: false,
curRevalidate: maxAge,
isStale: now > expireAt,
}
Expand Down
84 changes: 48 additions & 36 deletions packages/next/src/server/lib/incremental-cache/file-system-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export default class FileSystemCache implements CacheHandler {

public async get(...args: Parameters<CacheHandler['get']>) {
const [key, ctx] = args
const { tags, softTags, kind, isRoutePPREnabled } = ctx
const { tags, softTags, kind, isRoutePPREnabled, isFallback } = ctx

let data = memoryCache?.get(key)

Expand Down Expand Up @@ -252,24 +252,27 @@ export default class FileSystemCache implements CacheHandler {
}
} else if (kind === IncrementalCacheKind.APP_PAGE) {
let meta: RouteMetadata | undefined
let rscData: Buffer | undefined

// We try to load the metadata file, but if it fails, we don't
// error.
try {
meta = JSON.parse(
await this.fs.readFile(
filePath.replace(/\.html$/, NEXT_META_SUFFIX),
'utf8'
// error. We also don't load it if this is a fallback.
if (!isFallback) {
try {
meta = JSON.parse(
await this.fs.readFile(
filePath.replace(/\.html$/, NEXT_META_SUFFIX),
'utf8'
)
)
)
} catch {}
} catch {}

const rscData = await this.fs.readFile(
this.getFilePath(
`${key}${isRoutePPREnabled ? RSC_PREFETCH_SUFFIX : RSC_SUFFIX}`,
IncrementalCacheKind.APP_PAGE
rscData = await this.fs.readFile(
this.getFilePath(
`${key}${isRoutePPREnabled ? RSC_PREFETCH_SUFFIX : RSC_SUFFIX}`,
IncrementalCacheKind.APP_PAGE
)
)
)
}

data = {
lastModified: mtime.getTime(),
Expand All @@ -284,16 +287,19 @@ export default class FileSystemCache implements CacheHandler {
}
} else if (kind === IncrementalCacheKind.PAGES) {
let meta: RouteMetadata | undefined
let pageData: string | object = {}

const pageData = JSON.parse(
await this.fs.readFile(
this.getFilePath(
`${key}${NEXT_DATA_SUFFIX}`,
IncrementalCacheKind.PAGES
),
'utf8'
if (!isFallback) {
pageData = JSON.parse(
await this.fs.readFile(
this.getFilePath(
`${key}${NEXT_DATA_SUFFIX}`,
IncrementalCacheKind.PAGES
),
'utf8'
)
)
)
}

data = {
lastModified: mtime.getTime(),
Expand Down Expand Up @@ -376,6 +382,7 @@ export default class FileSystemCache implements CacheHandler {

public async set(...args: Parameters<CacheHandler['set']>) {
const [key, data, ctx] = args
const { isFallback } = ctx
memoryCache?.set(key, {
value: data,
lastModified: Date.now(),
Expand Down Expand Up @@ -417,25 +424,30 @@ export default class FileSystemCache implements CacheHandler {
await this.fs.mkdir(path.dirname(htmlPath))
await this.fs.writeFile(htmlPath, data.html)

await this.fs.writeFile(
this.getFilePath(
`${key}${
// Fallbacks don't generate a data file.
if (!isFallback) {
await this.fs.writeFile(
this.getFilePath(
`${key}${
isAppPath
? ctx.isRoutePPREnabled
? RSC_PREFETCH_SUFFIX
: RSC_SUFFIX
: NEXT_DATA_SUFFIX
}`,
isAppPath
? ctx.isRoutePPREnabled
? RSC_PREFETCH_SUFFIX
: RSC_SUFFIX
: NEXT_DATA_SUFFIX
}`,
isAppPath ? IncrementalCacheKind.APP_PAGE : IncrementalCacheKind.PAGES
),
isAppPath ? data.rscData : JSON.stringify(data.pageData)
)
? IncrementalCacheKind.APP_PAGE
: IncrementalCacheKind.PAGES
),
isAppPath ? data.rscData : JSON.stringify(data.pageData)
)
}

if (data.headers || data.status || (isAppPath && data.postponed)) {
if (data?.kind === CachedRouteKind.APP_PAGE) {
const meta: RouteMetadata = {
headers: data.headers,
status: data.status,
postponed: isAppPath ? data.postponed : undefined,
postponed: data.postponed,
}

await this.fs.writeFile(
Expand Down
Loading

0 comments on commit 5aae257

Please # to comment.