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

More explicit typing for IncrementalCache API #26941

Merged
merged 6 commits into from
Jul 6, 2021
Merged
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
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