Skip to main content

Які режими рендерингу в Nuxt (SSR, SSG, SPA, Hybrid)?

Режими рендерингу Nuxt визначають де і коли генерується HTML: на сервері при кожному запиті (SSR), заздалегідь під час збірки (SSG), у браузері клієнта (SPA), або по-різному для кожного роута (Hybrid).

Теорія

TL;DR

  • SSR = кухня ресторану, яка готує страву під кожне замовлення. SSG = страви приготовані заздалегідь і чекають на видачу. SPA = продукти привезли додому і готуєш сам. Hybrid = для кожної страви свій підхід.
  • Головний поділ: SSR і SSG віддають готовий HTML з сервера. SPA надсилає JS-бандл, а браузер збирає сторінку самостійно.
  • SEO + свіжі дані? SSR. Статичний контент + CDN? SSG. Дашборд за логіном? SPA. Змішаний сайт? Hybrid через routeRules.
  • В основі всіх чотирьох режимів - Nitro, серверний рушій Nuxt.

Швидкий приклад

typescript
// nuxt.config.ts - чотири режими в одному файлі export default defineNuxtConfig({ // SSR (за замовчуванням): сервер рендерить HTML при кожному запиті ssr: true, // SSG: запусти `npx nuxi generate` - отримаєш папку /dist зі статичними HTML-файлами // ssr: true + команда nuxi generate // SPA: браузер робить все сам, сервер віддає порожню оболонку // ssr: false // Hybrid (Nuxt 3): правила рендерингу по роутах routeRules: { '/blog/**': { ssr: true }, // SSR для SEO '/dashboard/**': { ssr: false } // SPA для інтерактивності } })

Запусти npx nuxi dev з ssr: true і побачиш серверні логи при кожному переході на сторінку. Переключи на ssr: false і логи зникнуть. Браузер тепер сам з усім розбирається.

Ключова різниця

SSR і SSG обидва генерують справжній HTML, який одразу отримують і пошукові краулери, і користувачі. Відмінність у часі: SSR генерує HTML у момент запиту, SSG - один раз під час збірки. SPA взагалі не генерує HTML на сервері. Він надсилає JS-бандл, і браузер мусить завантажити, розібрати та виконати JavaScript перш ніж з'явиться будь-який контент. Ця затримка шкодить і SEO, і відчуттю швидкості.

Коли що використовувати

  • SEO + контент що часто оновлюється: SSR. Новинні сайти, сторінки товарів e-commerce, все де дані міняються щогодини.
  • Статичний контент, швидкість CDN: SSG. Документація, маркетингові сторінки, лендінги. Сам nuxt.com побудований через nuxi generate.
  • Інтерактивний додаток без SEO: SPA. Адмін-панелі, дашборди, все що за авторизацією.
  • Змішаний сайт (блог + адмін): Hybrid через routeRules. Роути блогу отримують SSR, адмін - SPA.
  • Персоналізовані дані: SSR або Hybrid, де useAsyncData або useFetch виконується на сервері.

Таблиця порівняння

ПараметрSSRSSGSPAHybrid
Доставка HTMLСервер при запитіСтатичні файли зі збіркиПорожня оболонка + JSПо-різному для кожного роута
SEOВідміннеВідміннеПогане (залежить від JS)Залежить від роута
TTFBСередній (обчислення сервера)Найшвидший (CDN edge)Повільний (завантаження + виконання JS)Оптимізовано для кожного роута
Динамічні даніПовна підтримка (useAsyncData)Тільки під час збіркиЗапити з клієнтаЗмішано
Час збіркиШвидкоЗростає з кількістю сторінокНайшвидшеЯк SSR + статичні роути
Витрати на серверВисокі (кожен запит)Нуль (CDN)Нуль після деплоюНизькі (тільки SSR-роути)
Підходить дляE-commerce, блогиДокументація, лендінгиАдмін-панеліБільшість реальних проектів

Як Nitro це обробляє

Nitro, серверний рушій Nuxt, компілює Vue-компоненти в серверні рендерери для SSR і SSG. У режимі SSR Nitro виконує useAsyncData і useFetch в Node.js при кожному запиті, серіалізує результат в HTML і додає JSON-пейлоад у теги <script> для клієнтської гідрації. SSG робить те саме, але один раз при збірці і записує .html-файли. Режим SPA повністю оминає рендерер Nitro. Vite збирає бандл для браузера. Hybrid-режим аналізує routeRules і маршрутизує SSR-шляхи до обробників Nitro, а SPA-шляхи до клієнтської точки входу.

Повна послідовність для Hybrid-запиту до /blog/post: запит потрапляє до Nitro, збігається з правилом ssr: true, Vue рендериться у рядок HTML через @vue/server-renderer, виконується useAsyncData в Node, JSON-пейлоад додається в <script>, відповідь стримується клієнту.

Типові помилки

1. ssr: false на сайті що потребує SEO.

typescript
// Неправильно: краулери отримують порожній div export default defineNuxtConfig({ ssr: false }) // curl http://localhost:3000 → <div id="__nuxt"></div> // Правильно: тримай SSR і перевіряй через curl export default defineNuxtConfig({ ssr: true }) // curl http://localhost:3000 → <article>Повний текст поста</article>

2. useFetch у SSG для зовнішніх API, яких нема під час збірки.

typescript
// Неправильно: збірка впаде, бо API недоступне в момент nuxi generate const { data } = await useFetch('/api/live-prices') // Правильно: виконуй тільки на клієнті const { data } = await useFetch('/api/live-prices', { server: false })

3. Клієнт-only компоненти без <ClientOnly> на SSR-роутах.

Компонент, який звертається до window або document, зламає серверний рендеринг. Огорни його:

vue
<ClientOnly> <HeavyChartComponent /> </ClientOnly>

4. nuxi build замість nuxi generate для SSG-деплою.

nuxi build створює SSR-сервер на Node.js. nuxi generate виводить статичні HTML-файли для CDN. Неправильна команда - неправильний тип виводу і зламаний деплой.

5. Очікування що SSG обробить невідомі динамічні параметри.

Якщо під час збірки згенеровано /users/1, /users/2, /users/3, запит до /users/99 поверне 404. SSG фіксує тільки ті сторінки, які знає при збірці. Для всіх можливих ID правильний вибір - SSR.

Де зустрічається в реальних проектах

  • nuxt.com використовує SSG через nuxi generate, задеплоєний на CDN.
  • Vercel автоматично розпізнає SSG-роути з routeRules у Hybrid-деплоях.
  • E-commerce сайти застосовують SSR для кошика і checkout, SSG для каталогу товарів.
  • Nuxt + Contentful використовують SSR-блоги з useAsyncData.
  • Адмін-панелі отримують ssr: false на роутах /admin/**.

Питання на співбесіді

Q: Чим Hybrid у Nuxt 3 відрізняється від universal mode у Nuxt 2?
A: Nuxt 2 мав єдину глобальну SSR + клієнтська гідрація модель. У Nuxt 3 через routeRules можна вибирати режим для кожного роута окремо: SSR для /blog/** і SPA-поведінку для /admin/** в одному додатку без жодного глобального перемикача.

Q: Що відбувається з useState у SSG?
A: При збірці стан ініціалізується на сервері звично. Після гідрації на клієнті useState зберігається в пам'яті браузера. Але оскільки сервера при рантаймі нема, стан не переживає перезавантаження сторінки так само, як в SSR.

Q: Як симулювати ISR у Nuxt?
A: Нативного ISR, як у Next.js, у Nuxt нема. Найближче рішення - routeRules з swr: 3600 (stale-while-revalidate): сторінка подається з кешу миттєво, а в фоні відбувається ревалідація. Для повноцінної схеми з вебхук-перезбірками потрібно комбінувати prerender: true з CI-тригером.

Q: Як дебажити повільний SSR time-to-interactive?
A: Профілюй Nitro через dev tools, використовуй useLazyFetch для некритичних даних щоб перший HTML прийшов швидше, і огортай важкі сторонні компоненти у <ClientOnly> щоб не блокувати серверний рендеринг.

Q: Що в Hybrid-режимі визначає чи використовує роут SSR або SPA? Відстеж повну послідовність.
A: Правила routeRules у nuxt.config.ts. Запит потрапляє до Nitro, збігається з патерном шляху, і якщо правило ssr: false - Nitro повертає клієнтську HTML-оболонку. Якщо ssr: true - Vue рендериться через @vue/server-renderer, useAsyncData виконується в Node, JSON-пейлоад вбудовується у <script>, повна HTML-відповідь стримується клієнту.

Приклади

SSR-сторінка блогу з серверним завантаженням даних

vue
<!-- pages/blog/[slug].vue --> <template> <article> <h1>{{ post.title }}</h1> <div v-html="post.body" /> </article> </template> <script setup> const route = useRoute() // useAsyncData виконується на сервері - краулери бачать повний контент const { data: post } = await useAsyncData('post', () => $fetch(`/api/posts/${route.params.slug}`) ) </script>

Сервер виконує $fetch до /api/posts/[slug] ще до відправки HTML клієнту. Якщо переглянути код сторінки, заголовок і тіло статті вже в розмітці - JavaScript їх не вставляє пізніше.

Hybrid-додаток: блог і адмін-панель

typescript
// nuxt.config.ts export default defineNuxtConfig({ routeRules: { '/blog/**': { ssr: true }, // Повний SSR: SEO + свіжі дані '/dashboard/**': { ssr: false }, // SPA: краулери не потрібні, багата інтерактивність '/': { prerender: true } // SSG головна: максимально швидкий TTFB } })
vue
<!-- pages/dashboard/analytics.vue - виконується тільки на клієнті --> <script setup> const { data: stats } = await useFetch('/api/analytics', { server: false }) </script>

Три різних поведінки в одному Nuxt-додатку. Головна сторінка - статичний файл на CDN. Пости блогу - SSR з Node-сервера. Дашборд - SPA-оболонка, яка завантажує свої дані після монтування.

E-commerce з ISR-подібним кешуванням

typescript
// nuxt.config.ts - реальне налаштування e-commerce export default defineNuxtConfig({ routeRules: { '/': { prerender: true }, // Статична головна сторінка '/products': { swr: 600 }, // Каталог: кеш 10 хв, ревалідація у фоні '/products/**': { swr: 3600 }, // Сторінки товарів: кеш 1 година '/cart': { ssr: false }, // SPA кошик: персональний, SEO не потрібен '/checkout': { ssr: true }, // SSR checkout: безпека + персоналізація '/admin/**': { ssr: false } // SPA адмін-панель } })

swr (stale-while-revalidate) відразу віддає закешований контент і запускає ревалідацію у фоні після закінчення кеш-вікна. Сторінки товарів завантажуються швидко з кешу і при цьому залишаються достатньо актуальними. Це максимально близько до ISR у Next.js без зовнішніх тригерів перезбірки.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?