Nuxt 3: SSR і статична генерація, повний посібник

Опанувати SSR і статичну генерацію з Nuxt 3. Від useFetch до route rules: як оптимізувати продуктивність застосунків Vue.js.

Ілюстрація серверного рендерингу та статичної генерації з Nuxt 3 і Vue.js

Nuxt 3 змінює спосіб створення застосунків Vue.js, пропонуючи кілька режимів рендерингу, що пасують до різних сценаріїв. Від Server-Side Rendering (SSR) до статичної генерації та гібридного рендерингу — фреймворк надає виняткову гнучкість для оптимізації продуктивності та SEO.

Передумови

Цей посібник передбачає базові знання Vue 3 та Composition API. Знайомство з концепціями серверного рендерингу буде корисним, але не обов'язковим, оскільки основи пояснюються по ходу матеріалу.

Розуміння режимів рендерингу Nuxt 3

Перед тим як занурюватися в код, важливо зрозуміти відмінності між доступними режимами рендерингу. Кожен режим відповідає на конкретні потреби щодо продуктивності, SEO та користувацького досвіду.

SSR (Server-Side Rendering) генерує HTML на сервері для кожного запиту. Статична генерація (SSG) попередньо генерує всі сторінки на етапі збірки. Гібридний режим дозволяє поєднувати ці підходи посторінково.

nuxt.config.tstypescript
// Конфігурація різних режимів рендерингу
export default defineNuxtConfig({
  // SSR увімкнено за замовчуванням (рекомендовано для SEO)
  ssr: true,

  // Статична генерація: попередньо рендерить усі сторінки
  // Використовуйте 'npm run generate' для збірки
  // target: 'static', // Синтаксис Nuxt 2

  // Гібридний режим: налаштовується для кожного маршруту
  routeRules: {
    // Головна сторінка: попередньо рендериться та кешується
    '/': { prerender: true },
    // Блог: статична генерація
    '/blog/**': { prerender: true },
    // Dashboard: рендеринг лише на клієнті
    '/dashboard/**': { ssr: false },
    // API: без попереднього рендерингу
    '/api/**': { prerender: false }
  }
})

Ця конфігурація демонструє силу гібридного режиму: кожна частина застосунку використовує найбільш відповідний для своїх потреб режим рендерингу.

Отримання даних із useFetch і useAsyncData

Nuxt 3 надає два основні composables для ізоморфного отримання даних. Вони працюють як на сервері, так і на клієнті, з автоматичним керуванням гідратацією.

useFetch — це обгортка над useAsyncData, що спрощує HTTP-виклики. useAsyncData дає більше контролю в розширених сценаріях.

vue
<script setup lang="ts">
// pages/blog/[slug].vue
// Сторінка деталей статті з useFetch

// Отримання параметра маршруту
const route = useRoute()

// useFetch: автоматичне отримання даних
// Дані отримуються на сервері та гідратуються на клієнті
const { data: article, pending, error } = await useFetch(
  `/api/articles/${route.params.slug}`,
  {
    // Унікальний ключ для кешу та дедуплікації
    key: `article-${route.params.slug}`,
    // Перетворення даних за потреби
    transform: (response) => response.data,
    // Опції кешу
    getCachedData: (key) => {
      // Перевірити, чи є дані в кеші
      const nuxtApp = useNuxtApp()
      return nuxtApp.payload.data[key]
    }
  }
)

// Обробка помилок із навігацією
if (error.value) {
  throw createError({
    statusCode: 404,
    message: 'Статтю не знайдено'
  })
}
</script>

<template>
  <div>
    <div v-if="pending" class="loading">
      Завантаження статті...
    </div>
    <article v-else-if="article">
      <h1>{{ article.title }}</h1>
      <div v-html="article.content" />
    </article>
  </div>
</template>

Для випадків, що потребують більшого контролю, useAsyncData дозволяє виконати будь-яку асинхронну функцію.

vue
<script setup lang="ts">
// pages/products/index.vue
// Список продуктів з useAsyncData та фільтрами

const route = useRoute()

// useAsyncData: повний контроль над логікою отримання даних
const { data: products, refresh } = await useAsyncData(
  'products-list',
  async () => {
    // Отримання з кількох джерел за потреби
    const [productsResponse, categoriesResponse] = await Promise.all([
      $fetch('/api/products', {
        query: {
          category: route.query.category,
          sort: route.query.sort || 'date'
        }
      }),
      $fetch('/api/categories')
    ])

    // Об'єднання та перетворення даних
    return {
      products: productsResponse.data,
      categories: categoriesResponse.data,
      total: productsResponse.meta.total
    }
  },
  {
    // Оновлення при зміні query params
    watch: [() => route.query]
  }
)

// Функція ручного оновлення
const updateFilters = async (newCategory: string) => {
  await navigateTo({
    query: { ...route.query, category: newCategory }
  })
}
</script>

Ці composables запобігають подвійному отриманню даних: дані, отримані на сервері, серіалізуються в HTML-payload і повторно використовуються під час гідратації на клієнті.

Налаштування SSR за допомогою server hooks

SSR у Nuxt 3 можна налаштувати через server hooks. Ці хуки дозволяють втручатися на різних етапах циклу рендерингу, щоб змінити стандартну поведінку.

server/plugins/render-hooks.tstypescript
// Серверний плагін для кастомізації SSR-рендерингу

export default defineNitroPlugin((nitroApp) => {
  // Хук, що виконується перед рендерингом кожної сторінки
  nitroApp.hooks.hook('render:html', (html, { event }) => {
    // Інжекція скриптів або метаданих
    html.head.push(`
      <script>
        // Аналітика або глобальна конфігурація
        window.__APP_CONFIG__ = {
          environment: '${process.env.NODE_ENV}',
          apiUrl: '${process.env.API_URL}'
        }
      </script>
    `)
  })

  // Хук для керування кешем рендерингу
  nitroApp.hooks.hook('render:response', (response, { event }) => {
    // Додавання кастомних заголовків кешу
    const path = event.path

    if (path.startsWith('/blog/')) {
      // Тривалий кеш для статей блогу
      response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
    } else if (path.startsWith('/api/')) {
      // Без кешу для API
      response.headers['Cache-Control'] = 'no-store'
    }
  })
})
Продуктивність SSR

Хук render:response ідеально підходить для впровадження стратегій HTTP-кешу. Поєднання SSR із CDN, який поважає заголовки Cache-Control, дозволяє віддавати попередньо відрендерені сторінки, зберігаючи можливість їх інвалідації.

Статична генерація з nuxt generate

Статична генерація заздалегідь будує всі сторінки на етапі збірки. Цей підхід ідеальний для сайтів зі стабільним контентом — блогів, документації або маркетингових сайтів.

Для динамічних маршрутів Nuxt має знати всі URL, які треба згенерувати. Хук prerender:routes дозволяє визначати ці маршрути програмно.

nuxt.config.tstypescript
// Повна конфігурація для статичної генерації

export default defineNuxtConfig({
  // Увімкнути статичну генерацію
  nitro: {
    prerender: {
      // Увімкнути автоматичне сканування посилань
      crawlLinks: true,
      // Маршрути, які завжди потрібно включати
      routes: ['/', '/about', '/contact'],
      // Ігнорувати певні маршрути
      ignore: ['/admin', '/api']
    }
  },

  hooks: {
    // Хук для генерації динамічних маршрутів
    async 'prerender:routes'(ctx) {
      // Отримання статей з API або БД
      const articles = await fetch('https://api.example.com/articles')
        .then(res => res.json())

      // Додавання маршрутів статей
      for (const article of articles) {
        ctx.routes.add(`/blog/${article.slug}`)
      }

      // Отримання категорій
      const categories = await fetch('https://api.example.com/categories')
        .then(res => res.json())

      for (const category of categories) {
        ctx.routes.add(`/category/${category.slug}`)
      }
    }
  }
})

Для проєктів із багатьма сторінками автоматичний crawler може бути недостатнім. Ось більш надійний підхід з окремим конфігураційним файлом.

server/utils/generate-routes.tstypescript
// Утиліта для генерації списку динамічних маршрутів

import { prisma } from './prisma'

export async function getAllStaticRoutes(): Promise<string[]> {
  const routes: string[] = []

  // Статті блогу
  const articles = await prisma.article.findMany({
    where: { published: true },
    select: { slug: true, category: { select: { slug: true } } }
  })

  for (const article of articles) {
    routes.push(`/blog/${article.category.slug}/${article.slug}`)
  }

  // Сторінки продуктів
  const products = await prisma.product.findMany({
    where: { active: true },
    select: { slug: true }
  })

  for (const product of products) {
    routes.push(`/products/${product.slug}`)
  }

  // Сторінки тегів
  const tags = await prisma.tag.findMany({
    select: { slug: true }
  })

  for (const tag of tags) {
    routes.push(`/tags/${tag.slug}`)
  }

  return routes
}

Готовий до співбесід з Vue.js / Nuxt.js?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Гібридний рендеринг із routeRules

Гібридний рендеринг — флагманська функція Nuxt 3. Він дозволяє визначати різні правила рендерингу для кожного маршруту, поєднуючи переваги SSR та SSG.

nuxt.config.tstypescript
// Розширена конфігурація гібридного рендерингу

export default defineNuxtConfig({
  routeRules: {
    // Маркетингові сторінки: попередньо відрендерені та довго кешовані
    '/': { prerender: true },
    '/pricing': { prerender: true },
    '/features/**': { prerender: true },

    // Блог: ISR (Incremental Static Regeneration)
    // Ревалідація щогодини
    '/blog/**': {
      isr: 3600,
      prerender: true
    },

    // Документація: CDN-кеш з ревалідацією
    '/docs/**': {
      swr: 86400, // Stale-while-revalidate
      prerender: true
    },

    // E-commerce: SSR з коротким кешем
    '/products/**': {
      ssr: true,
      cache: {
        maxAge: 60,
        staleMaxAge: 300
      }
    },

    // Кошик і checkout: лише на клієнті
    '/cart': { ssr: false },
    '/checkout/**': { ssr: false },

    // Dashboard: режим SPA
    '/dashboard/**': {
      ssr: false,
      // Вимкнути попередній рендеринг
      prerender: false
    },

    // API маршрути: без кешу за замовчуванням
    '/api/**': {
      cors: true,
      headers: {
        'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
      }
    }
  }
})

Ця конфігурація ілюструє типову архітектуру сучасного застосунку: публічні сторінки оптимізуються для SEO за допомогою SSG, тоді як інтерактивні розділи використовують клієнтський рендеринг.

Оптимізація продуктивності з кешем даних

Окрім кешу сторінок, Nuxt 3 дозволяє кешувати отримані дані. Ця стратегія знижує навантаження на API та покращує час відповіді.

server/api/articles/[slug].get.tstypescript
// API-ендпоінт із кешем даних

import { getArticleBySlug } from '~/server/utils/articles'

export default defineCachedEventHandler(
  async (event) => {
    const slug = getRouterParam(event, 'slug')

    if (!slug) {
      throw createError({
        statusCode: 400,
        message: 'Відсутній slug'
      })
    }

    const article = await getArticleBySlug(slug)

    if (!article) {
      throw createError({
        statusCode: 404,
        message: 'Статтю не знайдено'
      })
    }

    return article
  },
  {
    // Ключ кешу на основі slug
    getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
    // Тривалість кешу: 1 година
    maxAge: 3600,
    // Stale-while-revalidate: віддавати застарілий кеш під час оновлення
    staleMaxAge: 7200,
    // Інвалідація на основі тегів
    tags: ['articles']
  }
)

Для інвалідації кешу при зміні контенту Nuxt надає систему тегів.

server/api/articles/[slug].put.tstypescript
// Оновлення статті з інвалідацією кешу

import { updateArticle } from '~/server/utils/articles'

export default defineEventHandler(async (event) => {
  const slug = getRouterParam(event, 'slug')
  const body = await readBody(event)

  // Оновлення статті
  const article = await updateArticle(slug, body)

  // Інвалідація кешу для цієї статті
  await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)

  // Або інвалідація на основі тегів (всі статті)
  // await useStorage('cache').clear('articles')

  return article
})
Розподілений кеш

У продакшені з кількома інстансами кеш у пам'яті недостатній. Рекомендується налаштувати Redis або іншу розподілену систему через конфігурацію Nitro, щоб забезпечити узгодженість між інстансами.

Керування SEO та метаданими

SSR дозволяє оптимізувати SEO, генеруючи метадані на сервері. Nuxt 3 пропонує кілька підходів до динамічного керування meta tag'ами.

vue
<script setup lang="ts">
// pages/blog/[slug].vue
// Сторінка блогу з оптимізованим SEO

const route = useRoute()

const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)

// Динамічна SEO-конфігурація на основі статті
useSeoMeta({
  title: article.value?.title,
  description: article.value?.excerpt,
  ogTitle: article.value?.title,
  ogDescription: article.value?.excerpt,
  ogImage: article.value?.coverImage,
  ogType: 'article',
  twitterCard: 'summary_large_image',
  twitterTitle: article.value?.title,
  twitterDescription: article.value?.excerpt,
  twitterImage: article.value?.coverImage
})

// Структуровані дані для Google
useHead({
  script: [
    {
      type: 'application/ld+json',
      innerHTML: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: article.value?.title,
        description: article.value?.excerpt,
        image: article.value?.coverImage,
        datePublished: article.value?.publishedAt,
        dateModified: article.value?.updatedAt,
        author: {
          '@type': 'Organization',
          name: 'SharpSkill'
        }
      })
    }
  ]
})
</script>

Для статичних сторінок метадані можна визначати безпосередньо в компоненті.

vue
<script setup lang="ts">
// pages/about.vue
// Статична сторінка з SEO

definePageMeta({
  title: 'Про нас'
})

useSeoMeta({
  title: 'Про SharpSkill | Підготовка до технічних співбесід',
  description: 'Знайомство з SharpSkill — платформою підготовки до технічних співбесід. Місія: допомагати розробникам успішно проходити технічні співбесіди.',
  ogTitle: 'Про SharpSkill',
  ogDescription: 'Платформа підготовки до технічних співбесід',
  ogImage: '/images/og-about.webp'
})
</script>

Розгортання та виробничі міркування

Вибір способу розгортання залежить від обраного режиму рендерингу. Ось основні варіанти та їхні конфігурації.

nuxt.config.tstypescript
// Конфігурація для різних середовищ розгортання

export default defineNuxtConfig({
  nitro: {
    // Preset відповідно до цільової платформи
    // preset: 'vercel', // Vercel
    // preset: 'netlify', // Netlify
    // preset: 'cloudflare-pages', // Cloudflare
    // preset: 'node-server', // Класичний Node.js

    // Конфігурація для Node.js у продакшені
    preset: 'node-server',

    // Стиснення відповідей
    compressPublicAssets: true,

    // Конфігурація сховища кешу
    storage: {
      cache: {
        driver: 'redis',
        url: process.env.REDIS_URL
      }
    }
  },

  // Змінні середовища під час виконання
  runtimeConfig: {
    // Секрети (не доступні клієнту)
    apiSecret: process.env.API_SECRET,
    // Публічна конфігурація
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
    }
  }
})

Для статичного розгортання команда npm run generate створює папку .output/public, готову до розміщення на будь-якому хостингу статичних файлів.

bash
# Статична генерація
npm run generate

# Вміст .output/public можна розгорнути на:
# - Vercel (автоматичне визначення)
# - Netlify (автоматична конфігурація)
# - GitHub Pages
# - S3 + CloudFront
# - Будь-якому CDN або сервері статичних файлів

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Висновок

Nuxt 3 пропонує виняткову гнучкість для рендерингу застосунків Vue.js. Вибір між SSR, SSG та гібридним рендерингом залежить від конкретних потреб кожного проєкту.

Ключові висновки:

SSR: ідеально для динамічного контенту, що потребує гарного SEO (e-commerce, новинні сайти)

SSG: чудово для стабільного контенту (блоги, документація, маркетингові сайти)

Гібридний: найкращий підхід для складних застосунків з різноманітними потребами

useFetch/useAsyncData: автоматична гідратація та керування кешем

routeRules: точне налаштування поведінки кожного маршруту

Кешування: множина стратегій для оптимізації продуктивності в продакшені

Поєднання гібридного рендерингу з продуманою стратегією кешування дозволяє створювати продуктивні застосунки, оптимізовані для SEO, зберігаючи інтерактивність Single Page Applications.

Теги

#nuxt 3
#vue.js
#ssr
#статична генерація
#веб-продуктивність

Поділитися

Пов'язані статті