Nuxt 3: SSR e geração estática, o guia completo
Domine o SSR e a geração estática com o Nuxt 3. De useFetch às route rules, aprenda a otimizar o desempenho das suas aplicações Vue.js.

O Nuxt 3 transforma a maneira como aplicações Vue.js são construídas, oferecendo múltiplos modos de renderização adaptados a diferentes casos de uso. Do Server-Side Rendering (SSR) à geração estática e à renderização híbrida, o framework oferece uma flexibilidade notável para otimizar desempenho e SEO.
Este tutorial assume conhecimento básico de Vue 3 e da Composition API. Familiaridade com conceitos de renderização no servidor é útil, mas não é obrigatória, já que os fundamentos são explicados ao longo do guia.
Compreender os modos de renderização do Nuxt 3
Antes de mergulhar no código, é essencial entender as diferenças entre os modos de renderização disponíveis. Cada modo atende a necessidades específicas em termos de desempenho, SEO e experiência do usuário.
O SSR (Server-Side Rendering) gera o HTML no servidor a cada requisição. A geração estática (SSG) pré-gera todas as páginas no momento do build. O modo híbrido permite combinar essas abordagens página por página.
// Configuração dos diferentes modos de renderização
export default defineNuxtConfig({
// SSR ativado por padrão (recomendado para SEO)
ssr: true,
// Geração estática: pré-renderiza todas as páginas
// Use 'npm run generate' para fazer o build
// target: 'static', // Sintaxe Nuxt 2
// Modo híbrido: configurável por rota
routeRules: {
// Página inicial: pré-renderizada e em cache
'/': { prerender: true },
// Blog: geração estática
'/blog/**': { prerender: true },
// Dashboard: renderização apenas no cliente
'/dashboard/**': { ssr: false },
// API: sem pré-renderização
'/api/**': { prerender: false }
}
})Essa configuração demonstra o poder do modo híbrido: cada seção da aplicação utiliza o modo de renderização mais adequado às suas necessidades.
Busca de dados com useFetch e useAsyncData
O Nuxt 3 oferece dois composables principais para buscar dados de forma isomórfica. Esses composables funcionam tanto no servidor quanto no cliente, com gestão automática da hidratação.
useFetch é um wrapper em torno de useAsyncData que simplifica chamadas HTTP. useAsyncData oferece mais controle para casos de uso avançados.
<script setup lang="ts">
// pages/blog/[slug].vue
// Página de detalhe do artigo com useFetch
// Obter parâmetro da rota
const route = useRoute()
// useFetch: busca automática de dados
// Os dados são buscados no servidor e hidratados no cliente
const { data: article, pending, error } = await useFetch(
`/api/articles/${route.params.slug}`,
{
// Chave única para cache e deduplicação
key: `article-${route.params.slug}`,
// Transformar dados se necessário
transform: (response) => response.data,
// Opções de cache
getCachedData: (key) => {
// Verificar se os dados estão em cache
const nuxtApp = useNuxtApp()
return nuxtApp.payload.data[key]
}
}
)
// Tratamento de erros com navegação
if (error.value) {
throw createError({
statusCode: 404,
message: 'Artigo não encontrado'
})
}
</script>
<template>
<div>
<div v-if="pending" class="loading">
Carregando artigo...
</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
</article>
</div>
</template>Para casos que exigem mais controle, useAsyncData permite executar qualquer função assíncrona.
<script setup lang="ts">
// pages/products/index.vue
// Lista de produtos com useAsyncData e filtros
const route = useRoute()
// useAsyncData: controle total sobre a lógica de fetching
const { data: products, refresh } = await useAsyncData(
'products-list',
async () => {
// Buscar de múltiplas fontes se necessário
const [productsResponse, categoriesResponse] = await Promise.all([
$fetch('/api/products', {
query: {
category: route.query.category,
sort: route.query.sort || 'date'
}
}),
$fetch('/api/categories')
])
// Combinar e transformar os dados
return {
products: productsResponse.data,
categories: categoriesResponse.data,
total: productsResponse.meta.total
}
},
{
// Atualizar quando os query params mudarem
watch: [() => route.query]
}
)
// Função de atualização manual
const updateFilters = async (newCategory: string) => {
await navigateTo({
query: { ...route.query, category: newCategory }
})
}
</script>Esses composables evitam duplo fetching: os dados obtidos no servidor são serializados no payload HTML e reutilizados durante a hidratação no cliente.
Personalizar o SSR com server hooks
O SSR do Nuxt 3 pode ser personalizado por meio de server hooks. Esses hooks permitem intervir em diferentes etapas do ciclo de renderização para modificar o comportamento padrão.
// Plugin de servidor para personalizar a renderização SSR
export default defineNitroPlugin((nitroApp) => {
// Hook executado antes da renderização de cada página
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Injetar scripts ou metadados
html.head.push(`
<script>
// Analytics ou configuração global
window.__APP_CONFIG__ = {
environment: '${process.env.NODE_ENV}',
apiUrl: '${process.env.API_URL}'
}
</script>
`)
})
// Hook para gestão do cache de renderização
nitroApp.hooks.hook('render:response', (response, { event }) => {
// Adicionar cabeçalhos de cache personalizados
const path = event.path
if (path.startsWith('/blog/')) {
// Cache longo para artigos do blog
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// Sem cache para APIs
response.headers['Cache-Control'] = 'no-store'
}
})
})O hook render:response é ideal para implementar estratégias de cache HTTP. Combinar SSR com um CDN que respeite os cabeçalhos Cache-Control permite servir páginas pré-renderizadas mantendo a capacidade de invalidá-las.
Geração estática com nuxt generate
A geração estática pré-constrói todas as páginas no momento do build. Essa abordagem é ideal para sites com conteúdo estável como blogs, documentação ou sites de marketing.
Para rotas dinâmicas, o Nuxt precisa conhecer todas as URLs a gerar. O hook prerender:routes permite definir essas rotas de forma programática.
// Configuração completa para geração estática
export default defineNuxtConfig({
// Ativar geração estática
nitro: {
prerender: {
// Ativar o crawling automático de links
crawlLinks: true,
// Rotas a sempre incluir
routes: ['/', '/about', '/contact'],
// Ignorar certas rotas
ignore: ['/admin', '/api']
}
},
hooks: {
// Hook para gerar rotas dinâmicas
async 'prerender:routes'(ctx) {
// Buscar artigos da API ou BD
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// Adicionar rotas de artigos
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// Buscar categorias
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})Para projetos com muitas páginas, o crawler automático pode ser insuficiente. Eis uma abordagem mais robusta com um arquivo de configuração separado.
// Utilitário para gerar a lista de rotas dinâmicas
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise<string[]> {
const routes: string[] = []
// Artigos do 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}`)
}
// Páginas de produtos
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// Páginas de tags
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}Pronto para mandar bem nas entrevistas de Vue.js / Nuxt.js?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Renderização híbrida com routeRules
A renderização híbrida é o recurso de destaque do Nuxt 3. Permite definir regras de renderização diferentes por rota, combinando o melhor do SSR e do SSG.
// Configuração avançada de renderização híbrida
export default defineNuxtConfig({
routeRules: {
// Páginas de marketing: pré-renderizadas e em cache de longo prazo
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// Blog: ISR (Incremental Static Regeneration)
// Revalidação a cada hora
'/blog/**': {
isr: 3600,
prerender: true
},
// Documentação: cache CDN com revalidação
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// E-commerce: SSR com cache curto
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// Carrinho e checkout: somente cliente
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// Dashboard: modo SPA
'/dashboard/**': {
ssr: false,
// Desativar pré-renderização
prerender: false
},
// Rotas API: sem cache por padrão
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})Essa configuração ilustra uma arquitetura típica de aplicação moderna: páginas públicas são otimizadas para SEO com SSG, enquanto seções interativas usam renderização no cliente.
Otimização de desempenho com cache de dados
Além do cache de páginas, o Nuxt 3 permite cachear os dados buscados. Essa estratégia reduz a carga sobre as APIs e melhora os tempos de resposta.
// Endpoint API com cache de dados
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'Slug ausente'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: 'Artigo não encontrado'
})
}
return article
},
{
// Chave de cache baseada no slug
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// Duração do cache: 1 hora
maxAge: 3600,
// Stale-while-revalidate: servir cache obsoleto durante a atualização
staleMaxAge: 7200,
// Invalidação baseada em tags
tags: ['articles']
}
)Para invalidar o cache quando o conteúdo muda, o Nuxt fornece um sistema de tags.
// Atualização de artigo com invalidação de cache
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// Atualizar o artigo
const article = await updateArticle(slug, body)
// Invalidar o cache deste artigo
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// Ou invalidação baseada em tags (todos os artigos)
// await useStorage('cache').clear('articles')
return article
})Em produção com múltiplas instâncias, o cache em memória é insuficiente. Recomenda-se configurar Redis ou outro sistema distribuído via configuração do Nitro para garantir consistência entre instâncias.
Gestão de SEO e metadados
O SSR permite otimizar o SEO ao gerar metadados no servidor. O Nuxt 3 oferece várias abordagens para gerenciar meta tags de forma dinâmica.
<script setup lang="ts">
// pages/blog/[slug].vue
// Página de blog com SEO otimizado
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
// Configuração SEO dinâmica baseada no artigo
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
})
// Dados estruturados para o 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>Para páginas estáticas, os metadados podem ser definidos diretamente no componente.
<script setup lang="ts">
// pages/about.vue
// Página estática com SEO
definePageMeta({
title: 'Sobre'
})
useSeoMeta({
title: 'Sobre o SharpSkill | Preparação para entrevistas técnicas',
description: 'Conheça o SharpSkill, a plataforma de preparação para entrevistas técnicas. Nossa missão: ajudar desenvolvedores a ter sucesso em suas entrevistas técnicas.',
ogTitle: 'Sobre o SharpSkill',
ogDescription: 'A plataforma de preparação para entrevistas técnicas',
ogImage: '/images/og-about.webp'
})
</script>Implantação e considerações de produção
A escolha da implantação depende do modo de renderização utilizado. Eis as principais opções e suas configurações.
// Configuração para diferentes ambientes de implantação
export default defineNuxtConfig({
nitro: {
// Preset conforme a plataforma alvo
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // Node.js clássico
// Configuração para Node.js em produção
preset: 'node-server',
// Compressão de respostas
compressPublicAssets: true,
// Configuração do armazenamento de cache
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// Variáveis de ambiente em runtime
runtimeConfig: {
// Segredos (não expostos ao cliente)
apiSecret: process.env.API_SECRET,
// Configuração pública
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})Para implantação estática, o comando npm run generate cria uma pasta .output/public pronta para implantar em qualquer host de arquivos estáticos.
# Geração estática
npm run generate
# O conteúdo de .output/public pode ser implantado em:
# - Vercel (detecção automática)
# - Netlify (configuração automática)
# - GitHub Pages
# - S3 + CloudFront
# - Qualquer CDN ou servidor de arquivos estáticosComece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Conclusão
O Nuxt 3 oferece flexibilidade excepcional para renderizar aplicações Vue.js. A escolha entre SSR, SSG e renderização híbrida depende das necessidades específicas de cada projeto.
Pontos-chave:
✅ SSR: ideal para conteúdo dinâmico que exige bom SEO (e-commerce, sites de notícias)
✅ SSG: perfeito para conteúdo estável (blogs, documentação, sites de marketing)
✅ Híbrido: a melhor abordagem para aplicações complexas com necessidades variadas
✅ useFetch/useAsyncData: hidratação automática e gestão de cache
✅ routeRules: configuração granular para o comportamento de cada rota
✅ Cache: múltiplas estratégias para otimizar o desempenho em produção
Combinar a renderização híbrida com uma estratégia de cache bem pensada permite construir aplicações performáticas otimizadas para SEO, mantendo a interatividade das Single Page Applications.
Tags
Compartilhar
Artigos relacionados

Perguntas essenciais de Vue.js: 25 perguntas para conquistar a vaga
Prepare-se para entrevistas de Vue.js com estas 25 perguntas essenciais. Da reatividade aos composables, domine os conceitos-chave para a próxima entrevista.

Nuxt 4 em 2026: nova estrutura de diretórios e migração do Nuxt 3
Guia completo de migração para o Nuxt 4: estrutura de diretórios app/, camada singleton de busca de dados, melhorias no TypeScript e instruções passo a passo com exemplos de código.

Vue 3 Pinia vs Vuex: Gerenciamento de Estado Moderno e Perguntas de Entrevista 2026
Comparação detalhada entre Pinia e Vuex: design de API, suporte TypeScript, Composition API, desempenho, estratégias de migração e perguntas frequentes em entrevistas Vue para 2026.