6.9 KiB
Router — AppLayout Compartilhado
Data: 2026-03-25
Arquivo: src/router/index.js e arquivos de rota relacionados
Problema
A aplicação apresentava comportamento de "reload" ao navegar entre seções distintas (ex: de /configuracoes/agenda para /therapist/agenda/recorrencias). O efeito visual era a sidebar e a topbar piscando e sendo completamente reconstruídas — mesmo sendo o mesmo layout.
Além disso, ao clicar rapidamente para trocar de página, o Vue recarregava o componente de layout inteiro, causando lentidão perceptível.
Causa raiz
Cada área da aplicação tinha seu próprio registro de rota com component: AppLayout:
/therapist → { component: AppLayout } ← instância A
/admin → { component: AppLayout } ← instância B
/configuracoes→ { component: AppLayout } ← instância C
/account → { component: AppLayout } ← instância D
...
O Vue Router trata registros de rota distintos como componentes distintos. Ao navegar de /configuracoes para /therapist, o Vue desmontava a instância A e montava a instância B do zero — sidebar, topbar, watchers, stores, tudo reiniciava. Isso é o comportamento correto do Vue Router, mas estava sendo usado de forma incorreta para uma aplicação que tem um layout único.
Solução
Consolidar todas as áreas autenticadas sob um único registro de rota pai com component: AppLayout. O Vue Router reutiliza a instância do componente pai enquanto navega entre os filhos.
Arquitetura após a mudança
/ → AppLayout (instância única — nunca desmontada)
├── therapist/ → RouterPassthrough → página
├── admin/ → RouterPassthrough → página
├── configuracoes/ → ConfiguracoesPage → sub-página
├── account/ → RouterPassthrough → página
├── upgrade/ → RouterPassthrough → página
├── supervisor/ → RouterPassthrough → página
└── features/ → RouterPassthrough → página
Antes: AppLayout era desmontado/remontado ao trocar de seção. Depois: Apenas o conteúdo (área central) é trocado. AppLayout nunca desmonta.
Componente RouterPassthrough
Criado em src/layout/RouterPassthrough.vue:
<template><router-view /></template>
Serve como "rota de grupo com componente" para as áreas que não têm layout intermediário próprio (therapist, admin, account etc.). Ele é necessário porque o Vue Router precisa de um componente em cada nível da hierarquia para renderizar os filhos via <router-view>.
Arquivos alterados
src/router/index.js
Antes: importava cada rota e espalhava todas no nível raiz do array de rotas.
Depois: cria um único pai { path: '/', component: AppLayout } e coloca todas as rotas de app como filhas.
const routes = [
// Rotas sem layout
...publicRoutes,
...authRoutes,
// Setup wizards (fullscreen, fora do AppLayout)
...therapistStandalone,
...clinicStandalone,
// ─── AppLayout compartilhado ───────────────────────────
{
path: '/',
component: AppLayout,
children: [
therapistRoutes, // path: 'therapist'
adminRoutes, // path: 'admin'
accountRoutes, // path: 'account'
billingRoutes, // path: 'upgrade'
supervisorRoutes, // path: 'supervisor'
featuresRoutes, // path: 'features'
configuracoesRoutes, // path: 'configuracoes'
]
},
// AppLayout próprio (usuários completamente distintos)
saasRoutes,
editorRoutes,
portalRoutes,
// Catch-all (sempre por último)
...miscRoutes,
];
src/router/routes.therapist.js e routes.clinic.js
Setup wizards (tela cheia, sem layout) foram separados como export const therapistStandalone / clinicStandalone e continuam com paths absolutos (/therapist/setup, /admin/setup).
A rota principal passou de:
{ path: '/therapist', component: AppLayout, children: [...] }
Para:
{ path: 'therapist', component: RouterPassthrough, meta: {...}, children: [...] }
src/router/routes.configs.js
Removido o double-nesting desnecessário. Antes havia:
{ path: '/configuracoes', component: AppLayout,
children: [
{ path: '', component: ConfiguracoesPage, children: [...] }
]
}
Depois, ConfiguracoesPage (que já tem <router-view> próprio) assume diretamente:
{ path: 'configuracoes', component: ConfiguracoesPage,
redirect: { name: 'ConfiguracoesAgenda' },
meta: { requiresAuth: true, roles: [...] },
children: [...]
}
src/router/guards.js
/configuracoes adicionado ao isTenantArea para garantir que o tenant e entitlements são carregados corretamente ao acessar a URL diretamente:
const isTenantArea =
to.path.startsWith('/admin') ||
to.path.startsWith('/therapist') ||
to.path.startsWith('/supervisor') ||
to.path.startsWith('/configuracoes');
src/App.vue
Mesma correção no isTenantArea local, que controla o checkSetupWizard.
O que foi mantido separado (e por quê)
| Área | Motivo para manter AppLayout próprio |
|---|---|
/saas |
Usado exclusivamente por saas_admin. Nunca há navegação cruzada com áreas de tenant. |
/editor |
Role de plataforma distinto. Área separada. |
/portal |
Sessão de paciente. Usuário completamente diferente. |
Mesclar essas áreas no AppLayout compartilhado não traria ganho algum e criaria acoplamento desnecessário entre contextos de usuário distintos.
Debug removido na mesma sessão
Foram removidos resquícios de código de debug que estavam ativos em produção e impactavam todos os usuários:
router/index.js:console.trace()injetado emrouter.pusherouter.replacea cada clique de navegação (gerava stack trace completo).App.vue:debugSnapshot()disparava 3 queries Supabase (auth.getUser,profiles,rpc.my_tenants) a cada troca de rota, causando o principal sintoma de lentidão.
O sistema de suporte técnico (supportGuard + supportDebugStore) não foi afetado — é uma feature controlada que só ativa via ?support=TOKEN validado no banco.
Meta inheritance
O Vue Router 4 mescla automaticamente o meta de todos os registros de rota correspondidos (to.matched). Isso significa que os filhos do RouterPassthrough herdam o meta do pai sem nenhuma configuração adicional.
Exemplo: ao navegar para /therapist/agenda, to.meta conterá:
{
area: 'therapist', // herdado do pai 'therapist'
requiresAuth: true, // herdado do pai 'therapist'
roles: ['therapist'], // herdado do pai 'therapist'
feature: 'agenda.view' // da própria rota 'agenda'
}
Os guards continuam funcionando sem nenhuma mudança de lógica.