mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-14 18:13:54 +00:00
fix(nuxt): use routeRules
to hint pages to prerender (#29172)
This commit is contained in:
parent
959061fc29
commit
781d8c4174
@ -8,6 +8,7 @@ import { hash } from 'ohash'
|
||||
import { camelCase } from 'scule'
|
||||
import { filename } from 'pathe/utils'
|
||||
import type { NuxtTemplate } from 'nuxt/schema'
|
||||
import type { Nitro } from 'nitro/types'
|
||||
|
||||
import { annotatePlugins, checkForCircularDependencies } from './app'
|
||||
|
||||
@ -516,6 +517,7 @@ export const nuxtConfigTemplate: NuxtTemplate = {
|
||||
`export const outdatedBuildInterval = ${ctx.nuxt.options.experimental.checkOutdatedBuildInterval}`,
|
||||
`export const multiApp = ${!!ctx.nuxt.options.future.multiApp}`,
|
||||
`export const chunkErrorEvent = ${ctx.nuxt.options.experimental.emitRouteChunkError ? ctx.nuxt.options.builder === '@nuxt/vite-builder' ? '"vite:preloadError"' : '"nuxt:preloadError"' : 'false'}`,
|
||||
`export const crawlLinks = ${!!((ctx.nuxt as any)._nitro as Nitro).options.prerender.crawlLinks}`,
|
||||
].join('\n\n')
|
||||
},
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import type { Nuxt, NuxtApp, NuxtPage } from 'nuxt/schema'
|
||||
import { createRoutesContext } from 'unplugin-vue-router'
|
||||
import { resolveOptions } from 'unplugin-vue-router/options'
|
||||
import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router'
|
||||
import { createRouter as createRadixRouter, toRouteMatcher } from 'radix3'
|
||||
|
||||
import type { NitroRouteConfig } from 'nitro/types'
|
||||
import { defu } from 'defu'
|
||||
@ -277,7 +278,7 @@ export default defineNuxtModule({
|
||||
|
||||
nuxt.hook('app:resolve', (app) => {
|
||||
const nitro = useNitro()
|
||||
if (nitro.options.prerender.crawlLinks) {
|
||||
if (nitro.options.prerender.crawlLinks || Object.values(nitro.options.routeRules).some(rule => rule.prerender)) {
|
||||
app.plugins.push({
|
||||
src: resolve(runtimeDir, 'plugins/prerender.server'),
|
||||
mode: 'server',
|
||||
@ -315,7 +316,20 @@ export default defineNuxtModule({
|
||||
})
|
||||
|
||||
nuxt.hook('nitro:build:before', (nitro) => {
|
||||
if (nuxt.options.dev || !nitro.options.static || nuxt.options.router.options.hashMode || !nitro.options.prerender.crawlLinks) { return }
|
||||
if (nuxt.options.dev || nuxt.options.router.options.hashMode) { return }
|
||||
|
||||
// Inject page patterns that explicitly match `prerender: true` route rule
|
||||
if (!nitro.options.static && !nitro.options.prerender.crawlLinks) {
|
||||
const routeRulesMatcher = toRouteMatcher(createRadixRouter({ routes: nitro.options.routeRules }))
|
||||
for (const route of prerenderRoutes) {
|
||||
const rules = defu({} as Record<string, any>, ...routeRulesMatcher.matchAll(route).reverse())
|
||||
if (rules.prerender) {
|
||||
nitro.options.prerender.routes.push(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!nitro.options.static || !nitro.options.prerender.crawlLinks) { return }
|
||||
|
||||
// Only hint the first route when `ssr: true` and no routes are provided
|
||||
// as the rest will be injected at runtime when this is prerendered
|
||||
|
@ -1,20 +1,31 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { joinURL } from 'ufo'
|
||||
import { createRouter as createRadixRouter, toRouteMatcher } from 'radix3'
|
||||
import defu from 'defu'
|
||||
|
||||
import { defineNuxtPlugin } from '#app/nuxt'
|
||||
import { defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
|
||||
import { prerenderRoutes } from '#app/composables/ssr'
|
||||
// @ts-expect-error virtual file
|
||||
import _routes from '#build/routes'
|
||||
import routerOptions from '#build/router.options'
|
||||
// @ts-expect-error virtual file
|
||||
import { crawlLinks } from '#build/nuxt.config.mjs'
|
||||
|
||||
let routes: string[]
|
||||
|
||||
let _routeRulesMatcher: undefined | ReturnType<typeof toRouteMatcher> = undefined
|
||||
|
||||
export default defineNuxtPlugin(async () => {
|
||||
if (!import.meta.server || !import.meta.prerender || routerOptions.hashMode) {
|
||||
return
|
||||
}
|
||||
if (routes && !routes.length) { return }
|
||||
|
||||
const routeRules = useRuntimeConfig().nitro!.routeRules
|
||||
if (!crawlLinks && routeRules && Object.values(routeRules).some(r => r.prerender)) {
|
||||
_routeRulesMatcher = toRouteMatcher(createRadixRouter({ routes: routeRules }))
|
||||
}
|
||||
|
||||
routes ||= Array.from(processRoutes(await routerOptions.routes?.(_routes) ?? _routes))
|
||||
const batch = routes.splice(0, 10)
|
||||
prerenderRoutes(batch)
|
||||
@ -24,10 +35,14 @@ export default defineNuxtPlugin(async () => {
|
||||
|
||||
const OPTIONAL_PARAM_RE = /^\/?:.*(?:\?|\(\.\*\)\*)$/
|
||||
|
||||
function shouldPrerender (path: string) {
|
||||
return !_routeRulesMatcher || defu({} as Record<string, any>, ..._routeRulesMatcher.matchAll(path).reverse()).prerender
|
||||
}
|
||||
|
||||
function processRoutes (routes: RouteRecordRaw[], currentPath = '/', routesToPrerender = new Set<string>()) {
|
||||
for (const route of routes) {
|
||||
// Add root of optional dynamic paths and catchalls
|
||||
if (OPTIONAL_PARAM_RE.test(route.path) && !route.children?.length) {
|
||||
if (OPTIONAL_PARAM_RE.test(route.path) && !route.children?.length && shouldPrerender(currentPath)) {
|
||||
routesToPrerender.add(currentPath)
|
||||
}
|
||||
// Skip dynamic paths
|
||||
@ -35,7 +50,9 @@ function processRoutes (routes: RouteRecordRaw[], currentPath = '/', routesToPre
|
||||
continue
|
||||
}
|
||||
const fullPath = joinURL(currentPath, route.path)
|
||||
routesToPrerender.add(fullPath)
|
||||
if (shouldPrerender(fullPath)) {
|
||||
routesToPrerender.add(fullPath)
|
||||
}
|
||||
if (route.children) {
|
||||
processRoutes(route.children, fullPath, routesToPrerender)
|
||||
}
|
||||
|
@ -620,6 +620,11 @@ describe('pages', () => {
|
||||
expect(status).toBe(200)
|
||||
}
|
||||
})
|
||||
|
||||
it.skipIf(isDev() || isWebpack /* TODO: fix bug with import.meta.prerender being undefined in webpack build */)('prerenders pages hinted with a route rule', async () => {
|
||||
const html = await $fetch('/prerender/test')
|
||||
expect(html).toContain('should be prerendered: true')
|
||||
})
|
||||
})
|
||||
|
||||
describe('nuxt composables', () => {
|
||||
|
1
test/fixtures/basic/nuxt.config.ts
vendored
1
test/fixtures/basic/nuxt.config.ts
vendored
@ -66,6 +66,7 @@ export default defineNuxtConfig({
|
||||
'/route-rules/middleware': { appMiddleware: 'route-rules-middleware' },
|
||||
'/hydration/spa-redirection/**': { ssr: false },
|
||||
'/no-scripts': { experimentalNoScripts: true },
|
||||
'/prerender/**': { prerender: true },
|
||||
},
|
||||
prerender: {
|
||||
routes: [
|
||||
|
9
test/fixtures/basic/pages/prerender/test.vue
vendored
Normal file
9
test/fixtures/basic/pages/prerender/test.vue
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const wasPrerendered = useState(() => import.meta.prerender)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
should be prerendered: {{ wasPrerendered }}
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user