perf(nuxt): use LRU prerender cache and limit cached items (#22465)

This commit is contained in:
Daniel Roe 2023-08-04 08:47:42 +01:00 committed by GitHub
parent 2f065184d2
commit 3722325220
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 11 deletions

View File

@ -273,6 +273,12 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
// Init nitro
const nitro = await createNitro(nitroConfig)
// Set prerender-only options
nitro.options._config.storage ||= {}
nitro.options._config.storage['internal:nitro:prerender'] = { driver: 'memory' }
nitro.options._config.storage['internal:nitro:prerender:island'] = { driver: 'lruCache', max: 1000 }
nitro.options._config.storage['internal:nitro:prerender:payload'] = { driver: 'lruCache', max: 1000 }
// Expose nitro to modules and kit
nuxt._nitro = nitro
await nuxt.callHook('nitro:init', nitro)

View File

@ -17,7 +17,7 @@ import { renderToString as _renderToString } from 'vue/server-renderer'
import { hash } from 'ohash'
import { renderSSRHead } from '@unhead/ssr'
import { defineRenderHandler, getRouteRules, useRuntimeConfig } from '#internal/nitro'
import { defineRenderHandler, getRouteRules, useRuntimeConfig, useStorage } from '#internal/nitro'
import { useNitroApp } from '#internal/nitro/app'
import type { Link, Script } from '@unhead/vue'
@ -155,9 +155,18 @@ const getSPARenderer = lazyCachedFunction(async () => {
}
})
const payloadCache = process.env.prerender ? useStorage('internal:nuxt:prerender:payload') : null
const islandCache = process.env.prerender ? useStorage('internal:nuxt:prerender:island') : null
const islandPropCache = process.env.prerender ? useStorage('internal:nuxt:prerender:island-props') : null
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
// TODO: Strict validation for url
const url = event.node.req.url?.substring('/__nuxt_island'.length + 1) || ''
let url = event.node.req.url || ''
if (process.env.prerender && event.node.req.url && await islandPropCache!.hasItem(event.node.req.url)) {
// rehydrate props from cache so we can rerender island if cache does not have it any more
url = await islandPropCache!.getItem(event.node.req.url) as string
}
url = url.substring('/__nuxt_island'.length + 1) || ''
const [componentName, hashId] = url.split('?')[0].split('_')
// TODO: Validate context
@ -175,8 +184,6 @@ async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
return ctx
}
const PAYLOAD_CACHE = (process.env.NUXT_PAYLOAD_EXTRACTION && process.env.prerender) ? new Map() : null // TODO: Use LRU cache
const ISLAND_CACHE = (process.env.NUXT_COMPONENT_ISLANDS && process.env.prerender) ? new Map() : null // TODO: Use LRU cache
const PAYLOAD_URL_RE = process.env.NUXT_JSON_PAYLOADS ? /\/_payload(\.[a-zA-Z0-9]+)?.json(\?.*)?$/ : /\/_payload(\.[a-zA-Z0-9]+)?.js(\?.*)?$/
const ROOT_NODE_REGEX = new RegExp(`^<${appRootTag} id="${appRootId}">([\\s\\S]*)</${appRootTag}>$`)
@ -206,8 +213,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
? await getIslandContext(event)
: undefined
if (process.env.prerender && islandContext && ISLAND_CACHE!.has(event.node.req.url)) {
return ISLAND_CACHE!.get(event.node.req.url)
if (process.env.prerender && islandContext && event.node.req.url && await islandCache!.hasItem(event.node.req.url)) {
return islandCache!.getItem(event.node.req.url) as Promise<Partial<RenderResponse>>
}
// Request url
@ -218,8 +225,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
if (isRenderingPayload) {
url = url.substring(0, url.lastIndexOf('/')) || '/'
event.node.req.url = url
if (process.env.prerender && PAYLOAD_CACHE!.has(url)) {
return PAYLOAD_CACHE!.get(url)
if (process.env.prerender && await payloadCache!.hasItem(url)) {
return payloadCache!.getItem(url) as Promise<Partial<RenderResponse>>
}
}
@ -285,7 +292,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
if (isRenderingPayload) {
const response = renderPayloadResponse(ssrContext)
if (process.env.prerender) {
PAYLOAD_CACHE!.set(url, response)
await payloadCache!.setItem(url, response)
}
return response
}
@ -294,7 +301,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
// Hint nitro to prerender payload for this route
appendResponseHeader(event, 'x-nitro-prerender', joinURL(url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js'))
// Use same ssr context to generate payload for this route
PAYLOAD_CACHE!.set(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
await payloadCache!.setItem(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
}
if (process.env.NUXT_INLINE_STYLES && !islandContext) {
@ -422,7 +429,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
}
} satisfies RenderResponse
if (process.env.prerender) {
ISLAND_CACHE!.set(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response)
await islandCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response)
await islandPropCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, event.node.req.url!)
}
return response
}