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.

Ilustração mostrando renderização no servidor e geração estática com Nuxt 3 e 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.

Pré-requisitos

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.

nuxt.config.tstypescript
// 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.

vue
<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.

vue
<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.

server/plugins/render-hooks.tstypescript
// 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'
    }
  })
})
Desempenho do SSR

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.

nuxt.config.tstypescript
// 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.

server/utils/generate-routes.tstypescript
// 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.

nuxt.config.tstypescript
// 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.

server/api/articles/[slug].get.tstypescript
// 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.

server/api/articles/[slug].put.tstypescript
// 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
})
Cache distribuído

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.

vue
<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.

vue
<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.

nuxt.config.tstypescript
// 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.

bash
# 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áticos

Comece 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

#nuxt 3
#vue.js
#ssr
#geração estática
#performance web

Compartilhar

Artigos relacionados