Entendendo Rotas Dinâmicas no Nuxt 4 cover image

Entendendo Rotas Dinâmicas no Nuxt 4

Todo produto acaba precisando de URLs que mudam conforme os dados: um perfil em /users/42, um post em /blog/meu-primeiro-artigo ou uma seção de documentação em /docs/primeiros-passos/instalacao. No Nuxt 4, você não monta esses caminhos manualmente em um arquivo central de rotas. Você expressa a estrutura com pastas e arquivos, e o Nuxt gera as rotas automaticamente.

Esse modelo mantém o roteamento perto dos componentes que renderizam cada página e escala bem conforme o app cresce. Quando você entende as convenções de nomenclatura e como os params chegam até a página, rotas dinâmicas viram uma das partes mais previsíveis de um projeto Nuxt.

Como funcionam as rotas dinâmicas baseadas em arquivos

O Nuxt 4 usa Vue Router por baixo dos panos, mas a maioria dos apps nunca mexe em um router.ts. Cada arquivo dentro de app/pages/ (ou pages/, se você mantiver a estrutura clássica) vira uma rota. Coloque colchetes no nome do segmento para marcá-lo como dinâmico.

  • app/pages/users/[id].vue atende /users/1, /users/abc e qualquer outro segmento único.

  • app/pages/blog/[slug].vue atende /blog/hello-world.

  • app/pages/docs/[...slug].vue é uma rota catch-all que atende /docs/a, /docs/a/b e caminhos mais profundos.

  • app/pages/archive/[[year]].vue usa colchetes duplos para param opcional, então /archive e /archive/2024 caem na mesma página.

O nome dentro dos colchetes vira a chave do param. Um arquivo [id].vue expõe route.params.id. Um catch-all [...slug].vue entrega route.params.slug como array de segmentos — ideal para árvores de documentação ou paths vindos de CMS.

Lendo params em pages e layouts

Dentro de qualquer page, use o composable useRoute() para ler o estado atual da URL:

const route = useRoute()
const userId = route.params.id

Os params são reativos. Quando o usuário navega de /users/1 para /users/2 sem recarregar a página, o mesmo componente é reutilizado e route.params é atualizado. Por isso, não assuma que uma leitura única no setup basta para carregar dados — observe o param ou amarre sua lógica assíncrona a ele.

Um padrão confiável é derivar uma chave de fetch do param e passá-la para useAsyncData ou useFetch:

const route = useRoute()

const { data: user, pending, error } = await useAsyncData(
  () => `user-${route.params.id}`,
  () => $fetch(`/api/users/${route.params.id}`)
)

Quando route.params.id muda, o Nuxt refaz o fetch porque a chave mudou. Isso evita UI desatualizada quando o usuário alterna entre registros em um layout lista/detalhe.

Rotas tipadas no Nuxt 4

Uma melhoria prática do Nuxt moderno é a integração mais forte com TypeScript no roteamento. Com rotas tipadas habilitadas, os nomes de params inferidos da estrutura de arquivos aparecem no autocomplete, e rotas inválidas quebram em compile time em vez de só em runtime.

Isso faz diferença em times onde URLs fazem parte da API pública. Se você renomear [slug].vue para [postSlug].vue, o TypeScript aponta cada navigateTo e cada useRoute().params que ainda esperam slug. Trate params de rota como qualquer outra fronteira tipada do app.

Rotas dinâmicas aninhadas

Apps reais combinam segmentos estáticos e dinâmicos no mesmo path. Por exemplo:

  • app/pages/orgs/[orgId]/projects/[projectId].vue → /orgs/acme/projects/website

  • app/pages/shop/[category]/[product].vue → /shop/shoes/running-pro

Cada nível de pasta pode ser estático ou dinâmico. Os params se acumulam, então a page acima expõe orgId e projectId. Rotas aninhadas brilham quando cada segmento representa um nível de recurso: organização, time, projeto, tarefa.

Mantenha o aninhamento raso quando possível. URLs profundas são difíceis de ler e dolorosas de refatorar. Se precisar de mais de três níveis dinâmicos, considere um catch-all com uma função que resolve slugs para entidades na API.

Navegando para rotas dinâmicas

Use NuxtLink para navegação declarativa:

<NuxtLink :to="`/users/${user.id}`">Ver perfil</NuxtLink>

Para navegação programática, prefira navigateTo em vez de chamar o Vue Router direto, para manter middleware e comportamento de servidor consistentes:

await navigateTo(`/blog/${post.slug}`)

Quando você conhece os valores do param em compile time, a sintaxe com objeto fica mais clara e funciona melhor com rotas tipadas:

await navigateTo({
  name: 'blog-slug',
  params: { slug: post.slug }
})

Os nomes de rota seguem convenções do Nuxt baseadas no caminho do arquivo. Consulte a lista gerada no DevTools ou na IDE quando tiver dúvida.

Validando params antes de renderizar

Segmentos dinâmicos aceitam qualquer string que o usuário digitar — lixo, tentativas de injection, UUIDs inexistentes no banco. Não use só a página de erro da API como única proteção.

O Nuxt permite validar params com definePageMeta e uma função validate:

definePageMeta({
  validate: async (route) => {
    const id = route.params.id
    return typeof id === 'string' && /^\d+$/.test(id)
  }
})

Se a validação retornar false, o Nuxt renderiza o error.vue mais próximo com 404. Você também pode buscar metadados leves dentro de validate para confirmar que um slug existe antes de queries mais pesadas na page. Mantenha a validação rápida; ela roda a cada navegação para aquela rota.

Rotas dinâmicas e fetch de dados

SSR adiciona uma restrição: os params precisam estar disponíveis quando a page executa no servidor. Por isso await useAsyncData no top level do setup é idiomático no Nuxt. O param é lido da URL da requisição durante o SSR, os dados são buscados e o HTML chega já preenchido.

Fique atento a estes erros comuns:

  • Fetch só no onMounted — funciona na navegação client-side, mas prejudica SEO e first paint em visitas diretas.

  • Ignorar o tipo do param — catch-all devolve array; segmento único devolve string. Normalize cedo.

  • Hardcode de paths de API — centralize builders de URL para renomear params sem quebrar fetches em silêncio.

  • Pular empty states — quando um param válido não encontra registro, mostre um not-found claro em vez de página em branco.

Catch-all para CMS e conteúdo markdown

Sites orientados a conteúdo muitas vezes guardam paths no CMS em vez de um arquivo Vue por página. Uma única page catch-all carrega o path, pede o documento ao CMS e renderiza um componente dinâmico ou corpo rich text.

const route = useRoute()
const path = Array.isArray(route.params.slug)
  ? route.params.slug.join('/')
  : route.params.slug

const { data: doc } = await useAsyncData(
  () => `doc-${path}`,
  () => queryCollection('content').path(`/${path}`).first()
)

Combine isso com middleware que normaliza trailing slash e lowercase para /About e /about/ não duplicarem conteúdo nem dividirem analytics.

SEO, URLs canônicas e meta tags

Pages dinâmicas precisam de metadata explícita. Use useSeoMeta ou useHead com valores do registro carregado:

useSeoMeta({
  title: () => post.value?.title,
  description: () => post.value?.excerpt,
  ogImage: () => post.value?.coverImage
})

Defina URL canônica quando o mesmo conteúdo puder aparecer em paths diferentes. Em variantes paginadas ou filtradas do mesmo recurso, canonical evita indexação duplicada.

Testando e depurando rotas dinâmicas

Em desenvolvimento, abra o Nuxt DevTools e inspecione a aba de router para ver cada rota gerada, seus params e os componentes correspondentes. Quando uma page retorna 404 inesperado, o problema costuma ser typo no nome do arquivo ([id].vue vs [userId].vue), falta de um index irmão ou a função validate rejeitando o param.

Em testes end-to-end, navegue direto para URLs dinâmicas, não apenas clicando em listas. Visitas diretas exercitam o tratamento de params no SSR, onde bugs sutis aparecem.

Amarrando tudo

Rotas dinâmicas no Nuxt 4 são convenção em vez de configuração. Nomeie arquivos com segmentos entre colchetes, leia params via useRoute(), amarre dados assíncronos às mudanças de param, valide entrada não confiável cedo e gere links com NuxtLink ou navigateTo. Seja em CRUD administrativo, sites de marketing ou dashboards SaaS multi-tenant, os mesmos padrões se repetem: um arquivo por forma de rota, params como ponte entre URL e dados, e navegação tipada e validada mantendo a experiência consistente para o usuário e sustentável para o time.