Roteamento
O padrão de rotas do front usa o App Router do Next.js para a URL real, mas a
nomeação no código segue a convenção Rails-like: helpers com sufixo
_path, prefixo de módulo e ações new_/edit_.
Isso deixa o código previsível para quem vem do Rails:
ROUTES.m_usuarios_path;
ROUTES.new_m_usuario_path;
ROUTES.edit_m_usuario_path(usuario.id);
ROUTES.a_tipo_usuarios_path;
ROUTES.new_a_tipo_usuario_path;
ROUTES.edit_a_tipo_usuario_path(tipoUsuario.id);
Por que essa disciplina
Comparado contra três projetos irmãos reais (não hipotéticos) que usam o mesmo template de backend Rails:
nextfrotas-combustivel(produto Next.js real em produção, commotorista/,empresas/,admin/) não tem helper de rota nenhum — toda navegação é string literal espalhada (router.push("/motorista/bdt? autorizacao_tipo=padrao"),router.push("/admin")repetido em vários componentes). Sem umROUTEScentral, renomear uma URL significa caçar cada ocorrência manualmente.frotas-mvc(Rails MVC mais antigo do mesmo grupo de produtos) usaresourcescom prefixo de módulo, só que a seção de relatórios cresceu sem nesting — hoje são ~90 rotas nomeadas na mão (relatorios_ordens_servico_fornecedor_pdf,relatorios_bdts_gestor_prestacao_print...) em vez de um blocomember/collectionaninhado. É o resultado de não aplicar a mesma disciplina de nomeação num canto do projeto.- HTD-Front (aqui): toda página em
app/tem um helper correspondente emROUTES, e nenhum outro arquivo do projeto usa string literal de rota — confirmado por grep, zero ocorrência derouter.push("/...")/href="/..."fora delib/routes.tse do teste dele. Esse é o resultado de tratarlib/routes.tscomo porta única, e é a régua pra manter conforme o projeto cresce (ver "Ação de membro/coleção" abaixo, pensado especificamente prarelatorios_*não repetir o sprawl dofrotas-mvc).
Onde fica
Toda rota navegável fica centralizada em lib/routes.ts. Página, sidebar,
formulário, preview de relatório e redirect devem importar ROUTES, em vez de
repetir string como "/usuarios/novo" no meio do componente.
import Link from "next/link";
import { ROUTES } from "@/lib/routes";
export function UsuariosListAction() {
return (
<Link href={ROUTES.new_m_usuario_path} className="btn btn-primary btn-sm">
Novo usuário
</Link>
);
}
Nomeação
Use a forma húngara/Rails-like como nome canônico do helper:
| Caso | Padrão | Exemplo |
|---|---|---|
| Lista | <modulo>_<recursos>_path | a_papeis_path |
| Novo | new_<modulo>_<recurso>_path | new_a_papel_path |
| Registro | <modulo>_<recurso>_path(id) | a_papel_path(id) |
| Edição | edit_<modulo>_<recurso>_path(id) | edit_a_papel_path(id) |
| Ação aninhada | <modulo>_<recurso>_<acao>_path(id) | a_papel_permissoes_path(id) |
| Relatório | relatorios_<assunto>_path | relatorios_usuarios_path |
| Preview/exportação | relatorios_<assunto>_<formato>_preview_path | relatorios_usuarios_pdf_preview_path |
a_papel_permissoes_path(id) merece nota à parte: o id que ela recebe é
de um papel, mas a URL de verdade é /acessos/permissoes/:id — o nome
descreve a relação ("as permissões deste papel"), não o formato exato
da URL. É intencional, não drift: a tela em /acessos/permissoes/:id
mostra as permissões de um papel específico, então nomear pela relação
(papel_permissoes) é mais claro no call site
(papeis-list.tsx/permissoes-papeis-list.tsx) do que nomear pela URL
literal. Difere do padrão Rails puro (que nomearia pelo path,
a_permissao_path(id)), mas seguir a URL ao pé da letra aqui esconderia
de quem lê o código que o id é de papel, não de permissão.
O prefixo indica a área do domínio:
| Prefixo | Uso |
|---|---|
a_ | Administração, RBAC, organização institucional e cadastros administrativos. |
g_ | Cadastros gerais/geográficos, como países, estados e municípios. |
m_ | Usuários/membros do sistema (caso m_usuarios). |
relatorios_ | Telas analíticas e exportações. |
m_ não é invenção deste projeto — mesmo prefixo, mesmo motivo, no
config/routes.rb real do frotas-mvc: resources :users, path: 'm_usuarios', as: :m_usuarios. Um User/users sem prefixo (a exceção
documentada no template do backend) ainda ganha m_ na camada de rota,
nos dois projetos, de forma independente — bom sinal de que é convenção
estável, não acidente.
Ação de membro/coleção com verbo
Nem toda rota é lista/novo/registro/edição. Um botão como "aprovar",
"cancelar" ou "validar" numa tela específica é uma ação de membro
(opera sobre um registro, precisa de id) ou ação de coleção (não
precisa de id, filtra ou agrega o conjunto inteiro) — o padrão ainda não
tinha isso documentado, apesar de já existir em produtos irmãos:
nextfrotas-api (member { patch :validar_voucher; patch :cancelar },
gerando validar_voucher_..._path(id)) e frotas-mvc (member { post :aprovar }).
Convenção do nome — verbo antes do módulo/recurso, no mesmo lugar que
new_/edit_ já ocupam:
| Caso | Padrão | Exemplo |
|---|---|---|
Ação de membro (com id) | <verbo>_<modulo>_<recurso>_path(id) | aprovar_o_proposta_path(id) |
Ação de coleção (sem id) | <verbo>_<modulo>_<recursos>_path | minhas_c_requisicoes_path |
O helper existente (memberPath, que já monta <base>/<id> pra
a_papel_path/edit_a_papel_path) é reaproveitável direto pra esse
caso, sem função nova — uma ação de membro é só
aprovar_o_proposta_path: (id: RouteId) => `${memberPath("/propostas", id)}/aprovar`,
— o mesmo memberPath que já sustenta a_papel_path/edit_a_papel_path
hoje, sem duplicar lógica de montagem de URL.
URL em português
O helper usa new_ e edit_ no nome porque esse é o padrão mental do Rails.
A URL pode continuar em português quando a rota já existe assim no Next:
ROUTES.new_a_papel_path; // /acessos/papeis/novo
ROUTES.edit_a_papel_path(10); // /acessos/papeis/10/editar
Ou seja: a regra de nomeação organiza o código; a URL pública respeita o idioma e a compatibilidade da aplicação.
Id ou slug
Use id quando a URL identifica um registro persistido do backend. É o caso
padrão de CRUD administrativo:
ROUTES.edit_m_usuario_path(usuario.id);
ROUTES.edit_a_tipo_usuario_path(tipoUsuario.id);
ROUTES.edit_a_papel_path(papel.id);
Use slug quando o segmento identifica uma categoria estável de rota, uma
página de documentação ou um agrupador conhecido pelo front. Em referenciais,
orgaos, estados e municipios são slugs de coleção:
ROUTES.referencial_recurso_path("orgaos");
ROUTES.new_referencial_path("orgaos");
ROUTES.edit_referencial_path("orgaos", orgao.id);
Nesse exemplo, orgaos é slug porque escolhe qual cadastro abrir. O registro
continua sendo id, porque a edição altera uma linha real da API.
Só use slug para registro quando o backend garantir unicidade e estabilidade,
como friendly_id, codigo imutável ou chave natural exposta como contrato.
Não transforme nome em slug no front para editar registro; isso quebra quando
o usuário renomeia, duplica ou muda acentuação.
Fallback seguro
Helper de registro volta para a coleção quando recebe id vazio. Esse padrão
segue o comportamento padrão de scaffold Rails, que redireciona para o índice quando não encontra o
registro.
ROUTES.edit_a_papel_path(undefined); // /acessos/papeis
ROUTES.edit_referencial_path("", 7); // /referenciais
Isso evita gerar links como /usuarios/undefined/editar. A tela ainda deve
validar permissão e existência via API, mas a montagem da URL não deve criar
caminho inválido.
Query string
Filtros de relatório e preview devem usar withQuery(), não
new URLSearchParams() espalhado em cada componente.
import { ROUTES, withQuery } from "@/lib/routes";
const href = withQuery(ROUTES.relatorios_usuarios_pdf_preview_path, {
busca,
tipo: tipoUsuarioId,
});
Valores vazios são ignorados. Listas repetem a chave:
withQuery(ROUTES.relatorios_orgaos_path, {
status: ["ativo", "pendente"],
});
Checklist
- Criou rota nova em
app/: adicionar helper emlib/routes.ts. - Linkou botão, menu ou breadcrumb: usar
ROUTES, não string literal. - Criou tela
novo: helpernew_<modulo>_<recurso>_path. - Criou tela
editar: helperedit_<modulo>_<recurso>_path(id). - Criou rota dinâmica por categoria: tratar como slug de coleção.
- Criou rota dinâmica de registro: preferir
id, salvo contrato explícito de slug no backend. - Criou filtro em link de relatório: montar com
withQuery(). - Adicionou helper compartilhado: cobrir em
lib/__tests__/routes.test.ts. - Criou ação de verbo (aprovar/cancelar/validar): nomear
<verbo>_<modulo>_<recurso>_path, montar commemberPathexistente — nunca deixarrelatorios_*(ou qualquer seção) crescer flat feito ofrotas-mvc.
Leitura de apoio
- Next.js — App Router (docs oficiais) — roteamento por sistema de arquivos.
- Next.js — Layouts and Pages — como
(auth),(admin)organizam a árvore sem mudar a URL. - OWASP — Unvalidated Redirects and Forwards — por que
redirectSeguro()bloqueia open redirect.

