From b7ba3a4a80e8564ca518c1bc7d172acda4c5a256 Mon Sep 17 00:00:00 2001 From: Ryan Clements Date: Fri, 12 Apr 2024 18:21:02 -0400 Subject: [PATCH] fix(nuxt): hint prerenderer to crawl routes at runtime (#26694) --- packages/nuxt/src/core/nitro.ts | 5 ++- packages/nuxt/src/pages/module.ts | 41 ++++------------- .../pages/runtime/plugins/prerender.server.ts | 45 +++++++++++++++++++ 3 files changed, 57 insertions(+), 34 deletions(-) create mode 100644 packages/nuxt/src/pages/runtime/plugins/prerender.server.ts diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts index 24f82eab8a..fb7df41d05 100644 --- a/packages/nuxt/src/core/nitro.ts +++ b/packages/nuxt/src/core/nitro.ts @@ -507,9 +507,12 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { if (nitro.options.static) { nitro.hooks.hook('prerender:routes', (routes) => { - for (const route of [nuxt.options.ssr ? '/' : '/index.html', '/200.html', '/404.html']) { + for (const route of ['/200.html', '/404.html']) { routes.add(route) } + if (!nuxt.options.ssr) { + routes.add('/index.html') + } }) } diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 8acabb18ab..1fbba760d5 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -3,7 +3,6 @@ import { mkdir, readFile } from 'node:fs/promises' import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, logger, updateTemplates, useNitro } from '@nuxt/kit' import { dirname, join, relative, resolve } from 'pathe' import { genImport, genObjectFromRawEntries, genString } from 'knitwork' -import { joinURL } from 'ufo' import type { Nuxt, NuxtApp, NuxtPage } from 'nuxt/schema' import { createRoutesContext } from 'unplugin-vue-router' import { resolveOptions } from 'unplugin-vue-router/options' @@ -18,8 +17,6 @@ import type { PageMetaPluginOptions } from './plugins/page-meta' import { PageMetaPlugin } from './plugins/page-meta' import { RouteInjectionPlugin } from './plugins/route-injection' -const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/ - export default defineNuxtModule({ meta: { name: 'pages', @@ -266,36 +263,14 @@ export default defineNuxtModule({ }) }) - nuxt.hook('nitro:init', (nitro) => { - if (nuxt.options.dev || !nitro.options.static || nuxt.options.router.options.hashMode) { return } - // Prerender all non-dynamic page routes when generating app - const prerenderRoutes = new Set() - nuxt.hook('pages:extend', (pages) => { - prerenderRoutes.clear() - const processPages = (pages: NuxtPage[], currentPath = '/') => { - for (const page of pages) { - // Add root of optional dynamic paths and catchalls - if (OPTIONAL_PARAM_RE.test(page.path) && !page.children?.length) { prerenderRoutes.add(currentPath) } - // Skip dynamic paths - if (page.path.includes(':')) { continue } - const route = joinURL(currentPath, page.path) - prerenderRoutes.add(route) - if (page.children) { processPages(page.children, route) } - } - } - processPages(pages) - }) - nuxt.hook('nitro:build:before', (nitro) => { - if (nitro.options.prerender.routes.length) { - for (const route of nitro.options.prerender.routes) { - // Skip default route value as we only generate it if it is already - // in the detected routes from `~/pages`. - if (route === '/') { continue } - prerenderRoutes.add(route) - } - } - nitro.options.prerender.routes = Array.from(prerenderRoutes) - }) + nuxt.hook('app:resolve', (app) => { + const nitro = useNitro() + if (nitro.options.prerender.crawlLinks) { + app.plugins.push({ + src: resolve(runtimeDir, 'plugins/prerender.server'), + mode: 'server', + }) + } }) nuxt.hook('imports:extend', (imports) => { diff --git a/packages/nuxt/src/pages/runtime/plugins/prerender.server.ts b/packages/nuxt/src/pages/runtime/plugins/prerender.server.ts new file mode 100644 index 0000000000..31467d17b0 --- /dev/null +++ b/packages/nuxt/src/pages/runtime/plugins/prerender.server.ts @@ -0,0 +1,45 @@ +import type { RouteRecordRaw } from 'vue-router' +import { joinURL } from 'ufo' + +import { defineNuxtPlugin } from '#app/nuxt' +import { prerenderRoutes } from '#app/composables/ssr' +// @ts-expect-error virtual file +import _routes from '#build/routes' +// @ts-expect-error virtual file +import routerOptions from '#build/router.options' + +let routes: string[] + +export default defineNuxtPlugin(async () => { + if (!import.meta.server || !import.meta.prerender || routerOptions.hashMode) { + return + } + if (routes && !routes.length) { return } + + routes ||= Array.from(processRoutes(await routerOptions.routes?.(_routes) ?? _routes)) + const batch = routes.splice(0, 10) + prerenderRoutes(batch) +}) + +// Implementation + +const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/ + +function processRoutes (routes: RouteRecordRaw[], currentPath = '/', routesToPrerender = new Set()) { + for (const route of routes) { + // Add root of optional dynamic paths and catchalls + if (OPTIONAL_PARAM_RE.test(route.path) && !route.children?.length) { + routesToPrerender.add(currentPath) + } + // Skip dynamic paths + if (route.path.includes(':')) { + continue + } + const fullPath = joinURL(currentPath, route.path) + routesToPrerender.add(fullPath) + if (route.children) { + processRoutes(route.children, fullPath, routesToPrerender) + } + } + return routesToPrerender +}