Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
More explicit typing for IncrementalCache API (vercel#26941)
Browse files Browse the repository at this point in the history
Make the typing for `IncrementalCache` more explicit. With streaming, we’ll want to stream page data as well as HTML. This is a bit complicated now because we’re overloading `pageData` for both redirects and pages.

This PR makes the different types explicit. With streaming, the data for redirects is synchronously available, while the data for pages will become a stream.

A follow up PR will add a “stream through” cache in front of `IncrementalCache`
  • Loading branch information
devknoll authored Jul 6, 2021
1 parent 77b6d52 commit 345f12b
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 46 deletions.
61 changes: 34 additions & 27 deletions packages/next/server/incremental-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@ function toRoute(pathname: string): string {
return pathname.replace(/\/$/, '').replace(/\/index$/, '') || '/'
}

type IncrementalCacheValue = {
html?: string
pageData?: any
isStale?: boolean
isNotFound?: boolean
isRedirect?: boolean
interface CachedRedirectValue {
kind: 'REDIRECT'
props: Object
}

interface CachedPageValue {
kind: 'PAGE'
html: string
pageData: Object
}

export type IncrementalCacheValue = CachedRedirectValue | CachedPageValue

type IncrementalCacheEntry = {
curRevalidate?: number | false
// milliseconds to revalidate after
revalidateAfter: number | false
isStale?: boolean
value: IncrementalCacheValue | null
}

export class IncrementalCache {
Expand All @@ -29,7 +39,7 @@ export class IncrementalCache {
}

prerenderManifest: PrerenderManifest
cache: LRUCache<string, IncrementalCacheValue>
cache: LRUCache<string, IncrementalCacheEntry>
locales?: string[]

constructor({
Expand Down Expand Up @@ -73,10 +83,10 @@ export class IncrementalCache {
this.cache = new LRUCache({
// default to 50MB limit
max: max || 50 * 1024 * 1024,
length(val) {
if (val.isNotFound || val.isRedirect) return 25
length({ value }) {
if (!value || value.kind === 'REDIRECT') return 25
// rough estimate of size of cache value
return val.html!.length + JSON.stringify(val.pageData).length
return value.html.length + JSON.stringify(value.pageData).length
},
})
}
Expand Down Expand Up @@ -112,16 +122,16 @@ export class IncrementalCache {
}

// get data from cache if available
async get(pathname: string): Promise<IncrementalCacheValue | void> {
if (this.incrementalOptions.dev) return
async get(pathname: string): Promise<IncrementalCacheEntry | null> {
if (this.incrementalOptions.dev) return null
pathname = normalizePagePath(pathname)

let data = this.cache.get(pathname)

// let's check the disk for seed data
if (!data) {
if (this.prerenderManifest.notFoundRoutes.includes(pathname)) {
return { isNotFound: true, revalidateAfter: false }
return { revalidateAfter: false, value: null }
}

try {
Expand All @@ -134,15 +144,21 @@ export class IncrementalCache {
)

data = {
html,
pageData,
revalidateAfter: this.calculateRevalidate(pathname),
value: {
kind: 'PAGE',
html,
pageData,
},
}
this.cache.set(pathname, data)
} catch (_) {
// unable to get data from disk
}
}
if (!data) {
return null
}

if (
data &&
Expand All @@ -164,12 +180,7 @@ export class IncrementalCache {
// populate the incremental cache with new data
async set(
pathname: string,
data: {
html?: string
pageData?: any
isNotFound?: boolean
isRedirect?: boolean
},
data: IncrementalCacheValue | null,
revalidateSeconds?: number | false
) {
if (this.incrementalOptions.dev) return
Expand All @@ -188,17 +199,13 @@ export class IncrementalCache {

pathname = normalizePagePath(pathname)
this.cache.set(pathname, {
...data,
revalidateAfter: this.calculateRevalidate(pathname),
value: data,
})

// TODO: This option needs to cease to exist unless it stops mutating the
// `next build` output's manifest.
if (
this.incrementalOptions.flushToDisk &&
!data.isNotFound &&
!data.isRedirect
) {
if (this.incrementalOptions.flushToDisk && data?.kind === 'PAGE') {
try {
const seedPath = this.getSeedPath(pathname, 'html')
await promises.mkdir(path.dirname(seedPath), { recursive: true })
Expand Down
55 changes: 36 additions & 19 deletions packages/next/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ import prepareDestination, {
} from '../shared/lib/router/utils/prepare-destination'
import { sendPayload, setRevalidateHeaders } from './send-payload'
import { serveStatic } from './serve-static'
import { IncrementalCache } from './incremental-cache'
import { IncrementalCache, IncrementalCacheValue } from './incremental-cache'
import { execOnce } from '../shared/lib/utils'
import { isBlockedPage } from './utils'
import { loadEnvConfig } from '@next/env'
Expand Down Expand Up @@ -1653,30 +1653,25 @@ export default class Server {
}

// Complete the response with cached data if its present
const cachedData = ssgCacheKey
const cacheEntry = ssgCacheKey
? await this.incrementalCache.get(ssgCacheKey)
: undefined

if (cachedData) {
const data = isDataReq
? JSON.stringify(cachedData.pageData)
: cachedData.html

if (cacheEntry) {
const cachedData = cacheEntry.value
const revalidateOptions = !this.renderOpts.dev
? {
// When the page is 404 cache-control should not be added
private: isPreviewMode || is404Page,
stateful: false, // GSP response
revalidate:
cachedData.curRevalidate !== undefined
? cachedData.curRevalidate
cacheEntry.curRevalidate !== undefined
? cacheEntry.curRevalidate
: /* default to minimum revalidate (this should be an invariant) */ 1,
}
: undefined

if (!isDataReq && cachedData.pageData?.pageProps?.__N_REDIRECT) {
await handleRedirect(cachedData.pageData)
} else if (cachedData.isNotFound) {
if (!cachedData) {
if (revalidateOptions) {
setRevalidateHeaders(res, revalidateOptions)
}
Expand All @@ -1689,16 +1684,34 @@ export default class Server {
query,
} as UrlWithParsedQuery)
}
} else if (cachedData.kind === 'REDIRECT') {
if (isDataReq) {
sendPayload(
req,
res,
JSON.stringify(cachedData.props),
'json',
{
generateEtags: this.renderOpts.generateEtags,
poweredByHeader: this.renderOpts.poweredByHeader,
},
revalidateOptions
)
} else {
await handleRedirect(cachedData.props)
}
} else {
respondWith({
type: isDataReq ? 'json' : 'html',
body: data!,
body: isDataReq
? JSON.stringify(cachedData.pageData)
: cachedData.html,
revalidateOptions,
})
}

// Stop the request chain here if the data we sent was up-to-date
if (!cachedData.isStale) {
if (!cacheEntry.isStale) {
return
}
}
Expand Down Expand Up @@ -1912,11 +1925,15 @@ export default class Server {

// Update the cache if the head request and cacheable
if (isOrigin && ssgCacheKey) {
await this.incrementalCache.set(
ssgCacheKey,
{ html: html!, pageData, isNotFound, isRedirect },
sprRevalidate
)
let cacheValue: IncrementalCacheValue | null
if (isNotFound) {
cacheValue = null
} else if (isRedirect) {
cacheValue = { kind: 'REDIRECT', props: pageData }
} else {
cacheValue = { kind: 'PAGE', html, pageData }
}
await this.incrementalCache.set(ssgCacheKey, cacheValue, sprRevalidate)
}

if (!hasResponded() && isNotFound) {
Expand Down

0 comments on commit 345f12b

Please sign in to comment.