Режимы рендеринга

Узнайте о различных режимах рендеринга в Nuxt.

Nuxt поддерживает разные режимы рендеринга: универсальный рендеринг, клиентский рендеринг, а также гибридный рендеринг и возможность рендерить приложение на CDN edge-серверах.

И браузер, и сервер могут выполнять JavaScript, превращая компоненты Vue.js в HTML. Этот шаг называется рендерингом. Nuxt поддерживает и универсальный, и клиентский рендеринг. У обоих подходов есть плюсы и минусы, о которых мы расскажем.

По умолчанию Nuxt использует универсальный рендеринг для лучшего UX, производительности и индексации в поисковиках, но режим можно изменить одной строкой конфигурации.

Универсальный рендеринг

Этот шаг похож на классический серверный рендеринг в PHP или Ruby. При запросе URL с включённым универсальным рендерингом Nuxt выполняет JavaScript (Vue.js) на сервере и отдаёт браузеру готовую HTML-страницу. Nuxt может также вернуть уже сгенерированную страницу из кэша. Пользователь сразу получает полное начальное содержимое приложения — в отличие от клиентского рендеринга.

После загрузки HTML браузер его обрабатывает, и Vue.js «подхватывает» документ. Тот же код, что выполнялся на сервере, снова выполняется в браузере при гидрации и «оживляет» страницу, подключая обработчики к уже отрисованному HTML. Это называется гидрацией. После гидрации страница становится интерактивной: переходы, динамика и т.д.

Универсальный рендеринг даёт быструю загрузку и сохраняет преимущества клиентского приложения. Кроме того, контент уже есть в HTML, поэтому поисковые роботы могут его индексировать без дополнительной нагрузки.

Что рендерится на сервере, а что — на клиенте?

В режиме универсального рендеринга логично спросить, какие части .vue-файла выполняются на сервере и/или клиенте.

app/app.vue
<script setup lang="ts">
const counter = ref(0) // выполняется и на сервере, и на клиенте

const handleClick = () => {
  counter.value++ // выполняется только на клиенте
}
</script>

<template>
  <div>
    <p>Счёт: {{ counter }}</p>
    <button @click="handleClick">
      Увеличить
    </button>
  </div>
</template>

При первом запросе ref счётчика инициализируется на сервере, так как он отображается в <p>. Код внутри handleClick на сервере не выполняется. При гидрации в браузере ref инициализируется заново, а handleClick привязывается к кнопке; поэтому тело handleClick всегда выполняется в браузере.

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

Плюсы серверного рендеринга:

  • Производительность: контент отображается сразу, так как статический HTML показывается быстрее, чем сгенерированный через JavaScript. При этом Nuxt сохраняет интерактивность после гидрации.
  • SEO: универсальный рендеринг отдаёт полный HTML страницы, как классическое серверное приложение. Краулеры могут индексировать контент напрямую — это удобно для любого контента, который нужно быстро проиндексировать.

Минусы серверного рендеринга:

  • Ограничения разработки: окружения сервера и браузера различаются по API, и писать код, одинаково работающий с обеих сторон, бывает непросто. Nuxt даёт рекомендации и переменные, чтобы понимать, где выполняется код.
  • Затраты: нужен работающий сервер для рендеринга по запросу — это дополнительные расходы. Благодаря универсальному рендерингу и переходам на клиенте число обращений к серверу снижается. Дополнительно можно снизить затраты за счёт edge-side-rendering.

Универсальный рендеринг подходит почти для любых задач, особенно для блогов, маркетинговых сайтов, портфолио, интернет-магазинов и маркетплейсов.

Больше примеров кода без рассинхрона при гидрации — в документации Vue.
При импорте библиотеки, которая использует API браузера и имеет побочные эффекты, убедитесь, что компонент, который её импортирует, вызывается только на клиенте. Бандлеры не вырезают импорты модулей с побочными эффектами.

Клиентский рендеринг

В классическом Vue.js-приложении по умолчанию рендеринг происходит в браузере (на клиенте). Vue.js создаёт HTML-элементы после того, как браузер загрузит и выполнит весь JavaScript с инструкциями по отрисовке интерфейса.

Плюсы клиентского рендеринга:

  • Скорость разработки: при работе только на клиенте не нужно заботиться о совместимости с сервером — можно использовать только браузерные API, например объект window.
  • Дешевле: сервер требует инфраструктуры. Клиентское приложение можно разместить на любом статическом хостинге с HTML, CSS и JS.
  • Офлайн: код выполняется целиком в браузере, поэтому приложение может работать без интернета.

Минусы клиентского рендеринга:

  • Производительность: пользователь ждёт загрузки, разбора и выполнения JavaScript. Время зависит от сети и устройства и может ухудшать опыт.
  • SEO: индексация и обновление контента при чисто клиентском рендеринге занимает больше времени, чем при серверном HTML. Краулеры не ждут полной отрисовки интерфейса при первой попытке индексации, поэтому контент дольше появляется и обновляется в результатах поиска.

Клиентский рендеринг подходит для сильно интерактивных веб-приложений, которым не нужна индексация или которые пользователи открывают часто: SaaS, бэк-офис, онлайн-игры.

Включить только клиентский рендеринг в Nuxt можно в nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  ssr: false,
})
При ssr: false рекомендуется положить в ~/spa-loading-template.html HTML-файл с экраном загрузки, который будет показываться до гидрации приложения.
Узнать больше Шаблон загрузки SPA.

Деплой статического клиентского приложения

При деплое на статический хостинг командами nuxt generate или nuxt build --prerender Nuxt по умолчанию рендерит каждую страницу в отдельный статический HTML-файл.

При пререндере командами nuxt generate или nuxt build --prerender серверные эндпоинты недоступны — в выходной папке сервера не будет. Если нужна серверная логика, используйте nuxt build.

При чисто клиентском рендеринге отдельные HTML-файлы для каждой страницы могут быть не нужны: достаточно одного index.html, плюс запасные 200.html и 404.html, которые хостинг будет отдавать на все запросы.

Чтобы так сделать, измените список пререндериваемых маршрутов в хуках в nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    'prerender:routes' ({ routes }) {
      routes.clear() // не генерировать маршруты (кроме дефолтных)
    },
  },
})

В результате будут созданы три файла:

  • index.html
  • 200.html
  • 404.html

200.html и 404.html могут понадобиться в зависимости от хостинга.

Отключение генерации клиентских fallback-файлов

При пререндере клиентского приложения Nuxt по умолчанию создаёт index.html, 200.html и 404.html. Чтобы запретить генерацию любого (или всех) из них, используйте хук Nitro prerender:generate.

nuxt.config.ts
// @errors: 2353 7006
export default defineNuxtConfig({
  ssr: false,
  nitro: {
    hooks: {
      'prerender:generate' (route) {
        const routesToSkip = ['/index.html', '/200.html', '/404.html']
        if (routesToSkip.includes(route.route)) {
          route.skip = true
        }
      },
    },
  },
})

Гибридный рендеринг

Гибридный рендеринг позволяет задавать разные правила кэширования и рендеринга для маршрутов через Route Rules и управлять тем, как сервер отвечает на запрос по заданному URL.

Раньше все страницы и сервер Nuxt работали в одном режиме — универсальном или клиентском. На практике часть страниц удобнее генерировать при сборке, а другую — рендерить на клиенте. Например, контент-сайт с админкой: контентные страницы — статические и генерируются один раз, а админка требует авторизации и ведёт себя как динамическое приложение.

В Nuxt есть правила маршрутов и гибридный рендеринг. Через route rules можно задать правила для группы маршрутов, изменить режим рендеринга или стратегию кэширования.

Nuxt автоматически регистрирует соответствующие мидлвары и оборачивает маршруты обработчиками кэша через слой кэширования Nitro.

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Главная пререндерится при сборке
    '/': { prerender: true },
    // Страница товаров — по запросу, фоновое обновление, кэш до изменения ответа API
    '/products': { swr: true },
    // Страницы товаров — по запросу, кэш 1 час (3600 сек)
    '/products/**': { swr: 3600 },
    // Список постов — по запросу, кэш на CDN 1 час
    '/blog': { isr: 3600 },
    // Отдельный пост — по запросу один раз до следующего деплоя, кэш на CDN
    '/blog/**': { isr: true },
    // Админка только на клиенте
    '/admin/**': { ssr: false },
    // CORS для API
    '/api/**': { cors: true },
    // Редирект со старых URL
    '/old-page': { redirect: '/new-page' },
  },
})

Правила маршрутов (Route Rules)

Доступные свойства:

  • redirect: string — серверные редиректы.
  • ssr: boolean — отключить серверный рендеринг HTML для части приложения (ssr: false — только в браузере).
  • cors: boolean — добавить CORS-заголовки (cors: true); можно переопределить через headers.
  • headers: object — задать заголовки для части сайта (например, ассетов).
  • swr: number | boolean — заголовки кэширования и кэш ответа на сервере или за прокси с настраиваемым TTL. Пресет Nitro node-server кэширует полный ответ. После истечения TTL отдаётся кэш, а страница регенерируется в фоне. При true добавляется заголовок stale-while-revalidate без MaxAge.
  • isr: number | boolean — то же, что swr, но с кэшированием на CDN (поддерживается, в частности, Netlify и Vercel). При true контент хранится в CDN до следующего деплоя.
  • prerender: boolean — пререндер маршрутов при сборке, результат попадает в билд как статика.
  • noScripts: boolean — не подключать скрипты Nuxt и подсказки ресурсов для части сайта.
  • appMiddleware: string | string[] | Record<string, boolean> — задать мидлвары, которые должны или не должны выполняться для путей страниц во Vue-части приложения (не Nitro-маршруты).
При использовании isr или swr также генерируются файлы _payload.json. При клиентской навигации загружаются эти кэшированные данные вместо повторного запроса. Динамические маршруты вроде pages/[...slug].vue настраивайте через glob: '/**': { isr: true }.

По возможности правила маршрутов преобразуются в нативные правила платформы деплоя для оптимальной работы (сейчас поддерживаются Netlify и Vercel).

Гибридный рендеринг недоступен при использовании nuxt generate.

Примеры:

Nuxt Vercel ISR

Пример Nuxt-приложения с гибридным рендерингом на Vercel.

Edge-Side Rendering

Edge-Side Rendering (ESR) — возможность рендерить Nuxt-приложение ближе к пользователю на edge-серверах CDN. Это уменьшает задержки и улучшает производительность.

При ESR рендеринг переносится на «край» сети — edge-серверы CDN. ESR скорее целевая среда деплоя, чем отдельный режим рендеринга.

При запросе страницы он обрабатывается ближайшим edge-сервером, а не исходным сервером. Этот сервер генерирует HTML и отдаёт его пользователю, сокращая задержку и ускоряя загрузку.

Edge-рендеринг реализован в Nitroсерверном движке Nuxt. Nitro поддерживает Node.js, Deno, Cloudflare Workers и другие платформы.

Платформы с поддержкой ESR:

  • Cloudflare Pages — нулевая конфигурация при деплое через Git и команде nuxt build
  • Vercel Cloud — команда nuxt build и переменная окружения NITRO_PRESET=vercel-edge
  • Netlify Edge Functions — команда nuxt build и переменная NITRO_PRESET=netlify-edge

Гибридный рендеринг можно использовать вместе с Edge-Side Rendering через правила маршрутов.