Navigation Guards
O que são Navigation Guards?
Section titled “O que são Navigation Guards?”Navigation Guards são funções que interceptam navegações antes que elas aconteçam. Você pode usá-los para:
- ✅ Autenticação: Proteger rotas que exigem login
- ✅ Autorização: Verificar permissões do usuário
- ✅ Validação: Confirmar se dados necessários existem
- ✅ Redirecionamento: Enviar usuários para outras rotas
- ✅ Confirmação: Perguntar antes de sair de uma página (ex: formulário não salvo)
- ✅ Analytics: Registrar navegações
Assinatura de um Guard
Section titled “Assinatura de um Guard”type NavigationGuard = ( to: RouteMatch, // Rota de destino from: RouteMatch | null // Rota atual (null se primeira navegação)) => void | boolean | string | Promise<void | boolean | string>Valores de retorno
Section titled “Valores de retorno”| Retorno | Comportamento |
|---|---|
void ou true | Permite a navegação |
false | Bloqueia a navegação |
string | Redireciona para o path retornado |
Promise<...> | Aguarda resolução e aplica a regra acima |
Guards globais
Section titled “Guards globais”Guards globais são aplicados a todas as rotas:
import { createRouter } from '@ezbug/slash'
const router = createRouter({ routes: [ { path: '/', component: Home }, { path: '/dashboard', component: Dashboard } ], guards: [ // Guard global: verificar autenticação async (to, from) => { const isAuthenticated = await checkAuth()
if (!isAuthenticated && to.path !== '/login') { console.log('Usuário não autenticado, redirecionando...') return '/login' }
// Permitir navegação return true } ]})Guards por rota
Section titled “Guards por rota”Guards específicos aplicam-se apenas à rota onde foram definidos:
import { createRouter } from '@ezbug/slash'
const router = createRouter({ routes: [ { path: '/', component: Home }, { path: '/admin', component: AdminPanel, guards: [ // Guard específico: verificar se é admin async (to, from) => { const user = await getCurrentUser()
if (!user || !user.isAdmin) { console.log('Acesso negado: não é admin') return '/' // Redirecionar para home }
return true } ] }, { path: '/settings', component: Settings, guards: [ // Guard de confirmação (to, from) => { if (from && hasUnsavedChanges()) { const confirmed = confirm('Você tem alterações não salvas. Deseja sair?') return confirmed // true permite, false bloqueia } return true } ] } ]})Ordem de execução
Section titled “Ordem de execução”Guards são executados nesta ordem:
- Guards globais (definidos em
RouterConfig.guards) - Guards da rota (definidos em
RouteConfig.guards)
Se qualquer guard bloquear ou redirecionar, os próximos não executam.
const router = createRouter({ routes: [ { path: '/protected', component: ProtectedPage, guards: [ // 2️⃣ Executa depois do guard global (to, from) => { console.log('Guard da rota executando...') return true } ] } ], guards: [ // 1️⃣ Executa primeiro (to, from) => { console.log('Guard global executando...') return true } ]})Exemplos práticos
Section titled “Exemplos práticos”Autenticação simples
Section titled “Autenticação simples”import { createRouter } from '@ezbug/slash'
// Simulação de authlet isLoggedIn = false
function login() { isLoggedIn = true}
function logout() { isLoggedIn = false}
const router = createRouter({ routes: [ { path: '/', component: () => html`<h1>Home</h1>` }, { path: '/login', component: () => html` <div> <h1>Login</h1> <button onClick=${() => { login() router.push('/dashboard') }}> Entrar </button> </div> ` }, { path: '/dashboard', component: () => html` <div> <h1>Dashboard</h1> <button onClick=${() => { logout() router.push('/') }}> Sair </button> </div> `, guards: [ (to, from) => { if (!isLoggedIn) { return '/login' } return true } ] } ]})Verificação de permissões
Section titled “Verificação de permissões”type User = { id: number name: string role: 'admin' | 'user' | 'guest'}
let currentUser: User | null = null
const requireRole = (requiredRole: User['role']) => { return (to: RouteMatch, from: RouteMatch | null) => { if (!currentUser) { return '/login' }
if (currentUser.role !== requiredRole) { console.warn(`Acesso negado: requer role ${requiredRole}`) return '/' }
return true }}
const router = createRouter({ routes: [ { path: '/admin', component: AdminPanel, guards: [requireRole('admin')] }, { path: '/user-profile', component: UserProfile, guards: [requireRole('user')] } ]})Carregar dados antes da navegação
Section titled “Carregar dados antes da navegação”import { createRouter } from '@ezbug/slash'import { createState } from '@ezbug/slash'
const userData = createState<any>(null)
const router = createRouter({ routes: [ { path: '/profile/:id', component: (state) => { const user = userData.get()
if (!user) { return html`<p>Carregando...</p>` }
return html` <div> <h1>${user.name}</h1> <p>${user.email}</p> </div> ` }, guards: [ async (to, from) => { const userId = to.params.id
try { // Carregar dados do usuário antes de navegar const response = await fetch(`/api/users/${userId}`) const user = await response.json()
userData.set(user) return true // Permitir navegação } catch (error) { console.error('Erro ao carregar usuário:', error) return '/' // Redirecionar para home em caso de erro } } ] } ]})Analytics e tracking
Section titled “Analytics e tracking”const router = createRouter({ routes: [ { path: '/', component: Home }, { path: '/about', component: About } ], guards: [ // Guard global para analytics (to, from) => { // Enviar evento de pageview if (typeof gtag !== 'undefined') { gtag('config', 'GA_MEASUREMENT_ID', { page_path: to.path }) }
console.log('Navegação:', { from: from?.path || 'inicial', to: to.path, params: to.params, query: to.query })
// Sempre permitir navegação return true } ]})Confirmação de saída (unsaved changes)
Section titled “Confirmação de saída (unsaved changes)”import { createState } from '@ezbug/slash'
const hasUnsavedChanges = createState(false)
function Editor({ router }) { const content = createState('')
const handleInput = (e: Event) => { content.set((e.target as HTMLTextAreaElement).value) hasUnsavedChanges.set(true) }
const handleSave = () => { // Salvar conteúdo... hasUnsavedChanges.set(false) console.log('Conteúdo salvo!') }
return html` <div> <h1>Editor</h1> <textarea onInput=${handleInput}>${content}</textarea> <button onClick=${handleSave}>Salvar</button> </div> `}
const router = createRouter({ routes: [ { path: '/editor', component: Editor, guards: [ (to, from) => { // Guard de saída: confirmar se há mudanças não salvas if (from && from.path === '/editor' && hasUnsavedChanges.get()) { const confirmed = confirm( 'Você tem alterações não salvas. Deseja sair mesmo assim?' ) return confirmed } return true } ] } ]})Guards assíncronos
Section titled “Guards assíncronos”Guards podem ser assíncronos para fazer verificações que dependem de I/O:
const router = createRouter({ routes: [ { path: '/premium', component: PremiumContent, guards: [ async (to, from) => { // Verificar status de assinatura no servidor const response = await fetch('/api/subscription/status') const { isPremium } = await response.json()
if (!isPremium) { return '/upgrade' // Redirecionar para página de upgrade }
return true } ] } ]})Tratamento de erros
Section titled “Tratamento de erros”Se um guard lançar um erro, a navegação é bloqueada:
const router = createRouter({ routes: [ { path: '/dashboard', component: Dashboard, guards: [ async (to, from) => { try { const user = await fetchCurrentUser() return !!user } catch (error) { console.error('Erro no guard:', error) // Erro = navegação bloqueada // Se quiser redirecionar em caso de erro, use return '/error' return false } } ] } ]})Metadados e guards
Section titled “Metadados e guards”Você pode usar metadados de rota (meta) para controle declarativo:
const router = createRouter({ routes: [ { path: '/', component: Home, meta: { requiresAuth: false } }, { path: '/dashboard', component: Dashboard, meta: { requiresAuth: true, requiredRole: 'user' } }, { path: '/admin', component: AdminPanel, meta: { requiresAuth: true, requiredRole: 'admin' } } ], guards: [ // Guard global que lê metadata (to, from) => { const { requiresAuth, requiredRole } = to.meta
if (requiresAuth && !isLoggedIn()) { return '/login' }
if (requiredRole && !hasRole(requiredRole)) { return '/' }
return true } ]})Best practices
Section titled “Best practices”- Mantenha guards simples: Lógica complexa deve estar em serviços/helpers
- Use metadados: Prefira metadados + guard global ao invés de guards duplicados
- Trate erros: Sempre use try/catch em guards assíncronos
- Evite side effects: Guards devem ser funções puras quando possível
- Documente comportamento: Deixe claro o que cada guard faz e por que
Próximos passos
Section titled “Próximos passos”- Roteamento Básico - Conceitos fundamentais de roteamento
- Rotas Aninhadas - Estrutura hierárquica de rotas
- SSR com Router - Guards no servidor