Pular para o conteúdoHypio
← Voltar para o blog

Por que o acesso de cada rota é um dado, e não código

2 min de leitura

Todo app multi-tenant chega no mesmo ponto: alguém cria uma página nova e esquece de chamar o guard. O detalhe cruel é que isso não produz sintoma nenhum. A página abre, os dados carregam, o teste manual passa — porque quem testou estava logado como dono da organização.

O bug só aparece quando a pessoa errada abre a URL certa. Normalmente meses depois, e normalmente pelo suporte.

A decisão vira dado

Em vez de espalhar a checagem por dezenas de arquivos, este template concentra a decisão em um mapa:

export const ROUTE_ACCESS = {
  "/precos": PUBLIC,
  "/painel": ANY_MEMBER,
  "/configuracoes/faturamento": OWNER_ONLY,
} as const satisfies Record<string, Access>;

Não existe default. Uma rota sem entrada no mapa não "cai no seguro" — ela impede o build.

As quatro travas

  1. Um script no prebuild varre a árvore de rotas e falha nas duas direções: rota sem

decisão, e decisão sem rota.

  1. Um teste roda a mesma varredura, para quem executa a suíte sem buildar.
  2. Um teste de matriz executa cada par (papel × rota) e congela o resultado inteiro.

Afrouxar uma rota sem querer quebra o teste.

  1. Um teste de navegação reconcilia o menu com o mapa.
Item de menu para rota negada é armadilha de UX. Rota permitida fora do menu é funcionalidade invisível. As duas divergências nascem da mesma causa: duas listas escritas à mão que precisavam concordar.

O que isso custa

Uma linha por rota, escrita no mesmo commit da página. É pouco, e é o ponto: o custo de lembrar precisa ser menor que o custo de descobrir.

Quer ver o mapa inteiro? Ele vive em apps/web/lib/route-access.ts, e o comentário no topo dele explica cada uma das travas.