Pular para o conteúdo principal

Pontos de Customização Rápida

Alguns arquivos do template existem justamente pra serem trocados por um fork — são os primeiros que uma brand nova vai querer alterar. Mas o cliente não está limitado a eles: qualquer arquivo sob ~/ é overrideable, e qualquer arquivo do projeto pode ser editado direto.

Estrutura app/config/ em subpastas semânticas

app/config/ é organizado em subpastas por domínio — 22 hoje. Imports usam path completo (~/config/<dir>/X), sem suffix .config. O override da brand espelha o mesmo path.

Lista viva — pra conferir o estado atual:

ls -d app/config/*/ | sed 's|app/config/||;s|/$||'
app/config/
├── analytics/ analytics, schema, hotjar, pendo, appsflyer, …
├── auth/
├── cashback/
├── catalog/ categories.custom, categories.personalize, category-chip-order,
│ game-thumbs, smart-related
├── content/ faq.server, game-details.server
├── features/ features, configcat
├── ftd-checkin/
├── gamification/ gamification, gamification.server
├── layout/ composition (slots), header, sidebar, sidebar-sports,
│ bottomnav, menu, user-tabs
├── legal/ licenses, pages, html/{privacy,terms,…}
├── payments/ deposit
├── profiles/
├── referral/ v1, v2
├── routes/ paths, legacy-redirects, legacy-redirects-cross-brand,
│ live-hub-redirect, lp-redirects, lps, lps/
├── scripts/ scripts, images, games (inject de provider)
├── search/
├── sections/ home-rows.{legacy,new,custom}, casino-rows.{…},
│ casino-live-rows.{…}
├── seo/ seo, page-descriptions.server, games-seo.server, sports-seo
├── sports/ sports
├── strategy/
├── theme/ colors, sizes, fonts, font-preloads, header,
│ mobile-bottom-nav, search-input
└── widgets/ sidebar-buttons, topbar-notifications, bottom-notifications,
campaign-widget, home-leagues

Há também arquivos soltos na raiz de app/config/: app-install.ts, brand-appearance.ts, brand-links.ts, version-panel.ts.

1. theme/colors.ts

Define os tokens de cor do tema da brand. Todo fork vai querer personalizar este arquivo.

// app/config/theme/colors.ts (ou overrides/<brand>/app/config/theme/colors.ts)
export const themeColors = {
primary: "#ff6b00",
"bg-primary": "#1a0a00",
sidebar: {
bg: "#1f1200",
"button-bg": "#3a1f5e",
icon: "#ffffff",
"cta-bg": "#ff6b00",
"cta-text": "#ffffff",
// ...
},
// ... demais tokens
};

Os tokens viram Tailwind utility classes automaticamente: sidebar.button-bgbg-sidebar-button-bg, text-sidebar-button-bg, border-sidebar-button-bg, from-sidebar-button-bg, etc. Componentes usam essas classes pra ficarem brand-aware sem hardcoding.

O tailwind.config.js define theme.colors (não extend), então a paleta stock do Tailwind não existe neste projeto — bg-red-500 não compila. Referência completa de tokens e a regra de cores hardcoded em Theming — Deep Dive.

2. routes/paths.ts

Customiza URL paths por brand (ex: /casino/cassino):

// overrides/<brand-key>/app/config/routes/paths.ts
import type { RoutePathMap } from "~/types/routes";

export const routePaths: RoutePathMap = {
casino: "/cassino",
"casino.play": "/cassino/jogar/:provider/:game",
// ... todas as outras chaves obrigatórias
};

:::caution Registro de rotas não é overrideable O registro de rotas (app/router/routes.ts) não é overrideable via Vite plugin — é um arquivo do fork. Para adicionar novas páginas, edite app/router/routes.ts diretamente. Para customizar URL paths das páginas existentes, use o override de routes/paths.ts. :::

3. layout/composition.ts

Define a composição de layout — qual shell, quais variantes de header, sidebar, footer e outros slots usar.

// overrides/<brand-key>/app/config/layout/composition.ts
import { defineLayoutConfig } from "~/layouts/layout.defaults";

export const layoutConfig = defineLayoutConfig({
shell: "split-shell",
structure: { footerFullWidth: true },
slots: {
sidebar: "sidebar-tabbed",
},
});

:::tip Este é o único config com deep merge — e o merge é dentro do arquivo defineLayoutConfig recebe um DeepPartial<LayoutConfig> e faz deep merge com defaultLayoutConfig. Por isso o exemplo acima declara só o que muda.

Não confunda com o mecanismo de override: a resolução do arquivo continua sendo substituição completa (o Vite plugin escolhe um arquivo). O que o helper merge-ia é o objeto que você passou contra os defaults do base, já dentro do arquivo da brand. :::

As chaves válidas de cada slot são uniões fechadas em app/types/layout.tsessa é a lista viva, consulte lá antes de escrever. Hoje, por exemplo, RightPanelVariantKey é never: o slot existe mas não tem nenhuma variante registrada, então o único valor aceito é null.

Para criar uma variante completamente nova (ex: "header-custom"):

  1. Criar o componente em app/layouts/variants/header/HeaderCustom.tsx
  2. Adicionar a chave "header-custom" em app/types/layout.ts (HeaderVariantKey)
  3. Registrar em app/layouts/layout-registry.ts
  4. Usar a nova chave no override de composition.ts

Veja a documentação completa em Layout Composition System.

4. widgets/sidebar-buttons.ts (e demais widgets)

Widgets opcionais embebidos (sidebar-buttons, topbar-notifications, bottom-notifications, campaign-widget, home-leagues) têm cada um seu próprio config file que a brand pode redeclarar. Pattern recorrente: { variant: VariantKey | null, items: Item[] }.

Exemplo — sidebar-buttons com gradient horizontal customizado per-item:

// overrides/<brand>/app/config/widgets/sidebar-buttons.ts
import type { SidebarButtonsConfig } from "~/types/sidebar-buttons";
import IconTrophy from "~icons/lucide/trophy";
import IconTarget from "~icons/lucide/target";
import IconIndiqueGanhe from "~icons/custom/indique-ganhe";

export const sidebarButtonsConfig: SidebarButtonsConfig = {
variant: "gradient",
items: [
{
intent: "referral",
iconType: "lucide",
icon: IconIndiqueGanhe,
gradient: ["#a1cd3df2", "#a1cd3d36"], // accent → fade pro sidebar bg
},
{ intent: "tournaments", iconType: "lucide", icon: IconTrophy },
{ intent: "missions", iconType: "lucide", icon: IconTarget },
],
};

:::warning lucide-react não é dependência do projeto Ícones vêm de unplugin-icons com datasets Iconify: ~icons/lucide/<nome>, ~icons/mdi/<nome>, ~icons/simple-icons/<nome>, ou ~icons/custom/<nome> pros SVGs próprios em app/icons/custom/. O pacote npm lucide-react foi removidoimport { Trophy } from "lucide-react" não compila.

Confusão previsível: iconType: "lucide" continua existindo e é válido. Ele é só o discriminador de "este item usa um componente de ícone" (em oposição a image/emoji) — não tem relação com o pacote npm. O tipo do componente é IconComponent (app/types/icon.ts), que qualquer ícone Iconify satisfaz.

Detalhes em Sistema de ícones. :::

Variants visuais: colored, gradient, icon-tiles. O catálogo de intents é fechado — hoje 9 (casino, sports, tournaments, missions, referral, rewards, mini-games, promotions, store) — e adicionar um é mudança de core. Pra one-offs a brand usa customItems (label + href + dicas visuais), sem inventar intent. Uniões vivas em app/types/sidebar-buttons.ts; registry de variants em app/widgets/sidebar-buttons/registry.ts.

Item de catálogo resolve o href via routeHref(routeKey) em tempo de render, o que respeita o path da brand — a brand escolhe o visual, nunca a rota. Detalhes em Layout Composition — Widgets.

Brand overrides (overrides/<brand-key>/app/)

Cada brand pode sobrescrever arquivos espelhando a estrutura de app/ em overrides/<brand-key>/app/. O brandOverridesPlugin do Vite resolve qualquer import ~/xxx olhando primeiro em overrides/<brand-key>/app/xxx; se o arquivo existir lá, ele é usado. Caso contrário, cai no base em app/xxx. Não há whitelist — qualquer arquivo sob ~/ é potencialmente overridável.

Override = file replacement, não deep-merge. Se brand sobrescreve widgets/sidebar-buttons.ts, redeclara o config inteiro. Não há merge campo-a-campo.

:::danger Consequência: campo novo tem que ser propagado pra TODOS os overrides Como o arquivo da brand substitui o do base inteiro, adicionar um campo ao config do base não faz esse campo chegar em nenhuma brand que sobrescreve aquele arquivo — cada uma resolve o campo novo pra undefined.

Não há erro de compilação avisando (o objeto da brand satisfaz o tipo antigo), e o sintoma aparece só em runtime, só naquelas brands. Então: ao adicionar campo em features.ts, theme/colors.ts, layout/composition.ts ou qualquer config com override, propague pra todos.

Pra saber quem sobrescreve o arquivo que você está mexendo:

# quais brands sobrescrevem theme/colors.ts, por exemplo
git ls-files 'overrides/*/app/config/theme/colors.ts'

:::

Brands hoje

13, todas versionadas em overrides/:

7k-bet-br, betpontobet-bet-br, casateste-com, cl-bet7k-com, donald-bet-br, fi-7k-bet, ng-7k-bet, pb-bet, ph-state77-com, pt-state77-com, rj-bet, state77-com, x2b-bet.

# lista viva (só as versionadas — a árvore local pode ter sobras não rastreadas)
git ls-files overrides/ | awk -F/ 'NF>2 {print $2}' | sort -u

:::note cassino-bet-br e vera-bet-br não estão mais aqui As duas saíram do front-web-base em 2026-07 e não têm mais override nem ambiente de deploy. Sobras não rastreadas podem aparecer na sua árvore local — confira sempre com git ls-files, não com ls. :::

Na prática, os alvos mais comuns por domínio (lista viva — conferir app/config/ pra estado atual):

# Visual / layout
theme/colors.ts theme/sizes.ts theme/fonts.ts
theme/font-preloads.ts theme/header.ts theme/search-input.ts
theme/mobile-bottom-nav.ts
layout/composition.ts layout/header.ts layout/footer.ts
layout/header-secondary-nav.ts layout/sidebar.ts layout/sidebar-sports.ts
layout/bottomnav.ts layout/menu.ts layout/user-tabs.ts

# Widgets opcionais
widgets/sidebar-buttons.ts widgets/topbar-notifications.ts
widgets/bottom-notifications.ts widgets/campaign-widget.ts widgets/home-leagues.ts

# Rotas
routes/paths.ts routes/legacy-redirects.ts
routes/legacy-redirects-cross-brand.ts routes/live-hub-redirect.ts
routes/lp-redirects.ts routes/lps.ts

# Catálogo de jogos / categorias
catalog/categories.custom.ts catalog/categories.personalize.ts
catalog/category-chip-order.ts catalog/game-thumbs.ts catalog/smart-related.ts

# Seções de página
sections/home-rows.legacy.ts sections/casino-rows.legacy.ts
sections/casino-live-rows.legacy.ts sections/home-row-overrides.ts

# Conteúdo / features / SEO
features/features.ts features/configcat.ts
analytics/{analytics,schema,analayer,hotjar,pendo,appsflyer}.ts
legal/licenses.ts legal/pages.ts legal/html/*.ts
seo/seo.ts seo/page-descriptions.server.ts
seo/games-seo.server.ts seo/sports-seo.ts
payments/deposit.ts gamification/gamification.ts
sports/sports.ts scripts/{scripts,images,games}.ts
referral/v1.ts referral/v2.ts
content/faq.server.ts content/game-details.server.ts

Casino rows — override por brand

CASSINO_MODE tem dois valores: "legacy" | "api_new" (ver env-vars). Brands em CASSINO_MODE=legacy declaram as rows da home/hub/live nos arquivos .legacy.ts. Em api_new o BFF é a fonte das rows — brands nesse modo não precisam desses overrides.

ArquivoExport
sections/home-rows.legacy.tslegacyHomeRows
sections/casino-rows.legacy.tscasinoRows
sections/casino-live-rows.legacy.tscasinoLiveRows

Só existem as variantes .legacy.ts. Não há .new.ts nem .custom.ts.

Pra ajustar apresentação de rows que vêm do BFF (api_new), o arquivo é sections/home-row-overrides.ts (exports homeRowOverrides, homeMobileCarouselMatchesGrid, recentsRowAfterSlug).

app/config/catalog/categories.personalize.ts é um overlay opcional que aplica orderBy + displayPriority em slugs do BFF (vale nos dois modos). Sem entry, a categoria segue o comportamento nativo do BFF.

game-details.server.ts (SEO e descriptions de jogos)

O arquivo game-details.server.ts centraliza metadados SEO e descriptions de jogos. É server-only — nunca entra no bundle client.

Estrutura:

  • defaults — templates aplicados a todos os jogos sem entry específica
  • games — overrides por jogo (campos omitidos herdam do defaults)

Template tags disponíveis: {game_name}, {game_provider}, {game_rtp}, {brand_name}.

// overrides/casateste-com/app/config/content/game-details.server.ts
export interface GameDetailEntry {
meta_title?: string;
meta_description?: string;
front_description?: string;
}

export interface GameDetailsConfig {
defaults: Required<GameDetailEntry>;
games: Record<string, Partial<GameDetailEntry>>;
}

export const gameDetails: GameDetailsConfig = {
defaults: {
meta_title: "{game_name} - Jogar no CasaTeste | {brand_name}",
meta_description: "{game_name} é um jogo disponível no {brand_name}...",
front_description: "<strong>{game_name}</strong> é um jogo disponível no <strong>{brand_name}</strong>...",
},
games: {
"pgsoft/fortune-tiger": {
meta_title: "Fortune Tiger 🐯 - Exclusivo CasaTeste",
// meta_description e front_description herdam do defaults
},
},
};

routes/paths.ts (Route Registry)

O arquivo app/config/routes/paths.ts permite que brands customizem todos os URL paths de páginas sem tocar em componentes ou no route tree. O override deve exportar um objeto routePaths completo implementando RoutePathMap:

// overrides/state77-com/app/config/routes/paths.ts
import type { RoutePathMap } from "~/types/routes";

export const routePaths: RoutePathMap = {
casino: "/casino",
"casino.play": "/casino/juego/:provider/:game",
sports: "/deportes",
// ... todas as outras chaves obrigatórias
};

Apenas os segmentos estáticos devem ser alterados — placeholders de parâmetros (:slug, :provider, etc.) devem permanecer iguais. Veja a documentação completa em app/types/routes.ts.

:::warning Regra arquitetural app/config deve conter somente configuração estática de brand (dados que mudam por fork/marca). Hooks, parsers, helpers de phone mask e lógica de negócio não devem ficar em app/config. Dados globais (catálogo de países, DDI, currency-country) vêm do SDK @cactus-agents/*. :::

Além dos config files

O cliente pode ir muito além desses arquivos:

  • Criar componentes próprios em app/components/
  • Adicionar páginas em app/routes/ (e registrá-las em app/router/routes.ts)
  • Criar hooks em app/hooks/
  • Adicionar stores em app/store/
  • Customizar serviços em app/services/
  • Redesenhar o layout inteiro — criar um novo layout em app/layouts/
  • Adicionar bibliotecas — instalar o que precisar via pnpm

O que evitar

AçãoPor que evitar
Modificar @cactus-agents/* diretamenteSão pacotes npm — alterações locais serão perdidas no pnpm update
Hardcodar URL de páginaUse routeHref() / gameHref() de ~/utils/routes — path hardcoded ignora o override de routes/paths.ts da brand
Hardcodar corA paleta do Tailwind é substituída pelos tokens da brand; classe de cor stock não compila. Ver Theming
Alterar root.tsx sem necessidadeHTML shell e providers globais — mudanças aqui podem quebrar atualizações futuras do template

app/router/routes.ts é exceção: editar é o caminho correto pra adicionar ou remover páginas. Não é overrideable por brand e não existe helper que o gere — é uma lista explícita de route()/index(), mantida à mão.

dica

Se precisar de algo que o @cactus-agents/* não suporta, entre em contato com o time Cactus. Podemos adicionar ao SDK para que todos os clientes se beneficiem.