Nuxt 3: SSR i generowanie statyczne, kompletny przewodnik
Opanowanie SSR i generowania statycznego w Nuxt 3. Od useFetch po route rules: jak optymalizować wydajność aplikacji Vue.js.

Nuxt 3 zmienia sposób tworzenia aplikacji Vue.js, oferując wiele trybów renderowania dostosowanych do różnych przypadków użycia. Od Server-Side Rendering (SSR) przez generowanie statyczne aż po renderowanie hybrydowe framework zapewnia wyjątkową elastyczność w optymalizacji wydajności i SEO.
Ten tutorial zakłada podstawową znajomość Vue 3 i Composition API. Znajomość koncepcji renderowania po stronie serwera jest pomocna, lecz nie wymagana, ponieważ podstawy są wyjaśniane w trakcie przewodnika.
Zrozumienie trybów renderowania w Nuxt 3
Zanim zacznie się pisać kod, warto zrozumieć różnice między dostępnymi trybami renderowania. Każdy tryb odpowiada na konkretne potrzeby związane z wydajnością, SEO i doświadczeniem użytkownika.
SSR (Server-Side Rendering) generuje HTML na serwerze przy każdym żądaniu. Generowanie statyczne (SSG) wstępnie generuje wszystkie strony w czasie buildu. Tryb hybrydowy pozwala łączyć te podejścia strona po stronie.
// Konfiguracja różnych trybów renderowania
export default defineNuxtConfig({
// SSR włączone domyślnie (zalecane dla SEO)
ssr: true,
// Generowanie statyczne: pre-renderuje wszystkie strony
// Użyj 'npm run generate' do zbudowania
// target: 'static', // Składnia Nuxt 2
// Tryb hybrydowy: konfigurowalny per trasa
routeRules: {
// Strona główna: pre-renderowana i cache'owana
'/': { prerender: true },
// Blog: generowanie statyczne
'/blog/**': { prerender: true },
// Dashboard: renderowanie tylko po stronie klienta
'/dashboard/**': { ssr: false },
// API: bez pre-renderowania
'/api/**': { prerender: false }
}
})Ta konfiguracja pokazuje moc trybu hybrydowego: każda sekcja aplikacji korzysta z trybu renderowania najlepiej dopasowanego do jej potrzeb.
Pobieranie danych z useFetch i useAsyncData
Nuxt 3 udostępnia dwa główne composable do izomorficznego pobierania danych. Działają one zarówno po stronie serwera, jak i klienta, z automatycznym zarządzaniem hydracją.
useFetch to wrapper wokół useAsyncData, który upraszcza wywołania HTTP. useAsyncData daje większą kontrolę w zaawansowanych scenariuszach.
<script setup lang="ts">
// pages/blog/[slug].vue
// Strona szczegółów artykułu z useFetch
// Pobranie parametru trasy
const route = useRoute()
// useFetch: automatyczne pobieranie danych
// Dane są pobierane na serwerze, a następnie hydratowane na kliencie
const { data: article, pending, error } = await useFetch(
`/api/articles/${route.params.slug}`,
{
// Unikalny klucz dla cache i deduplikacji
key: `article-${route.params.slug}`,
// Transformacja danych w razie potrzeby
transform: (response) => response.data,
// Opcje cache
getCachedData: (key) => {
// Sprawdzenie, czy dane są w cache
const nuxtApp = useNuxtApp()
return nuxtApp.payload.data[key]
}
}
)
// Obsługa błędów z nawigacją
if (error.value) {
throw createError({
statusCode: 404,
message: 'Artykuł nie znaleziony'
})
}
</script>
<template>
<div>
<div v-if="pending" class="loading">
Ładowanie artykułu...
</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
</article>
</div>
</template>W przypadkach wymagających większej kontroli useAsyncData pozwala wykonać dowolną funkcję asynchroniczną.
<script setup lang="ts">
// pages/products/index.vue
// Lista produktów z useAsyncData i filtrami
const route = useRoute()
// useAsyncData: pełna kontrola nad logiką pobierania
const { data: products, refresh } = await useAsyncData(
'products-list',
async () => {
// Pobranie z wielu źródeł w razie potrzeby
const [productsResponse, categoriesResponse] = await Promise.all([
$fetch('/api/products', {
query: {
category: route.query.category,
sort: route.query.sort || 'date'
}
}),
$fetch('/api/categories')
])
// Łączenie i transformacja danych
return {
products: productsResponse.data,
categories: categoriesResponse.data,
total: productsResponse.meta.total
}
},
{
// Odświeżanie przy zmianie query params
watch: [() => route.query]
}
)
// Funkcja ręcznego odświeżania
const updateFilters = async (newCategory: string) => {
await navigateTo({
query: { ...route.query, category: newCategory }
})
}
</script>Te composable zapobiegają podwójnemu pobieraniu: dane uzyskane na serwerze są serializowane w payloadzie HTML i ponownie wykorzystywane podczas hydracji po stronie klienta.
Personalizacja SSR za pomocą server hooks
SSR w Nuxt 3 można dostosować za pomocą server hooks. Hooki te pozwalają ingerować w różne etapy cyklu renderowania, aby modyfikować domyślne zachowanie.
// Plugin serwera do dostosowywania renderowania SSR
export default defineNitroPlugin((nitroApp) => {
// Hook wykonywany przed renderowaniem każdej strony
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Wstrzyknięcie skryptów lub metadanych
html.head.push(`
<script>
// Analityka lub konfiguracja globalna
window.__APP_CONFIG__ = {
environment: '${process.env.NODE_ENV}',
apiUrl: '${process.env.API_URL}'
}
</script>
`)
})
// Hook do zarządzania cache renderowania
nitroApp.hooks.hook('render:response', (response, { event }) => {
// Dodanie niestandardowych nagłówków cache
const path = event.path
if (path.startsWith('/blog/')) {
// Długi cache dla artykułów blogowych
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// Brak cache dla API
response.headers['Cache-Control'] = 'no-store'
}
})
})Hook render:response doskonale nadaje się do wdrażania strategii cache HTTP. Połączenie SSR z CDN, który respektuje nagłówki Cache-Control, pozwala serwować pre-renderowane strony zachowując możliwość ich unieważnienia.
Generowanie statyczne z nuxt generate
Generowanie statyczne buduje wszystkie strony z wyprzedzeniem w czasie buildu. Takie podejście jest idealne dla witryn ze stabilną treścią, takich jak blogi, dokumentacja czy strony marketingowe.
W przypadku tras dynamicznych Nuxt musi znać wszystkie URL-e do wygenerowania. Hook prerender:routes pozwala definiować te trasy programowo.
// Pełna konfiguracja generowania statycznego
export default defineNuxtConfig({
// Włączenie generowania statycznego
nitro: {
prerender: {
// Włączenie automatycznego crawlowania linków
crawlLinks: true,
// Trasy zawsze do uwzględnienia
routes: ['/', '/about', '/contact'],
// Ignorowanie wybranych tras
ignore: ['/admin', '/api']
}
},
hooks: {
// Hook do generowania tras dynamicznych
async 'prerender:routes'(ctx) {
// Pobranie artykułów z API lub bazy
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// Dodanie tras artykułów
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// Pobranie kategorii
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})W projektach z dużą liczbą stron automatyczny crawler może okazać się niewystarczający. Oto bardziej solidne podejście z osobnym plikiem konfiguracyjnym.
// Narzędzie do generowania listy tras dynamicznych
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise<string[]> {
const routes: string[] = []
// Artykuły blogowe
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}`)
}
// Strony produktów
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// Strony tagów
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}Gotowy na rozmowy o Vue.js / Nuxt.js?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Renderowanie hybrydowe z routeRules
Renderowanie hybrydowe jest sztandarową funkcją Nuxt 3. Pozwala definiować różne reguły renderowania dla każdej trasy, łącząc to, co najlepsze z SSR i SSG.
// Zaawansowana konfiguracja renderowania hybrydowego
export default defineNuxtConfig({
routeRules: {
// Strony marketingowe: pre-renderowane i długo cache'owane
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// Blog: ISR (Incremental Static Regeneration)
// Rewalidacja co godzinę
'/blog/**': {
isr: 3600,
prerender: true
},
// Dokumentacja: cache CDN z rewalidacją
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// E-commerce: SSR z krótkim cache
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// Koszyk i checkout: tylko po stronie klienta
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// Dashboard: tryb SPA
'/dashboard/**': {
ssr: false,
// Wyłączenie pre-renderowania
prerender: false
},
// Trasy API: brak cache domyślnie
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})Ta konfiguracja ilustruje typową architekturę współczesnej aplikacji: strony publiczne są optymalizowane pod SEO za pomocą SSG, podczas gdy interaktywne sekcje używają renderowania po stronie klienta.
Optymalizacja wydajności z cache danych
Poza cache stron Nuxt 3 pozwala cache'ować pobrane dane. Strategia ta zmniejsza obciążenie API i poprawia czasy odpowiedzi.
// Endpoint API z cache danych
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'Brak slugu'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: 'Artykuł nie znaleziony'
})
}
return article
},
{
// Klucz cache oparty na slugu
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// Czas życia cache: 1 godzina
maxAge: 3600,
// Stale-while-revalidate: serwowanie nieaktualnego cache podczas aktualizacji
staleMaxAge: 7200,
// Unieważnianie oparte na tagach
tags: ['articles']
}
)Aby unieważnić cache przy zmianie treści, Nuxt udostępnia system tagów.
// Aktualizacja artykułu z unieważnieniem cache
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// Aktualizacja artykułu
const article = await updateArticle(slug, body)
// Unieważnienie cache dla tego artykułu
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// Lub unieważnianie po tagach (wszystkie artykuły)
// await useStorage('cache').clear('articles')
return article
})W produkcji z wieloma instancjami cache w pamięci jest niewystarczający. Zaleca się skonfigurowanie Redisa lub innego systemu rozproszonego za pośrednictwem konfiguracji Nitro, aby zapewnić spójność między instancjami.
Zarządzanie SEO i metadanymi
SSR umożliwia optymalizację SEO poprzez generowanie metadanych po stronie serwera. Nuxt 3 oferuje kilka podejść do dynamicznego zarządzania meta tagami.
<script setup lang="ts">
// pages/blog/[slug].vue
// Strona blogowa ze zoptymalizowanym SEO
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
// Dynamiczna konfiguracja SEO oparta na artykule
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
})
// Dane strukturalne dla 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>Dla stron statycznych metadane można zdefiniować bezpośrednio w komponencie.
<script setup lang="ts">
// pages/about.vue
// Strona statyczna z SEO
definePageMeta({
title: 'O nas'
})
useSeoMeta({
title: 'O SharpSkill | Przygotowanie do rozmów technicznych',
description: 'Poznaj SharpSkill, platformę do przygotowania do rozmów technicznych. Misja: pomóc programistom odnieść sukces na rozmowach technicznych.',
ogTitle: 'O SharpSkill',
ogDescription: 'Platforma do przygotowania do rozmów technicznych',
ogImage: '/images/og-about.webp'
})
</script>Wdrożenie i kwestie produkcyjne
Wybór metody wdrożenia zależy od używanego trybu renderowania. Oto główne opcje i ich konfiguracje.
// Konfiguracja dla różnych środowisk wdrożenia
export default defineNuxtConfig({
nitro: {
// Preset zależny od platformy docelowej
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // Klasyczny Node.js
// Konfiguracja dla Node.js w produkcji
preset: 'node-server',
// Kompresja odpowiedzi
compressPublicAssets: true,
// Konfiguracja magazynu cache
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// Zmienne środowiskowe runtime
runtimeConfig: {
// Sekrety (niedostępne dla klienta)
apiSecret: process.env.API_SECRET,
// Konfiguracja publiczna
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})Dla wdrożenia statycznego polecenie npm run generate tworzy folder .output/public gotowy do wdrożenia na dowolnym hoście plików statycznych.
# Generowanie statyczne
npm run generate
# Zawartość .output/public można wdrożyć na:
# - Vercel (automatyczne wykrywanie)
# - Netlify (automatyczna konfiguracja)
# - GitHub Pages
# - S3 + CloudFront
# - Dowolnym CDN lub serwerze plików statycznychZacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Podsumowanie
Nuxt 3 oferuje wyjątkową elastyczność w renderowaniu aplikacji Vue.js. Wybór między SSR, SSG i renderowaniem hybrydowym zależy od konkretnych potrzeb każdego projektu.
Kluczowe wnioski:
✅ SSR: idealne dla treści dynamicznych wymagających dobrej SEO (e-commerce, serwisy informacyjne)
✅ SSG: doskonałe dla stabilnych treści (blogi, dokumentacja, strony marketingowe)
✅ Hybrydowe: najlepsze podejście dla złożonych aplikacji o różnych potrzebach
✅ useFetch/useAsyncData: automatyczna hydracja i zarządzanie cache
✅ routeRules: precyzyjna konfiguracja zachowania każdej trasy
✅ Caching: liczne strategie optymalizacji wydajności w produkcji
Połączenie renderowania hybrydowego z dobrze przemyślaną strategią cache pozwala budować wydajne aplikacje zoptymalizowane pod SEO, zachowujące jednocześnie interaktywność Single Page Applications.
Tagi
Udostępnij
Powiązane artykuły

Kluczowe pytania rekrutacyjne Vue.js: 25 pytań na zdobycie pracy
Przygotowanie do rozmów Vue.js dzięki 25 kluczowym pytaniom. Od reaktywności po composables, opanuj najważniejsze pojęcia przed kolejną rozmową.

Vue 3 Pinia vs Vuex: Nowoczesne zarządzanie stanem i pytania rekrutacyjne 2026
Porównanie Pinia i Vuex: architektura, TypeScript, Composition API, migracja, hydratacja SSR oraz najczęstsze pytania rekrutacyjne o zarządzanie stanem Vue na rok 2026.

Vue 3 Composition API: Kompletny przewodnik po reaktywności i kompozycji
Praktyczny przewodnik po Vue 3 Composition API. Ref, reactive, computed, watch i composables — wszystko, czego potrzeba do budowania wydajnych aplikacji Vue.