Nuxt 3: SSR e generazione statica, la guida completa
Padroneggiare SSR e generazione statica con Nuxt 3. Da useFetch alle route rules, scoprire come ottimizzare le prestazioni delle applicazioni Vue.js.

Nuxt 3 trasforma il modo in cui vengono costruite le applicazioni Vue.js, offrendo molteplici modalità di rendering adatte a casi d'uso diversi. Dal Server-Side Rendering (SSR) alla generazione statica fino al rendering ibrido, il framework offre una flessibilità notevole per ottimizzare prestazioni e SEO.
Questo tutorial presuppone una conoscenza di base di Vue 3 e della Composition API. La familiarità con i concetti di rendering lato server è utile ma non obbligatoria, poiché i fondamentali vengono spiegati lungo tutta la guida.
Comprendere le modalità di rendering di Nuxt 3
Prima di immergersi nel codice, è essenziale comprendere le differenze tra le modalità di rendering disponibili. Ogni modalità risponde a esigenze specifiche in termini di prestazioni, SEO ed esperienza utente.
Il SSR (Server-Side Rendering) genera l'HTML sul server a ogni richiesta. La generazione statica (SSG) pre-genera tutte le pagine al momento del build. La modalità ibrida consente di combinare questi approcci pagina per pagina.
// Configurazione delle diverse modalità di rendering
export default defineNuxtConfig({
// SSR attivo per default (consigliato per la SEO)
ssr: true,
// Generazione statica: pre-renderizza tutte le pagine
// Usare 'npm run generate' per il build
// target: 'static', // Sintassi Nuxt 2
// Modalità ibrida: configurabile per rotta
routeRules: {
// Home page: pre-renderizzata e in cache
'/': { prerender: true },
// Blog: generazione statica
'/blog/**': { prerender: true },
// Dashboard: rendering solo lato client
'/dashboard/**': { ssr: false },
// API: nessun pre-rendering
'/api/**': { prerender: false }
}
})Questa configurazione mostra la potenza della modalità ibrida: ogni sezione dell'applicazione utilizza la modalità di rendering più adatta alle proprie esigenze.
Recupero dei dati con useFetch e useAsyncData
Nuxt 3 fornisce due composable principali per recuperare i dati in modo isomorfo. Questi composable funzionano sia lato server sia lato client, con una gestione automatica dell'idratazione.
useFetch è un wrapper attorno a useAsyncData che semplifica le chiamate HTTP. useAsyncData offre più controllo per casi d'uso avanzati.
<script setup lang="ts">
// pages/blog/[slug].vue
// Pagina di dettaglio dell'articolo con useFetch
// Ottenere il parametro della rotta
const route = useRoute()
// useFetch: recupero automatico dei dati
// I dati vengono recuperati lato server e idratati sul client
const { data: article, pending, error } = await useFetch(
`/api/articles/${route.params.slug}`,
{
// Chiave univoca per cache e deduplicazione
key: `article-${route.params.slug}`,
// Trasformare i dati se necessario
transform: (response) => response.data,
// Opzioni di cache
getCachedData: (key) => {
// Verificare se i dati sono in cache
const nuxtApp = useNuxtApp()
return nuxtApp.payload.data[key]
}
}
)
// Gestione degli errori con navigazione
if (error.value) {
throw createError({
statusCode: 404,
message: 'Articolo non trovato'
})
}
</script>
<template>
<div>
<div v-if="pending" class="loading">
Caricamento articolo...
</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
</article>
</div>
</template>Per i casi che richiedono più controllo, useAsyncData consente di eseguire qualsiasi funzione asincrona.
<script setup lang="ts">
// pages/products/index.vue
// Lista prodotti con useAsyncData e filtri
const route = useRoute()
// useAsyncData: controllo completo sulla logica di fetching
const { data: products, refresh } = await useAsyncData(
'products-list',
async () => {
// Recuperare da più sorgenti se necessario
const [productsResponse, categoriesResponse] = await Promise.all([
$fetch('/api/products', {
query: {
category: route.query.category,
sort: route.query.sort || 'date'
}
}),
$fetch('/api/categories')
])
// Combinare e trasformare i dati
return {
products: productsResponse.data,
categories: categoriesResponse.data,
total: productsResponse.meta.total
}
},
{
// Aggiornare quando cambiano i query params
watch: [() => route.query]
}
)
// Funzione di refresh manuale
const updateFilters = async (newCategory: string) => {
await navigateTo({
query: { ...route.query, category: newCategory }
})
}
</script>Questi composable evitano il doppio fetching: i dati recuperati sul server vengono serializzati nel payload HTML e riutilizzati durante l'idratazione lato client.
Personalizzare l'SSR con i server hooks
L'SSR di Nuxt 3 può essere personalizzato tramite i server hooks. Questi hooks consentono di intervenire in diverse fasi del ciclo di rendering per modificare il comportamento di default.
// Plugin di server per personalizzare il rendering SSR
export default defineNitroPlugin((nitroApp) => {
// Hook eseguito prima del rendering di ogni pagina
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Iniettare script o metadati
html.head.push(`
<script>
// Analytics o configurazione globale
window.__APP_CONFIG__ = {
environment: '${process.env.NODE_ENV}',
apiUrl: '${process.env.API_URL}'
}
</script>
`)
})
// Hook per la gestione della cache di rendering
nitroApp.hooks.hook('render:response', (response, { event }) => {
// Aggiungere header di cache personalizzati
const path = event.path
if (path.startsWith('/blog/')) {
// Cache lunga per gli articoli del blog
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// Nessuna cache per le API
response.headers['Cache-Control'] = 'no-store'
}
})
})L'hook render:response è ideale per implementare strategie di cache HTTP. Combinare l'SSR con un CDN che rispetti gli header Cache-Control consente di servire pagine pre-renderizzate mantenendo la possibilità di invalidarle.
Generazione statica con nuxt generate
La generazione statica costruisce in anticipo tutte le pagine al momento del build. Questo approccio è ideale per siti con contenuto stabile come blog, documentazione o siti di marketing.
Per le rotte dinamiche, Nuxt deve conoscere tutti gli URL da generare. L'hook prerender:routes consente di definire queste rotte in modo programmatico.
// Configurazione completa per la generazione statica
export default defineNuxtConfig({
// Attivare la generazione statica
nitro: {
prerender: {
// Attivare il crawling automatico dei link
crawlLinks: true,
// Rotte da includere sempre
routes: ['/', '/about', '/contact'],
// Ignorare alcune rotte
ignore: ['/admin', '/api']
}
},
hooks: {
// Hook per generare rotte dinamiche
async 'prerender:routes'(ctx) {
// Recuperare gli articoli da API o DB
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// Aggiungere le rotte degli articoli
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// Recuperare le categorie
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})Per progetti con molte pagine, il crawler automatico può risultare insufficiente. Ecco un approccio più robusto con un file di configurazione separato.
// Utility per generare la lista delle rotte dinamiche
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise<string[]> {
const routes: string[] = []
// Articoli del blog
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}`)
}
// Pagine prodotto
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// Pagine tag
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}Pronto a superare i tuoi colloqui su Vue.js / Nuxt.js?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Rendering ibrido con routeRules
Il rendering ibrido è la funzione di punta di Nuxt 3. Consente di definire regole di rendering diverse per ciascuna rotta, combinando il meglio di SSR e SSG.
// Configurazione avanzata del rendering ibrido
export default defineNuxtConfig({
routeRules: {
// Pagine marketing: pre-renderizzate e in cache a lungo termine
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// Blog: ISR (Incremental Static Regeneration)
// Revalidazione ogni ora
'/blog/**': {
isr: 3600,
prerender: true
},
// Documentazione: cache CDN con revalidazione
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// E-commerce: SSR con cache breve
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// Carrello e checkout: solo lato client
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// Dashboard: modalità SPA
'/dashboard/**': {
ssr: false,
// Disattivare il pre-rendering
prerender: false
},
// Rotte API: nessuna cache di default
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})Questa configurazione illustra un'architettura tipica delle applicazioni moderne: le pagine pubbliche sono ottimizzate per la SEO con SSG, mentre le sezioni interattive utilizzano il rendering lato client.
Ottimizzazione delle prestazioni con la cache dei dati
Oltre alla cache delle pagine, Nuxt 3 consente di mettere in cache i dati recuperati. Questa strategia riduce il carico sulle API e migliora i tempi di risposta.
// Endpoint API con cache dei dati
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'Slug mancante'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: 'Articolo non trovato'
})
}
return article
},
{
// Chiave di cache basata sullo slug
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// Durata della cache: 1 ora
maxAge: 3600,
// Stale-while-revalidate: servire la cache obsoleta durante l'aggiornamento
staleMaxAge: 7200,
// Invalidazione basata su tag
tags: ['articles']
}
)Per invalidare la cache quando i contenuti cambiano, Nuxt fornisce un sistema di tag.
// Aggiornamento articolo con invalidazione della cache
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// Aggiornare l'articolo
const article = await updateArticle(slug, body)
// Invalidare la cache per questo articolo
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// Oppure invalidazione basata sui tag (tutti gli articoli)
// await useStorage('cache').clear('articles')
return article
})In produzione con più istanze, la cache in memoria è insufficiente. Si consiglia di configurare Redis o un altro sistema distribuito tramite la configurazione di Nitro per garantire la coerenza tra le istanze.
Gestione di SEO e metadati
L'SSR consente di ottimizzare la SEO generando i metadati lato server. Nuxt 3 propone diversi approcci per gestire i meta tag in modo dinamico.
<script setup lang="ts">
// pages/blog/[slug].vue
// Pagina blog con SEO ottimizzata
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
// Configurazione SEO dinamica basata sull'articolo
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
})
// Dati strutturati per 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>Per le pagine statiche, i metadati possono essere definiti direttamente nel componente.
<script setup lang="ts">
// pages/about.vue
// Pagina statica con SEO
definePageMeta({
title: 'Chi siamo'
})
useSeoMeta({
title: 'Chi siamo SharpSkill | Preparazione ai colloqui tecnici',
description: 'Scoprire SharpSkill, la piattaforma di preparazione ai colloqui tecnici. La missione: aiutare gli sviluppatori ad avere successo nei colloqui tecnici.',
ogTitle: 'Chi siamo SharpSkill',
ogDescription: 'La piattaforma di preparazione ai colloqui tecnici',
ogImage: '/images/og-about.webp'
})
</script>Deploy e considerazioni di produzione
La scelta del deploy dipende dalla modalità di rendering utilizzata. Ecco le opzioni principali e le loro configurazioni.
// Configurazione per diversi ambienti di deploy
export default defineNuxtConfig({
nitro: {
// Preset in base alla piattaforma di destinazione
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // Node.js classico
// Configurazione per Node.js in produzione
preset: 'node-server',
// Compressione delle risposte
compressPublicAssets: true,
// Configurazione dello storage di cache
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// Variabili d'ambiente runtime
runtimeConfig: {
// Segreti (non esposti al client)
apiSecret: process.env.API_SECRET,
// Configurazione pubblica
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})Per il deploy statico, il comando npm run generate crea una cartella .output/public pronta per essere distribuita su qualsiasi host di file statici.
# Generazione statica
npm run generate
# Il contenuto di .output/public può essere distribuito su:
# - Vercel (rilevamento automatico)
# - Netlify (configurazione automatica)
# - GitHub Pages
# - S3 + CloudFront
# - Qualsiasi CDN o server di file staticiInizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Conclusione
Nuxt 3 offre una flessibilità eccezionale per il rendering delle applicazioni Vue.js. La scelta tra SSR, SSG e rendering ibrido dipende dalle esigenze specifiche di ciascun progetto.
Punti chiave:
✅ SSR: ideale per contenuti dinamici che richiedono buona SEO (e-commerce, siti di notizie)
✅ SSG: perfetto per contenuti stabili (blog, documentazione, siti di marketing)
✅ Ibrido: l'approccio migliore per applicazioni complesse con esigenze variegate
✅ useFetch/useAsyncData: idratazione automatica e gestione della cache
✅ routeRules: configurazione granulare per il comportamento di ogni rotta
✅ Caching: molteplici strategie per ottimizzare le prestazioni in produzione
Combinare il rendering ibrido con una strategia di cache ben pensata consente di costruire applicazioni performanti e ottimizzate per la SEO, mantenendo l'interattività delle Single Page Applications.
Tag
Condividi
Articoli correlati

Domande essenziali su Vue.js: 25 domande per ottenere il lavoro
Preparati ai colloqui Vue.js con queste 25 domande essenziali. Dalla reattività ai composable, padroneggia i concetti chiave per il prossimo colloquio.

Nuxt 4 nel 2026: nuova struttura di directory e migrazione da Nuxt 3
Analisi approfondita delle novità di Nuxt 4 nel 2026: struttura app/, data fetching con cache evoluto, reattività shallow, TypeScript rigoroso e guida alla migrazione graduale da Nuxt 3.

Vue 3 Pinia vs Vuex: Gestione dello Stato Moderna e Domande da Colloquio 2026
Pinia vs Vuex a confronto: design delle API, supporto TypeScript, performance, strategie di migrazione e domande frequenti sulla gestione dello stato Vue nei colloqui 2026.