Пререндеринг

Nuxt позволяет рендерить выбранные страницы статически на этапе сборки для улучшения производительности и SEO.

Nuxt может отрендерить часть страниц при сборке. По запросу будут отдаваться уже готовые страницы, а не генерироваться на лету.

Узнать больше Режимы рендеринга Nuxt.

Пререндеринг по обходу ссылок

Команда nuxt generate собирает приложение и пререндерит его с помощью краулера Nitro. По смыслу это то же, что nuxt build с nitro.static: true или nuxt build --prerender.

Собирается сайт, поднимается экземпляр Nuxt, по умолчанию пререндериваются корневая страница / и все страницы, на которые с неё есть ссылки, затем страницы, на которые ссылаются они, и т.д.

npx nuxt generate

Директорию .output/public можно развернуть на любом статическом хостинге или посмотреть локально: npx serve .output/public.

Статические и пререндеренные сборки также создают SPA fallback-файлы 200.html и 404.html. Подробнее: Что такое 200.html и 404.html?.

Как работает краулер Nitro:

  1. Загружается HTML корневого маршрута (/), всех нединамических страниц из ~/pages и маршрутов из nitro.prerender.routes.
  2. HTML и _payload.json сохраняются в ~/.output/public/ для статической раздачи.
  3. В HTML ищутся все ссылки (<a href="...">) для перехода на другие маршруты.
  4. Шаги 1–3 повторяются для каждой найденной ссылки, пока нечего обходить.

Страницы, на которые нет ссылок с обходимых страниц, автоматически пререндерены не будут.

Подробнее о команде nuxt generate.

Выборочный пререндеринг

Маршруты для пререндеринга и исключения можно задать в nuxt.config:

nuxt.config.ts
// @errors: 2353
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/user/1', '/user/2'],
      ignore: ['/dynamic'],
    },
  },
})

В сочетании с crawlLinks: true можно добавить маршруты, которые краулер сам не найдёт (например /sitemap.xml, /robots.txt):

nuxt.config.ts
// @errors: 2353
export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: ['/sitemap.xml', '/robots.txt'],
    },
  },
})

Установка nitro.prerender в true эквивалентна nitro.prerender.crawlLinks: true.

Пререндеринг в документации Nitro.

То же можно задать через routeRules:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/rss.xml': { prerender: true },
    '/this-DOES-NOT-get-prerendered': { prerender: false },
    '/blog/**': { prerender: true },
  },
})
Конфигурация routeRules в Nitro.

Краткая форма в файле страницы — defineRouteRules.

Функция экспериментальная; для использования включите experimental.inlineRouteRules в nuxt.config.
app/pages/index.vue
<script setup>
defineRouteRules({
  prerender: true,
})
</script>

<template>
  <div>
    <h1>Homepage</h1>
    <p>Pre-rendered at build time</p>
  </div>
</template>

Это преобразуется в:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
  },
})

Извлечение payload

Когда Nuxt рендерит страницу на сервере, он сериализует результаты загрузки данных (useAsyncData и useFetch) и состояние приложения (useState) в payload, чтобы клиент мог выполнить гидрацию без повторных запросов. При включённом извлечении payload Nuxt также записывает его в файл _payload.json рядом с HTML маршрута:

  • Пререндеренные маршруты создают файл payload при сборке.
  • Маршруты с кэшированием ISR или SWR создают файл payload при первом рендере, даже на гибридном (не полностью статическом) сайте.

При клиентской навигации Nuxt загружает _payload.json для целевого маршрута и переиспользует извлечённые данные вместо повторного выполнения загрузки в браузере.

Поведение настраивается опцией experimental.payloadExtraction:

  • 'client' — payload встраивается в HTML при первом рендере и извлекается в файлы _payload.json для клиентской навигации. Дополнительного сетевого запроса при первой загрузке нет.
  • true — payload выносится в отдельный _payload.json и при первом рендере, и при клиентской навигации. HTML меньше, файл payload можно кэшировать на CDN, но при первой загрузке добавляется один запрос.
  • false — извлечение payload отключено. Payload всегда встраивается в HTML, файлы _payload.json не создаются.

По умолчанию: true, или 'client' при compatibilityVersion: 5. Принудительно false при ssr: false.

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    payloadExtraction: 'client',
  },
})

Практические последствия:

  • На полностью статическом сайте клиентская навигация переиспользует данные, захваченные при сборке, поэтому они могут устареть до следующей пересборки.
  • Для маршрутов ISR/SWR CDN может кэшировать файлы payload вместе с HTML, улучшая производительность клиентской навигации для закэшированных маршрутов. Динамические маршруты вроде pages/[...slug].vue можно подключить glob-паттернами, например '/**': { isr: true }.
  • Payload сериализуется через devalue, поэтому пользовательские типы (например, экземпляры классов) требуют payload-плагинов с reducers и revivers, чтобы пережить цикл сериализация–десериализация.
Узнать больше Payload reducers и revivers.

Конфигурация пререндеринга в рантайме

prerenderRoutes

В контексте Nuxt можно добавлять маршруты для пререндеринга:

app/pages/index.vue
<script setup>
prerenderRoutes(['/some/other/url'])
prerenderRoutes('/api/content/article/my-article')
</script>

<template>
  <div>
    <h1>This will register other routes for prerendering when prerendered</h1>
  </div>
</template>
Узнать больше prerenderRoutes.

Хук Nuxt prerender:routes

Вызывается перед пререндерингом для регистрации дополнительных маршрутов:

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    async 'prerender:routes' (ctx) {
      const { pages } = await fetch('https://api.some-cms.com/pages').then(
        res => res.json(),
      )
      for (const page of pages) {
        ctx.routes.add(`/${page.name}`)
      }
    },
  },
})

Хук Nitro prerender:generate

Вызывается для каждого маршрута при пререндеринге — можно точечно решать, пропускать ли маршрут:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    hooks: {
      'prerender:generate' (route) {
        if (route.route?.includes('private')) {
          route.skip = true
        }
      },
    },
  },
})