Skip to content

Commit

Permalink
Make the incremental cache storage and cache providers extensible.
Browse files Browse the repository at this point in the history
This will especially allow developers to store pages in object storages like S3 and shared cache like Redis. Thanks to that, all Next instances can share the same cache pool, therefore it increase the overall performances depending on the company infrastructure.

This commit basically changes nothing to the Next.js current features and behaviors. It just allow us to to use plugins to customize that behavior.
  • Loading branch information
MartinLG-LaFourche committed Feb 28, 2021
1 parent 6c59b77 commit d4f53eb
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 27 deletions.
71 changes: 44 additions & 27 deletions packages/next/next-server/server/incremental-cache.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { promises, readFileSync } from 'fs'
import { readFileSync, promises } from 'fs'
import LRUCache from 'next/dist/compiled/lru-cache'
import path from 'path'
import { PrerenderManifest } from '../../build'
import { PRERENDER_MANIFEST } from '../lib/constants'
import {
FileSystemStorageProvider,
StorageProfiderInterface,
} from './lib/storage'
import { normalizePagePath } from './normalize-page-path'

function toRoute(pathname: string): string {
return pathname.replace(/\/$/, '').replace(/\/index$/, '') || '/'
}

type IncrementalCacheValue = {
export type IncrementalCacheValue = {
html?: string
pageData?: any
isStale?: boolean
Expand All @@ -31,6 +35,7 @@ export class IncrementalCache {
prerenderManifest: PrerenderManifest
cache: LRUCache<string, IncrementalCacheValue>
locales?: string[]
storageProvider: StorageProfiderInterface

constructor({
max,
Expand All @@ -39,13 +44,17 @@ export class IncrementalCache {
pagesDir,
flushToDisk,
locales,
storageProvider,
lruCacheProvider,
}: {
dev: boolean
max?: number
distDir: string
pagesDir: string
flushToDisk?: boolean
locales?: string[]
storageProvider?: StorageProfiderInterface
lruCacheProvider?: LRUCache<string, IncrementalCacheValue>
}) {
this.incrementalOptions = {
dev,
Expand All @@ -70,15 +79,19 @@ 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
// rough estimate of size of cache value
return val.html!.length + JSON.stringify(val.pageData).length
},
})
this.storageProvider = storageProvider || new FileSystemStorageProvider()

this.cache =
lruCacheProvider ||
new LRUCache({
// default to 50MB limit
max: max || 50 * 1024 * 1024,
length(val) {
if (val.isNotFound || val.isRedirect) return 25
// rough estimate of size of cache value
return val.html!.length + JSON.stringify(val.pageData).length
},
})
}

private getSeedPath(pathname: string, ext: string): string {
Expand Down Expand Up @@ -116,31 +129,34 @@ export class IncrementalCache {
if (this.incrementalOptions.dev) return
pathname = normalizePagePath(pathname)

let data = this.cache.get(pathname)
// Await in case the actual cache implementation returns a promise.
let data = await this.cache.get(pathname)

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

try {
const html = await promises.readFile(
this.getSeedPath(pathname, 'html'),
'utf8'
const html = await this.storageProvider.readValue(
this.getSeedPath(pathname, 'html')
)
const pageData = JSON.parse(
await promises.readFile(this.getSeedPath(pathname, 'json'), 'utf8')
await this.storageProvider.readValue(
this.getSeedPath(pathname, 'json')
)
)

data = {
html,
pageData,
revalidateAfter: this.calculateRevalidate(pathname),
}
this.cache.set(pathname, data)
// Await in case the actual cache implementation returns a promise.
await this.cache.set(pathname, data)
} catch (_) {
// unable to get data from disk
// unable to get data from storage provider
}
}

Expand Down Expand Up @@ -187,7 +203,8 @@ export class IncrementalCache {
}

pathname = normalizePagePath(pathname)
this.cache.set(pathname, {
// Await in case the actual cache implementation returns a promise.
await this.cache.set(pathname, {
...data,
revalidateAfter: this.calculateRevalidate(pathname),
})
Expand All @@ -196,16 +213,16 @@ export class IncrementalCache {
// `next build` output's manifest.
if (this.incrementalOptions.flushToDisk && !data.isNotFound) {
try {
const seedPath = this.getSeedPath(pathname, 'html')
await promises.mkdir(path.dirname(seedPath), { recursive: true })
await promises.writeFile(seedPath, data.html, 'utf8')
await promises.writeFile(
await this.storageProvider.write(
this.getSeedPath(pathname, 'html'),
data.html
)
await this.storageProvider.write(
this.getSeedPath(pathname, 'json'),
JSON.stringify(data.pageData),
'utf8'
JSON.stringify(data.pageData)
)
} catch (error) {
// failed to flush to disk
// failed to flush to storage provider
console.warn('Failed to update prerender files for', pathname, error)
}
}
Expand Down
19 changes: 19 additions & 0 deletions packages/next/next-server/server/lib/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { promises } from 'fs'

export interface StorageProfiderInterface {
readValue: (key: string) => Promise<string>
write: (key: string, value: string | undefined) => Promise<void>
}

export class FileSystemStorageProvider implements StorageProfiderInterface {
async readValue(key: string): Promise<string> {
return await promises.readFile(key, 'utf8')
}

async write(key: string, value: string | undefined) {
await promises.mkdir(key.substring(0, key.lastIndexOf('/')), {
recursive: true,
})
await promises.writeFile(key, String(value), 'utf8')
}
}
2 changes: 2 additions & 0 deletions packages/next/next-server/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ export default class Server {
),
locales: this.nextConfig.i18n?.locales,
flushToDisk: !minimalMode && this.nextConfig.experimental.sprFlushToDisk,
storageProvider: this.nextConfig.storageProvider,
lruCacheProvider: this.nextConfig.lruCacheProvider,
})

/**
Expand Down

0 comments on commit d4f53eb

Please sign in to comment.