Capítulo 09: Roteamento SPA com Vue Router 4 (routes, router-view, guards)

Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript


🗺️ Mapa Conceitual do Ciclo de Navegação e Guards

flowchart TD
    A["Navegação Disparada (RouterLink ou router.push)"] --> B["1. router.beforeEach (Guard Global)"]
    B --> C{"Requer Autenticação / Permissão?"}
    C -->|Não Autorizado| D["Redireciona para /login (next('/login'))"]
    C -->|Permitido| E["2. beforeEnter (Guard por Rota)"]

    E --> F["3. beforeRouteEnter (No Componente de Destino)"]
    F --> G["4. router.beforeResolve (Última Barreira Global)"]
    G --> H["5. Atualização da URL no Histórico do Navegador"]
    H --> I["6. router.afterEach (Atualiza document.title / Analytics)"]
    I --> J["Renderização do Componente no <RouterView>"]

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style D fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style J fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. Arquitetura de Roteamento SPA com Vue Router 4

O Vue Router 4 é o roteador oficial para aplicações Single Page Application (SPA) construídas em Vue 3. Ele foi integralmente reescrito em TypeScript, oferecendo integração estrita com a Composition API através dos composables useRoute() (para leitura de parâmetros e query) e useRouter() (para comandos de navegação programática).

1.1 Modos de Histórico: HTML5 vs Hash Mode

  • createWebHistory() (HTML5 History): Utiliza as APIs nativas do navegador (pushState, replaceState). As URLs são limpas e semânticas (ex: https://meuapp.com/dashboard/relatorios). Exige configuração no servidor web (Nginx/Apache/Cloudflare) para reescrever todas as rotas para o arquivo index.html raiz (fallback de 404).
  • createWebHashHistory() (Hash Mode): Utiliza o fragmento de hash da URL (ex: https://meuapp.com/#/dashboard). Não requer nenhuma configuração especial no servidor web, porém prejudica a indexação em motores de busca (SEO).

1.2 Lazy-Loading de Rotas e Divisão de Código

Para evitar que a aplicação baixe um bundle JavaScript monolítico gigantesco no primeiro acesso, as rotas corporativas devem adotar Dynamic Imports:

{
  path: '/analytics',
  name: 'Analytics',
  component: () => import('../views/AnalyticsView.vue') // Chunk separado sob demanda
}

1.3 TypeScript Declaration Merging para RouteMeta

No Vue Router 4, você pode tipar metadados customizados de rota (como permissões RBAC, breadcrumbs ou títulos) estendendo a interface do módulo:

declare module 'vue-router' {
  interface RouteMeta {
    requiresAuth?: boolean;
    roles?: ('ADMIN' | 'OPERATOR')[];
    title: string;
  }
}

1.4 A Ordem Exata dos Navigation Guards

A segurança e a autorização baseiam-se na sequência canônica de interceptadores:

  1. router.beforeEach: Guard global inicial. Ideal para checagem de tokens de autenticação.
  2. beforeEnter nas rotas: Validação específica daquele endpoint.
  3. router.beforeResolve: Disparado após todos os guards de componente terem resolvido, imediatamente antes da navegação ser confirmada.
  4. router.afterEach: Disparado após a conclusão da navegação (usado para trackers como Google Analytics e atualização de <title>).

⚖️ 2. Tabela Comparativa de Estratégias de Navegação

Mecanismo Declaração Caso de Uso Primário
<RouterLink to="..."> Declarativo no template. Menus de navegação, breadcrumbs, links semânticos com active-class.
router.push(...) Imperativo via código (useRouter). Redirecionamento após submissão de formulário ou autenticação bem-sucedida.
router.replace(...) Imperativo (substitui histórico). Redirecionamento de telas de login ou 404 (não polui o botão “Voltar” do browser).
router.go(delta) Imperativo numérico. Avançar (+1) ou retroceder (-1) no histórico do navegador.

⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)

Anti-Pattern 1: Loop Infinito de Redirecionamento no beforeEach

Erro Comum:

router.beforeEach((to, from, next) => {
  if (!isAuthenticated) {
    next({ name: 'Login' }); // Se 'Login' também passar por este guard, cria loop infinito de crashes!
  }
});

Abordagem Correta: Verifique se a rota atual já é a página de login: if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }); else next();.

Anti-Pattern 2: Confundir useRoute() com useRouter()

  • useRoute(): Objeto reativo somente leitura com dados da rota ativa (params, query, path, meta).
  • useRouter(): Instância controladora da máquina de rotas com métodos (push, replace, back).

💻 4. Código Conceitual Arquitetural Comentado

// src/router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/HomeView.vue'),
    meta: { title: 'Página Inicial' }
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('../views/DashboardView.vue'),
    meta: { requiresAuth: true, title: 'Painel Geral' },
    children: [
      {
        path: 'relatorios',
        name: 'DashboardReports',
        component: () => import('../views/ReportsView.vue'),
        meta: { requiresAuth: true, title: 'Relatórios Fiscais' }
      }
    ]
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/LoginView.vue'),
    meta: { title: 'Autenticação' }
  }
];

export const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(_to, _from, savedPosition) {
    return savedPosition || { top: 0, behavior: 'smooth' };
  }
});

// Guard Global de Segurança e Atualização de Título
router.beforeEach((to, _from, next) => {
  const token = localStorage.getItem('access_token');
  const isAuthRequired = to.matched.some((record) => record.meta.requiresAuth);

  if (isAuthRequired && !token) {
    return next({ name: 'Login', query: { redirect: to.fullPath } });
  }

  next();
});

router.afterEach((to) => {
  document.title = `${to.meta.title ?? 'App'} | Portal Corporativo`;
});

🛠️ 5. Cheat-Sheet do Vue Router 4

// Leitura de parâmetros de rota no componente:
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();

// Acessando :id da URL (/usuario/:id)
const userId = route.params.id;

// Acessando query strings (?filtro=ativo)
const filtro = route.query.filtro;

// Navegação programática com substituição
router.replace({ name: 'Dashboard' });