Routing
O template usa React Router v7 com SSR. A definicao de rotas usa a API @react-router/dev/routes.
Route Registry
O Route Registry e o sistema centralizado de rotas configuraveis por brand. Ele separa a definicao de URLs (paths) da arvore de rotas (route tree), permitindo que brands customizem todos os paths de pagina sem tocar em componentes nem na arvore.
Arquivos
| Arquivo | Descricao |
|---|---|
app/types/routes.ts | Tipos: RoutePathMap, RouteParamsMap, StaticRouteKey, DynamicRouteKey |
app/config/routes/paths.ts | Mapa padrao de paths (overridavel por brand) |
app/utils/routes.ts | Helpers: routeHref(), routePattern(), gameHref(), sportPath(), isRouteActive(), validateRoutePaths() |
app/router/routes.ts | Arvore de rotas (consome routePattern()) |
overrides/<brand-key>/app/config/routes/paths.ts | Override de paths por brand |
Helpers — ~/utils/routes
| Funcao | Descricao | Exemplo |
|---|---|---|
routePattern(key) | Pattern para route config (sem / inicial) | routePattern('casino.play') → 'games/:provider/:game' |
routeHref(key) | URL para rota estatica | routeHref('casino') → '/games' |
routeHref(key, params) | URL para rota dinamica | routeHref('casino.play', { provider: 'pgsoft', game: 'tiger' }) → '/games/pgsoft/tiger' |
gameHref(slug) | URL de jogo a partir de slug provider/game | gameHref('pgsoft/fortune-tiger') → '/games/pgsoft/fortune-tiger' |
sportPath(subPath) | Deep-link prefixado com a base do sports | sportPath('/live') → '/sports/live' |
isRouteActive(key, pathname) | Verifica se o pathname casa a rota | isRouteActive('casino', '/games/category/slots') → true |
validateRoutePaths(paths) | Valida um RoutePathMap (build-time) | Usado no startup do server |
Como app/router/routes.ts resolve o override
app/router/routes.ts e avaliado por vite-node em config time, antes do sistema de aliases do
Vite estar ativo. Por isso ele nao importa ~/config/routes/paths: resolve o arquivo pelo resolver
compartilhado (vite-plugins/brand-resolver.mjs — o mesmo usado pelo plugin de overrides e pelo
tailwind.config.js) e faz import() dinamico do arquivo certo.
ORIGIN_DOMAIN=state77.com
→ brand-key = state77-com
→ findBrandOverride(cwd, "state77-com", "app/config/routes/paths.ts")
→ overrides/state77-com/app/config/routes/paths.ts
→ routePattern('casino') → 'casino' (sem /)
→ routeHref('casino') → '/casino'
Em runtime, os componentes importam routePaths de ~/config/routes/paths e o
brandOverridesPlugin faz a mesma substituicao — o resultado e identico.
Alem de paths.ts, o mesmo mecanismo de config-time e usado para
app/config/routes/lp-redirects.ts e app/config/routes/legacy-redirects-cross-brand.ts, porque
essas listas geram rotas e precisam existir antes do build da arvore.
Regras
- Nomes de parametros sao fixos:
:slug,:provider,:game,:id,:type,:token,:area,:filename,:cat— componentes usamuseParams()com esses nomes exatos - Brands so customizam segmentos estaticos do path
- Rotas de API nao entram no registro — sao endpoints internos do servidor
- Override deve ser completo — TypeScript da erro se faltar alguma chave
routes.ts (entry point)
O arquivo app/routes.ts na raiz do app/ e o entry point do routing:
// app/routes.ts
import type { RouteConfig } from '@react-router/dev/routes';
import { buildRoutes } from './router/routes';
export default buildRoutes() satisfies RouteConfig;
routes.ts usa import relativo ./router/routes — o alias com tilde (~/router/routes) nao
funciona aqui por limitacao do React Router.
app/router/routes.ts (arvore de rotas)
Define toda a arvore. Usa routePattern() para obter os paths do Route Registry. Forks editam este
arquivo diretamente para adicionar/remover paginas.
Este arquivo foi movido de app/config/routes.config.ts para app/router/routes.ts na refatoracao
do sistema de layout. app/config/ hoje contem apenas configuracao brand-editavel — ver
Config map.
A arvore esta organizada em:
- Redirects legados gerados a partir de config (fora do layout)
- Resource routes — manifest, robots, sitemaps (fora do layout, sem
DefaultLayout) - API routes — server-only, organizadas em
app/routes/api/<dominio>/ - Rotas fora do layout — footer embeddable, bounce de redirect,
game-error,mkt,clever - Page routes dentro de
layout("routes/_layout.tsx") - Guard de restricao —
layout("routes/restricted-guard-layout.tsx")envolvendo casino, sports, gamification, search, favoritos, recentes e profiles - User area —
layout("routes/user/_layout.tsx"), navegacao em abas - Catch-all —
route("*", "routes/$.tsx")dentro do layout
Resource Routes (fora do layout)
| Rota | Arquivo | Descricao |
|---|---|---|
manifest.json | routes/manifest[.]json.ts | PWA manifest |
robots.txt | routes/robots[.]txt.ts | Robots.txt |
sitemap.xml | routes/sitemap[.]xml.ts | Index de sitemaps |
sitemap-static.xml | routes/sitemap-static[.]xml.ts | URLs estaticas |
sitemap-games/categories.xml · sitemap-games/providers.xml · sitemap-games/:provider.xml | routes/sitemap-games.* | Catalogo de jogos. categories e providers sao slugs reservados — um provider real com esse slug nunca chegaria na rota dinamica |
sitemap/:area/:filename | routes/sitemap-content.$area.$filename.ts | Sitemap de conteudo (blog, promotions, news, faq, help, responsible-gaming) com sharding por volume |
sitemap-faq.xml · sitemap-profiles.xml | rotas dedicadas | — |
sitemap-<group>.xml (varios) | routes/sitemap-brand-group[.]xml.ts | Sitemap-groups definidos pela brand em ~/config/seo/sitemap.ts. Cada path e registrado explicitamente porque RR7 nao aceita :param depois de -; o handler e o mesmo e resolve o group pelo pathname. 404 quando a brand nao declara o group |
Sao 36 rotas sitemap* hoje — a maioria delas grupos de brand apontando para o mesmo handler.
Para a lista exata, consulte app/router/routes.ts.
Redirects legados
Tres geradores transformam config em rotas de redirect. Nenhum deles precisa de arquivo novo por path.
| Gerador | Config | Handler |
|---|---|---|
| Legacy Vue/Nuxt | app/config/routes/legacy-redirects.ts (base, compartilhada) + legacy-redirects-cross-brand.ts (overridavel — usada por brands que compartilham usuarios, como as quatro "7k") | routes/legacy-redirect.ts |
| Landing pages | app/config/routes/lp-redirects.ts | routes/lp-legacy-redirect.ts |
Conteudo legado (/<area>/:cat/:slug → /<area>/:slug) | derivado dos paths das areas de conteudo | routes/legacy-content-redirect.ts |
Ha ainda redirects pontuais: routes/casino-redirect.ts (/casino → path de cassino da brand, so
registrado quando os dois diferem — evita loop) e routes/pending-redirect.ts (/pending → 301
para /redirect). O redirect do hub live nao e uma rota: vive no loader de routes/games/live.tsx,
gateado por liveHubRedirectsToCategory (~/config/routes/live-hub-redirect, overridavel) e pelo
CASSINO_MODE.
API Routes
101 rotas api/* hoje, todas em diretorios aninhados sob app/routes/api/. A fonte de verdade
e app/router/routes.ts — a tabela abaixo mapeia dominio → diretorio, nao rota a rota.
:::danger Os paths NAO sao arquivos flat
As rotas de API vivem em routes/api/<dominio>/<nome>.ts — por exemplo
routes/api/auth/login.ts, routes/api/wallet/refresh.ts, routes/api/kyc/start.ts. Nao existe
nenhum arquivo app/routes/api.*.ts.
:::
| Dominio | Diretorio | Rotas hoje | Notas |
|---|---|---|---|
| Auth | routes/api/auth/ | 12 | login, register, register.validate-phone, validate-document, documents.validate, documents.confirm-birthdate, social.$provider, logout, profile, recovery, refresh, recheck-spa |
| Validation | routes/api/validation/ | 11 | email.{send,verify,change}, sms.{send,verify,change}, docs.submit, address.submit, full-name.submit, terms.accept, link-confirm |
| User | routes/api/user/ | 22 | Perfil, senha, telefone, limites, preferencias, historico de login, contas sociais, timeout/auto-exclusao, referrals, documentos, 2FA, zendesk-token |
| Wallet | routes/api/wallet/ | 4 | refresh, transactions, action, receipt |
| Payments | routes/api/payments/ | 8 | providers, deposit, deposit-status, withdraw, bank-list, bank-account, pix-key, coupon |
| Games | routes/api/games/ | 13 | list, search, start, vote, by-slugs, suggestions, stats-batch, statistics-dl, top-wins, top-games, category/:slug, provider/:slug, rows/:page |
| Sports | routes/api/sports/ | 8 | search, launch, anonymous-launch, betby-jwt, altenar-token, rogue-anonymous, rogue-login, rogue-proxy/* |
| Sportsbook proxy | routes/api/cactus-sportbook/$.ts | 1 | Proxy raw do iframe First (ApiClient.proxyRaw, binding-aware) |
| KYC | routes/api/kyc/ | 2 | start, status |
| Cache | routes/api/cache/ | 2 | purge e inspect. A rota antiga api/cache/games/purge nao existe mais |
| Income report | routes/api/income-report/ | 3 | available-years, generate, status/:id |
| Rewards | routes/api/rewards/ | 2 | list, redeem |
| FTD cashback | routes/api/ftd-cashback/ | 2 | verify, send-cashback |
| Favoritos | routes/api/favorites.ts | 1 | GET le, POST/DELETE fazem toggle |
| Search | routes/api/search/unified.ts | 1 | Busca unificada (games + sports) |
| Tracking | routes/api/tracking/appsflyer.ts | 1 | Proxy AppsFlyer — 503 sem APPSFLYER_API_KEY |
| Address | routes/api/address/lookup-by-postal-code.ts | 1 | CEP → endereco |
| Logs | routes/api/logs/auth-logout.ts | 1 | Telemetria de logout |
| Version | routes/api/version.ts | 1 | Payload de build (BUILD_ID) |
| Clear cache | routes/api/clear-cache.ts | 1 | Version-check + Clear-Site-Data |
| Dev | routes/api/dev/ | 4 | cache-clear, cache-policy, proxy, visitor-info |
:::caution Rotas de dev nao existem em producao
devApiRoutes so e registrado em builds de dev. Em producao as rotas nao existem e o Vite
tree-shaka os arquivos do bundle do worker inteiro. Essa e a unica camada de defesa — um gate
so em runtime seria insuficiente para uma rota que encaminha requests arbitrarios ao BFF com o
cf-worker-key.
:::
Contrato de erro das rotas de proxy
Toda rota /api/* que proxia o BFF deve usar proxyErrorResponse() e unauthorizedNoToken() de
~/utils/proxy-error.server — nunca um envelope proprio em 401/403. Detalhes e a lista de excecoes
em Services.
Rotas fora do layout (paginas)
| Rota | Arquivo | Descricao |
|---|---|---|
_/footer | routes/_._footer.tsx | Footer embeddable (iframe do First) |
redirect · redirect/* | routes/redirect.$.tsx | Bounce full-screen ("Estamos te redirecionando…"), sem header/sidebar/footer |
game-error | routes/game-error.tsx | Erro de launch de jogo. noindex,nofollow, Disallow no robots e fora de todo sitemap |
mkt · mkt/* | routes/mkt.ts | Landing de marketing (302 + Set-Cookie de tracking). Nunca cacheada |
clever | routes/clever.ts | Integracao dedicada |
dev/ftd-cashback | routes/dev.ftd-cashback.tsx | Preview de dev |
Page Routes (dentro do layout)
Renderizadas dentro de layout("routes/_layout.tsx"), que monta o DefaultLayout (shell, header,
sidebar, footer, providers).
:::info Route Registry
Os paths abaixo sao os defaults de app/config/routes/paths.ts (59 chaves hoje). Brands
customizam qualquer um deles via override. app/router/routes.ts usa routePattern(key) para
resolver o path correto.
:::
Gerais
| Chave | Path padrao | Arquivo |
|---|---|---|
home | / | routes/_index.tsx |
search | /search | routes/search.tsx |
search.casino | /search/casino | routes/search.casino.tsx |
search.sports | /search/sports | routes/search.sports.tsx |
favorites | /favorites | routes/favorites.tsx |
recents | /recents | routes/recents.tsx |
promocode | /promocode | routes/promocode.tsx |
debug | /debug | routes/debug.tsx (so em build de dev) |
Games / Casino
| Chave | Path padrao | Arquivo |
|---|---|---|
casino | /games | routes/games/_index.tsx |
casino.live | /games/live | routes/games/live.tsx |
casino.category | /games/category/:slug | routes/games/category.$slug.tsx |
casino.providers | /games/providers | routes/games/providers._index.tsx |
casino.provider | /games/providers/:slug | routes/games/providers.$slug.tsx |
casino.play | /games/:provider/:game | routes/games/$provider.$game.tsx |
Sports
O sportsbook usa nested layout (routes/sports/_layout.tsx) para manter o componente do sportsbook
montado entre sub-rotas.
| Chave | Path padrao | Arquivo |
|---|---|---|
sports | /sports | routes/sports/_index.tsx |
sports.catchAll | /sports/* | routes/sports/$.tsx |
sports.test | /sports-test | routes/sports-test.tsx |
VIP / Gamificacao
Agrupadas sob layout("routes/vip.tsx").
| Chave | Path padrao | Arquivo |
|---|---|---|
gamification | /vip | routes/vip/_index.tsx |
gamification.missions | /vip/missions | routes/vip/missions.tsx |
gamification.tournaments | /vip/tournaments | routes/vip/tournaments.tsx |
gamification.tournament | /vip/tournaments/:id | routes/vip/tournaments.$id.tsx |
gamification.store | /vip/store | routes/vip/store.tsx |
gamification.miniGames | /vip/mini-games | routes/vip/mini-games.tsx |
gamification.levels | /vip/levels | routes/vip/levels.tsx |
gamification.badges | /vip/badges | routes/vip/badges.tsx |
gamification.bonuses | /vip/bonuses | routes/vip/bonuses.tsx |
Conteudo (WordPress)
Seis areas de conteudo com a mesma forma: hub, category-page e post detail. :slug e um smart
resolver — o server decide se e categoria ou post.
| Chave | Path padrao | Arquivo |
|---|---|---|
promotions / .category / .post | /promotions · /promotions/:slug | routes/promotions.tsx · routes/promotions.$slug.tsx |
blog / .category / .post | /blog · /blog/:slug | routes/blog.tsx · routes/blog.$slug.tsx |
news / .category / .post | /news · /news/:slug | routes/news._index.tsx · routes/news.$slug.tsx |
responsibleGaming / .category / .post | /responsible-gaming · /responsible-gaming/:slug | routes/responsible-gaming._index.tsx · routes/responsible-gaming.$slug.tsx |
faq / .category / .post | /faq · /faq/:slug | routes/faq/_index.tsx · routes/faq/$slug.tsx |
faq.game | /faq/:provider/:game | routes/faq/$provider.$game.tsx |
helpCenter | /help | routes/help-center.tsx |
help.categories | /help/categories | routes/help.categories.tsx |
help.category / help.post | /help/:slug | routes/help.$slug.tsx |
Legal, profiles e landing pages
| Chave | Path padrao | Arquivo |
|---|---|---|
legal.page | /page/:slug | routes/page.$slug.tsx |
profiles | /perfis | routes/profiles._index.tsx |
profiles.detail | /perfil/:slug | routes/profiles.$slug.tsx |
| — (sem chave) | /lp/:slug | routes/lp.$slug.tsx — catch-all generico; cada brand define quais slugs existem em ~/config/routes/lps. Slug desconhecido = 404 em runtime |
As rotas de profiles sempre existem, mas os loaders se auto-gateiam por
featuresConfig.profiles?.enabled + a quantidade de perfis registrados na brand — quando desligado,
lancam 404.
User (fora do user layout)
| Chave | Path padrao | Arquivo |
|---|---|---|
user.notifications | /user/notifications | routes/user/notifications.tsx |
user.validate | /user/validate/:type/:token | routes/user/validate.$type.$token.tsx |
User Area (auth-protected, navegacao em abas)
Renderizadas dentro de layout("routes/user/_layout.tsx").
:::info Modelo de abas
As rotas antigas user.account, user.security, user.loginHistory, user.accountProtection e
user.irpf nao existem — routeHref("user.account") e erro de typecheck. Foram substituidas
pelas abas abaixo.
:::
| Chave | Path padrao | Arquivo |
|---|---|---|
user.data | /user/data | routes/user/general.tsx |
user.wallet | /user/wallet | routes/user/wallet.tsx |
user.history | /user/history | routes/user/history.tsx |
user.config | /user/config | routes/user/config.tsx |
user.protection | /user/protection | routes/user/protection.tsx |
user.rewards | /user/rewards | routes/user/rewards.tsx |
user.incomeReport | /user/income-report | routes/user/income-report.tsx |
user.refers | /user/refers | routes/user/refers.tsx |
_layout.tsx (loader)
O _layout.tsx e o layout route que carrega a brand, monta o clientEnv e renderiza o
DefaultLayout.
:::danger O loader do _layout NAO retorna auth
Desde a spec user-data-out-of-ssr (2026-07), nenhum dado do usuario logado atravessa o SSR. O
campo auth foi removido do loader por completo, e existe um teste leak-canary
(app/routes/__tests__/no-user-data-in-ssr.test.ts) que quebra se ele voltar. Para ler auth na UI,
use useAuthGate() — ver State Management.
:::
O shape real do retorno:
interface LayoutLoaderData {
brand: BrandConfig | null;
brandError: BrandError | null;
clientEnv: ClientEnv;
serverIsMobile: boolean;
legalTerms: Promise<Pick<LegalTerm, "route" | "title">[]>; // deferred (streaming)
orgCountry: { alpha2: string; name: string } | null;
}
brand e o unico await critico no caminho do TTFB. legalTerms volta como Promise nao
aguardada: o RR7 faz streaming via <Await>/<Suspense> no footer, que degrada graciosamente para
links config-only com [].
shouldRevalidate
O loader do _layout nao revalida em navegacao SPA entre paginas diferentes (retorna false):
brand, legalTerms, clientEnv e serverIsMobile sao estaticos pela sessao inteira. Isso
eliminou um round-trip ao BFF na cauda de cada navegacao. Com auth fora do loader, o skip nao tem
nenhum efeito sobre reatividade de auth.
Dicas
- Edite
app/router/routes.tspara adicionar/remover paginas —app/routes.tsapenas importabuildRoutes()dali - Use
routePattern(key)na arvore em vez de strings hardcoded - Use
routeHref(key)ougameHref(slug)para links em componentes — nunca paths hardcoded - Use
layout()para nested layouts eindex()para a pagina default de um grupo - Rotas cacheaveis no edge sao whitelists de prefixo literal em
workers/middleware.ts: ao renomear um path por override, adicione o prefixo novo la tambem — ver Caching - API routes sao server-only, nao geram bundle client-side e nao fazem parte do Route Registry