/ Fundamentos
Arquitetura das rotas
App Router, grupos, segmentos dinâmicos, competições e acesso.
6 min de leitura Editar no GitHub
O RoboStage usa o App Router do Next.js. Cada pasta dentro de app/ vira um segmento da URL, e arquivos com nomes reservados (page.tsx, layout.tsx, route.ts…) dizem o que aquele segmento faz. Esta página explica como as rotas estão organizadas, quem decide o acesso a cada uma e como uma rota nova deve ser criada.
Para a lista completa de URLs, veja Páginas e rotas.
Arquivos especiais
| Arquivo | Papel | Exemplos no projeto |
|---|---|---|
page.tsx |
Torna o segmento acessível como página. | 77 páginas em app/ |
layout.tsx |
Envolve as páginas filhas e persiste entre navegações. | app/layout.tsx, dashboard/layout.tsx, [competicao]/layout.tsx |
loading.tsx |
Esqueleto exibido enquanto o Server Component carrega (Suspense). | documents/[documentId]/loading.tsx, quickbrick/[id]/loading.tsx |
not-found.tsx |
Resposta para notFound(). |
app/not-found.tsx, [competicao]/not-found.tsx |
error.tsx |
Fronteira de erro do cliente. | app/error.tsx |
route.ts |
Route Handler HTTP (API). | app/api/**/route.ts |
actions.ts |
Server Actions da área (convenção do projeto, não do Next). | dashboard/kanban/actions.ts |
sitemap.ts, robots.ts |
Arquivos gerados para buscadores. | app/sitemap.ts, app/robots.ts |
Grupos de rotas
Pastas entre parênteses agrupam arquivos sem aparecer na URL. O projeto usa grupos para separar áreas com layouts e regras de acesso diferentes:
app/
├── layout.tsx Raiz: Clerk, fonte, tema, Analytics
├── (public)/ Site aberto
│ ├── page.tsx → /
│ ├── about/, news/, help/… → /about, /news, /help…
│ └── [competicao]/ → /fll, /obr…
│ ├── (fll)/ Páginas só da FLL
│ └── (obr)/ Páginas só da OBR
├── (auth)/ → /sign-in, /sign-up (componentes do Clerk)
├── (protected)/
│ ├── dashboard/ → /dashboard/* (área logada, com sidebar)
│ └── (thinklab)/dashboard/thinklab/[id]
│ → editor do ThinkLab, com layout próprio
├── onboarding/ → /onboarding
└── api/ → /api/*Nota
Repare em
(protected)/(thinklab)/dashboard/thinklab/[id]. A URL é a mesma de uma página do Dashboard (/dashboard/thinklab/<id>), mas, como o arquivo fica fora de(protected)/dashboard/, o editor não herda odashboard/layout.tsx. Ele usa a tela inteira, sem a barra lateral.
Os nomes (public) e (protected) são organizacionais: quem bloqueia o acesso é o proxy.ts, e não o grupo (veja abaixo).
Segmentos dinâmicos
| Segmento | Tipo | Onde | O que recebe |
|---|---|---|---|
[competicao] |
Dinâmico | (public)/[competicao] |
fll ou obr |
[season] |
Dinâmico | score/[season], matriz-swot/[season], tabela-de-missoes/[season] |
bioglow, unearthed, submerged, masterpiece |
[mode] |
Dinâmico | timers/[mode] |
robot-game, judging, round, custom |
[id], [boardId], [documentId], [folderId], [diagramId] |
Dinâmico | Dashboard | UUID do recurso |
[slug] |
Dinâmico | news/[slug], legal/[slug] |
Texto da URL |
[[...sign-in]] |
Catch-all opcional | (auth)/sign-in |
Subpáginas internas do Clerk |
Nas versões atuais do Next.js, params é uma Promise:
export default async function Page({
params,
}: {
params: Promise<{ competicao: string }>;
}) {
const { competicao } = await params;
// ...
}Rotas de competição
Todas as competições compartilham a mesma árvore em app/(public)/[competicao]/. Três camadas garantem que só URLs válidas respondam.
1. O layout valida a competição. [competicao]/layout.tsx consulta o catálogo COMPETICOES (utils/competitions/competicoes.ts) e chama notFound() para qualquer valor desconhecido. Assim /fllx ou /vex caem no 404.
2. A home é escolhida por mapa. [competicao]/page.tsx busca o componente da home em COMPETICOES_COMPONENTES (utils/competitions/competicoes-map.ts): FLLHome ou OBRHome.
3. Páginas exclusivas checam a competição. Os grupos (fll) e (obr) só organizam os arquivos. Como o segmento [competicao] continua valendo, cada página exclusiva faz a própria checagem:
// app/(public)/[competicao]/(fll)/begins/page.tsx
const { competicao } = await params;
if (competicao !== "fll") notFound(); // /obr/begins → 404| Página | FLL | OBR | Checagem |
|---|---|---|---|
begins, explore, docs/missions-guide, future-edition/* |
✓ | competicao !== "fll" |
|
bastidores |
✓ | competicao !== "obr" |
|
cheklist |
✓ | ✓ | isConfereAICompetition() |
docs |
✓ | ✓ | isCompeticao() |
timers/[mode] |
✓ | ✓ | Modos de TIMERS_BY_COMPETITION |
score |
✓ | ✓ | A página troca o conteúdo: temporadas da FLL ou o Pontuador da OBR |
quickbrick/* |
✓ | ✓ | Ferramentas de ferramentasPorCompeticao |
Atenção
Uma página nova dentro de
(fll)ou(obr)precisa chamarnotFound()para as outras competições. O grupo de rota sozinho não impede/obr/<página-da-fll>.
O que cada competição oferece está descrito em Competições.
Menus e temporadas
A navegação das competições vem de NAVIGATION (utils/competitions/navigation.ts), com menus "Ferramentas" e "Recursos", atalhos e temporadas da FLL. Os modos de cronômetro vêm de TIMERS_BY_COMPETITION ([competicao]/timers/config.ts). O sitemap.ts usa essas mesmas fontes.
Área logada (/dashboard)
(protected)/dashboard/layout.tsx monta a moldura do Dashboard: barra lateral, seletor de escopo (pessoal ou equipe), notificações e preferências. Cada ferramenta segue o mesmo padrão:
dashboard/kanban/
├── page.tsx Lista (Server Component: lê do banco)
├── actions.ts Server Actions: criar, renomear, excluir
└── [boardId]/
├── page.tsx Detalhe
└── actions.ts Ações do quadro (cartões, colunas…)- Leitura: a
page.tsxé um Server Component assíncrono que resolve o escopo e consulta o banco direto, sem passar por API. - Escrita: os componentes cliente chamam funções de
actions.ts("use server"), que validam, conferem permissão, gravam e chamamrevalidatePath(). - Carregamento:
loading.tsxmostra um esqueleto nas rotas mais pesadas (documentos, pastas, QuickBrick, ligas e o editor do ThinkLab).
Algumas actions compartilhadas entre ferramentas ficam em utils/stagebook/actions/ (etiquetas, marcos, notificações, equipes, preferências).
Rotas de API
| Rota | Método | Acesso | Observação |
|---|---|---|---|
/api/competitions |
GET |
Sessão | Ligas do usuário. |
/api/data/missions |
GET |
Pública | Catálogo de missões de todas as temporadas. |
/api/data/missions/[season] |
GET |
Pública | Uma temporada. Estática: force-static + generateStaticParams. |
/api/data/missions/future-edition |
GET |
Pública | Missões da Future Edition. |
/api/data/fllDocs, /api/data/obrDocs, /api/data/toolsAbout |
GET |
Pública | Documentos e textos das ferramentas. |
/api/legal/[slug] |
GET |
Pública | Termos e privacidade em Markdown. |
/api/revalidate/news |
POST |
Segredo Bearer |
Atualiza o cache do NECTA. |
/api/webhooks/clerk |
POST |
Assinatura Svix | Sincroniza usuários e equipes. |
Dica
Prefira Server Actions para mutações feitas pela própria interface. Crie um Route Handler só quando algo externo precisa chamar a URL: webhook, outro site,
fetchpúblico. Detalhes de cada endpoint em APIs.
Quem decide o acesso
O controle de acesso por rota fica no middleware proxy.ts:
- Se o caminho está em
publicRoutePatterns, a requisição segue. - Sem sessão: navegação
GETvai para/sign-in?redirect_url=…; chamadas de API ePOSTrecebem401em JSON. - Com sessão, mas onboarding pendente: redireciona para
/onboarding.
O matcher ignora _next e arquivos com extensão, e sempre inclui /api.
A autorização fina (de quem é o dado e o que cada cargo pode fazer) acontece nas Server Actions e nas páginas, pelo escopo. Veja Autenticação e permissões.
Lista única de rotas públicas
utils/competitions/routes.ts foi criado para ser a fonte única das rotas públicas:
| Export | Conteúdo |
|---|---|
SITE_PAGES |
Páginas institucionais. |
COMPETITION_PAGES |
Páginas válidas de cada competição. |
FLL_SCORE_SEASONS, FLL_QUICKBRICK_SEASONS |
Temporadas com pontuador e com ferramentas sazonais. |
publicSitemapPaths() |
Todas as URLs públicas. |
isPublicPath(pathname) |
Se um caminho dispensa login. |
O teste tests/routes.test.ts garante que toda URL dessa lista corresponde a uma page.tsx existente e que cada competição registrada fica pública.
Importante
Hoje
proxy.tseapp/sitemap.tsainda mantêm listas próprias: só os testes usamroutes.ts. Ao criar uma rota pública, atualize as três (routes.ts,publicRoutePatternsnoproxy.tse ositemap.ts) até que o middleware e o sitemap passem a importarisPublicPathepublicSitemapPaths.
Metadados e SEO
app/layout.tsxdefinemetadataBase, o título padrão (%s | RoboStage), Open Graph ealternates.canonical: "./", para que cada página seja canônica de si mesma.- As páginas exportam
metadataougenerateMetadata. As de competição geram o título a partir deparams. app/sitemap.tsjunta páginas institucionais, páginas das competições, documentos legais e notícias do NECTA. Ele é regerado a cada 5 minutos (revalidate = 300) e na hora em que o NECTA publica.
Criando uma rota
Página pública institucional
- Crie
app/(public)/<nome>/page.tsxcommetadata. - Adicione o caminho em
publicRoutePatterns(proxy.ts), emSITE_PAGES(routes.ts) e nositemap.ts. - Rode
npm test: o teste de rotas confere se a página existe.
Página de uma competição
- Crie em
app/(public)/[competicao]/(fll)/ou(obr)/e chamenotFound()para as outras competições. - Adicione em
COMPETITION_PAGESe, se fizer sentido, no menu deNAVIGATION.
Ferramenta no Dashboard
- Crie
app/(protected)/dashboard/<ferramenta>/page.tsxeactions.ts. - Nas actions, resolva o escopo e confira a permissão antes de gravar (veja Arquitetura).
- Adicione
loading.tsxse a página fizer consultas pesadas, e o link emapp/(protected)/dashboard/nav-links.tsx.
Competição nova
- Registre em
COMPETICOES(utils/competitions/competicoes.ts) e crie a home emCOMPETICOES_COMPONENTES. - Defina menus em
NAVIGATIONe páginas emCOMPETITION_PAGES. - Libere a rota no
proxy.ts. Hoje a lista é fixa (/fll(.*),/obr(.*)).
Algo errado ou faltando nesta página? Sugira uma edição.