Nuxt 3: SSR en statische generatie, de complete gids
Beheers SSR en statische generatie met Nuxt 3. Van useFetch tot route rules: leer hoe Vue.js-applicaties optimaal presteren.

Nuxt 3 verandert de manier waarop Vue.js-applicaties gebouwd worden door meerdere renderingmodi aan te bieden die geschikt zijn voor uiteenlopende use cases. Van Server-Side Rendering (SSR) tot statische generatie en hybride rendering biedt het framework opmerkelijke flexibiliteit om performance en SEO te optimaliseren.
Deze tutorial gaat uit van basiskennis van Vue 3 en de Composition API. Bekendheid met server-side rendering is handig maar niet verplicht, omdat de fundamenten in de gids worden uitgelegd.
De renderingmodi van Nuxt 3 begrijpen
Voordat men in de code duikt, is het essentieel om de verschillen tussen de beschikbare renderingmodi te begrijpen. Elke modus speelt in op specifieke behoeften qua performance, SEO en gebruikerservaring.
SSR (Server-Side Rendering) genereert de HTML op de server bij elke request. Statische generatie (SSG) pre-genereert alle pagina's tijdens de build. De hybride modus maakt het mogelijk deze benaderingen per pagina te combineren.
// Configuratie van de verschillende renderingmodi
export default defineNuxtConfig({
// SSR standaard ingeschakeld (aanbevolen voor SEO)
ssr: true,
// Statische generatie: pre-rendert alle pagina's
// Gebruik 'npm run generate' om te bouwen
// target: 'static', // Nuxt 2 syntax
// Hybride modus: per route configureerbaar
routeRules: {
// Homepagina: pre-gerenderd en gecached
'/': { prerender: true },
// Blog: statische generatie
'/blog/**': { prerender: true },
// Dashboard: alleen client-side rendering
'/dashboard/**': { ssr: false },
// API: geen pre-rendering
'/api/**': { prerender: false }
}
})Deze configuratie toont de kracht van de hybride modus: elke sectie van de applicatie gebruikt de renderingmodus die het beste past bij haar behoeften.
Data ophalen met useFetch en useAsyncData
Nuxt 3 biedt twee belangrijke composables om data isomorf op te halen. Deze composables werken zowel server- als client-side, met automatisch hydratatiebeheer.
useFetch is een wrapper rond useAsyncData die HTTP-aanroepen vereenvoudigt. useAsyncData biedt meer controle voor geavanceerde scenario's.
<script setup lang="ts">
// pages/blog/[slug].vue
// Detailpagina van een artikel met useFetch
// Routeparameter ophalen
const route = useRoute()
// useFetch: automatisch data ophalen
// Data wordt server-side opgehaald en op de client gehydrateerd
const { data: article, pending, error } = await useFetch(
`/api/articles/${route.params.slug}`,
{
// Unieke key voor caching en deduplicatie
key: `article-${route.params.slug}`,
// Data zo nodig transformeren
transform: (response) => response.data,
// Cache-opties
getCachedData: (key) => {
// Controleren of data in cache staat
const nuxtApp = useNuxtApp()
return nuxtApp.payload.data[key]
}
}
)
// Foutafhandeling met navigatie
if (error.value) {
throw createError({
statusCode: 404,
message: 'Artikel niet gevonden'
})
}
</script>
<template>
<div>
<div v-if="pending" class="loading">
Artikel laden...
</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
</article>
</div>
</template>Voor situaties die meer controle vragen, kan useAsyncData elke asynchrone functie uitvoeren.
<script setup lang="ts">
// pages/products/index.vue
// Productlijst met useAsyncData en filters
const route = useRoute()
// useAsyncData: volledige controle over de fetch-logica
const { data: products, refresh } = await useAsyncData(
'products-list',
async () => {
// Indien nodig uit meerdere bronnen ophalen
const [productsResponse, categoriesResponse] = await Promise.all([
$fetch('/api/products', {
query: {
category: route.query.category,
sort: route.query.sort || 'date'
}
}),
$fetch('/api/categories')
])
// Data combineren en transformeren
return {
products: productsResponse.data,
categories: categoriesResponse.data,
total: productsResponse.meta.total
}
},
{
// Verversen wanneer query params veranderen
watch: [() => route.query]
}
)
// Functie voor handmatige refresh
const updateFilters = async (newCategory: string) => {
await navigateTo({
query: { ...route.query, category: newCategory }
})
}
</script>Deze composables voorkomen dubbele fetches: data die op de server is opgehaald wordt geserialiseerd in de HTML-payload en hergebruikt tijdens de client-hydratatie.
SSR aanpassen met server hooks
De SSR van Nuxt 3 kan via server hooks aangepast worden. Deze hooks maken het mogelijk in te grijpen op verschillende momenten van de renderingcyclus om standaardgedrag aan te passen.
// Server-plugin om SSR-rendering aan te passen
export default defineNitroPlugin((nitroApp) => {
// Hook die voor het renderen van elke pagina draait
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Scripts of metadata injecteren
html.head.push(`
<script>
// Analytics of globale configuratie
window.__APP_CONFIG__ = {
environment: '${process.env.NODE_ENV}',
apiUrl: '${process.env.API_URL}'
}
</script>
`)
})
// Hook voor beheer van de render-cache
nitroApp.hooks.hook('render:response', (response, { event }) => {
// Aangepaste cache-headers toevoegen
const path = event.path
if (path.startsWith('/blog/')) {
// Lange cache voor blogartikelen
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// Geen cache voor API's
response.headers['Cache-Control'] = 'no-store'
}
})
})De hook render:response is ideaal om HTTP-cachingstrategieën te implementeren. SSR combineren met een CDN dat Cache-Control-headers respecteert, maakt het mogelijk pre-gerenderde pagina's te serveren met behoud van invalidatiemogelijkheden.
Statische generatie met nuxt generate
Statische generatie bouwt alle pagina's vooraf tijdens de build. Deze aanpak is ideaal voor sites met stabiele content zoals blogs, documentatie of marketingsites.
Voor dynamische routes moet Nuxt alle te genereren URL's kennen. De hook prerender:routes maakt het mogelijk deze routes programmatisch te definiëren.
// Volledige configuratie voor statische generatie
export default defineNuxtConfig({
// Statische generatie inschakelen
nitro: {
prerender: {
// Automatisch crawlen van links inschakelen
crawlLinks: true,
// Routes die altijd opgenomen moeten worden
routes: ['/', '/about', '/contact'],
// Bepaalde routes negeren
ignore: ['/admin', '/api']
}
},
hooks: {
// Hook om dynamische routes te genereren
async 'prerender:routes'(ctx) {
// Artikelen ophalen uit API of DB
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// Artikelroutes toevoegen
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// Categorieën ophalen
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})Voor projecten met veel pagina's kan de automatische crawler tekortschieten. Hier volgt een robuustere aanpak met een apart configuratiebestand.
// Hulpfunctie om de lijst met dynamische routes te genereren
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise<string[]> {
const routes: string[] = []
// Blogartikelen
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}`)
}
// Productpagina's
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// Tagpagina's
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}Klaar om je Vue.js / Nuxt.js gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Hybride rendering met routeRules
Hybride rendering is de paradepaardje-feature van Nuxt 3. Het stelt in staat verschillende renderingregels per route te definiëren en zo het beste van SSR en SSG te combineren.
// Geavanceerde configuratie voor hybride rendering
export default defineNuxtConfig({
routeRules: {
// Marketingpagina's: pre-gerenderd en lange tijd gecached
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// Blog: ISR (Incremental Static Regeneration)
// Revalidatie elk uur
'/blog/**': {
isr: 3600,
prerender: true
},
// Documentatie: CDN-cache met revalidatie
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// E-commerce: SSR met korte cache
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// Winkelwagen en checkout: alleen client-side
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// Dashboard: SPA-modus
'/dashboard/**': {
ssr: false,
// Pre-rendering uitschakelen
prerender: false
},
// API-routes: standaard geen cache
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})Deze configuratie illustreert een typische architectuur voor moderne applicaties: openbare pagina's worden voor SEO geoptimaliseerd met SSG, terwijl interactieve secties client-side rendering gebruiken.
Performanceoptimalisatie met datacaching
Naast paginacaching maakt Nuxt 3 het mogelijk opgehaalde data te cachen. Deze strategie verlaagt de belasting op API's en verbetert responstijden.
// API-endpoint met datacaching
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'Slug ontbreekt'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: 'Artikel niet gevonden'
})
}
return article
},
{
// Cachekey op basis van de slug
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// Cacheduur: 1 uur
maxAge: 3600,
// Stale-while-revalidate: oude cache serveren tijdens update
staleMaxAge: 7200,
// Tag-gebaseerde invalidatie
tags: ['articles']
}
)Om de cache te invalideren bij contentwijzigingen biedt Nuxt een tagsysteem.
// Artikel bijwerken met cache-invalidatie
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// Het artikel bijwerken
const article = await updateArticle(slug, body)
// Cache voor dit artikel invalideren
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// Of tag-gebaseerde invalidatie (alle artikelen)
// await useStorage('cache').clear('articles')
return article
})In productie met meerdere instanties is in-memory cache onvoldoende. Het wordt aangeraden Redis of een ander gedistribueerd systeem te configureren via de Nitro-configuratie om consistentie tussen instanties te garanderen.
SEO en metadata beheren
SSR maakt SEO-optimalisatie mogelijk door metadata server-side te genereren. Nuxt 3 biedt verschillende benaderingen om meta tags dynamisch te beheren.
<script setup lang="ts">
// pages/blog/[slug].vue
// Blogpagina met geoptimaliseerde SEO
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
// Dynamische SEO-configuratie op basis van het artikel
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
})
// Gestructureerde data voor 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>Voor statische pagina's kan de metadata direct in het component gedefinieerd worden.
<script setup lang="ts">
// pages/about.vue
// Statische pagina met SEO
definePageMeta({
title: 'Over ons'
})
useSeoMeta({
title: 'Over SharpSkill | Voorbereiding op technische sollicitatiegesprekken',
description: 'Maak kennis met SharpSkill, het platform voor de voorbereiding op technische sollicitatiegesprekken. De missie: ontwikkelaars helpen slagen in hun technische gesprekken.',
ogTitle: 'Over SharpSkill',
ogDescription: 'Het platform voor voorbereiding op technische sollicitatiegesprekken',
ogImage: '/images/og-about.webp'
})
</script>Deployment en productieoverwegingen
De deploymentkeuze hangt af van de gebruikte renderingmodus. Hier de belangrijkste opties en hun configuraties.
// Configuratie voor verschillende deployomgevingen
export default defineNuxtConfig({
nitro: {
// Preset afhankelijk van het doelplatform
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // Klassieke Node.js
// Configuratie voor Node.js in productie
preset: 'node-server',
// Compressie van responses
compressPublicAssets: true,
// Configuratie van de cache-storage
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// Runtime omgevingsvariabelen
runtimeConfig: {
// Geheimen (niet blootgesteld aan de client)
apiSecret: process.env.API_SECRET,
// Publieke configuratie
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})Voor statische deployment maakt het commando npm run generate een map .output/public aan die klaar is om bij elke statische bestandshost geplaatst te worden.
# Statische generatie
npm run generate
# De inhoud van .output/public kan ingezet worden op:
# - Vercel (automatische detectie)
# - Netlify (automatische configuratie)
# - GitHub Pages
# - S3 + CloudFront
# - Elke CDN of statische bestandsserverBegin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Conclusie
Nuxt 3 biedt uitzonderlijke flexibiliteit voor het renderen van Vue.js-applicaties. De keuze tussen SSR, SSG en hybride rendering hangt af van de specifieke behoeften van elk project.
Belangrijkste punten:
✅ SSR: ideaal voor dynamische content die goede SEO vereist (e-commerce, nieuwssites)
✅ SSG: perfect voor stabiele content (blogs, documentatie, marketingsites)
✅ Hybride: de beste aanpak voor complexe applicaties met uiteenlopende behoeften
✅ useFetch/useAsyncData: automatische hydratatie en cachebeheer
✅ routeRules: fijnmazige configuratie voor het gedrag van elke route
✅ Caching: meerdere strategieën om productieperformance te optimaliseren
Hybride rendering combineren met een doordachte cachestrategie maakt het mogelijk performante applicaties te bouwen die voor SEO geoptimaliseerd zijn en tegelijk de interactiviteit van Single Page Applications behouden.
Tags
Delen
Gerelateerde artikelen

Essentiële Vue.js-sollicitatievragen: 25 vragen om de baan te krijgen
Bereid Vue.js-sollicitatiegesprekken voor met deze 25 essentiële vragen. Van reactiviteit tot composables: beheers de kernconcepten voor het volgende gesprek.

Vue 3 Pinia vs Vuex: Modern State Management en Sollicitatievragen 2026
Pinia vs Vuex vergeleken: API-ontwerp, TypeScript-ondersteuning, performance, migratiestrategieën en veelgestelde Vue state management sollicitatievragen voor 2026.

Vue 3 Composition API: Volledige gids voor reactiviteit
De Vue 3 Composition API in de praktijk: ref, reactive, computed, watch en composables voor performante Vue-applicaties.