Brand Loading (Server-Side)
Visão geral
A configuração de marca (brand) é carregada no server-side via React Router loader, com cache e tratamento de erro robusto, e passada aos componentes via cadeia de Providers.
Fluxo de dados
_layout.tsx loader (server-side)
├── loadBrandConfig(cloudflareEnv) → BrandResult
│ ├── Cache hit (getBrandCache) → { ok: true, brand, fromCache: true }
│ ├── Cache miss:
│ │ ├── createClient(cloudflareEnv) → ApiClient
│ │ ├── createBrandFromClient(client, { country, language }) → BrandConfig
│ │ │ ├── GET /appearance → transformAppearance
│ │ │ ├── GET /bff/features → transformFeatures
│ │ │ └── POST /bookmaker-settings → transformSettings
│ │ └── { ok: true, brand, fromCache: false }
│ └── Erro → { ok: false, error: { message, status, detail? } }
├── Monta clientEnv (17 campos)
├── loadAuthProfile(request, env) → auth profile (token do cookie)
├── generateSmarticoUserHash(userId, env) → smarticoHash
└── return { brand, clientEnv, auth, smarticoHash }
DefaultLayout (client-side)
├── <EnvProvider env={clientEnv}>
│ └── <BrandProvider brand={brand}>
│ └── <CountryProvider>
│ └── <TranslationProvider>
│ └── Componentes filhos (useBrand(), useClientEnv(), useCountry(), useTranslation())
BrandResult — Discriminated Union
O loadBrandConfig retorna um discriminated union para tratamento explícito de erro:
type BrandResult =
| { ok: true; brand: BrandConfig; fromCache: boolean }
| { ok: false; error: BrandError }
type BrandError = {
message: string;
status: number;
detail?: string;
}
No loader, o resultado é verificado:
const result = await loadBrandConfig(context.cloudflare?.env);
if (!result.ok) {
// Renderiza BrandErrorPage com informações do erro
return { brandError: result.error };
}
const { brand } = result;
Na _layout.tsx, quando brandError está presente, o layout renderiza o componente BrandErrorPage ao invés do conteúdo normal.
Cache de Brand
O brand config é cacheado para evitar 3 requests a cada page load:
getBrandCache(ttl)— retorna instância do cache com TTL configurávelBRAND_CACHE_TTL— variável de ambiente que define o TTL (default: 3600 segundos / 1 hora)- No cache hit, retorna
{ ok: true, brand, fromCache: true }sem fazer requests - No cache miss, faz os 3 requests e armazena o resultado
// Internamente:
const cache = getBrandCache(Number(env.BRAND_CACHE_TTL) || 3600);
const cached = await cache.get(cacheKey);
if (cached) return { ok: true, brand: cached, fromCache: true };
Três requests em paralelo
O @cactus-agents/brand faz 3 requests simultâneos:
GET /appearance
Retorna dados visuais: logo, banners, social links, sponsorships, CSS customizado, cores do tema.
GET /bff/features
Feature flags por país: social auth, carousels, maintenance mode, auth config, módulos ativos, validação, contatos.
POST /bookmaker-settings
Configurações de negócio: nome, SEO, limites de depósito/saque/apostas, bônus, rollover, registro, analytics.
Transformações
O @cactus-agents/brand transforma os dados raw da API em tipos limpos:
- Banners:
{"1":{...}}objects OU[]→Banner[]sorted by order - Links: dedup (último LINK_TO_X entry vence)
- JSON string fields: parsed via
safeParseJson - Flags
0|1→boolean - Country filtering:
selectByCountrycom fallback chain
BrandConfig resultante
type BrandConfig = {
appearance: BrandAppearance // logo, banners, social, links, sponsorships, colors
features: BrandFeatures // feature flags, auth config, modules, contacts
settings: BrandSettings // name, SEO, limits, bonus, registration, analytics
}
Cadeia de Providers
O DefaultLayout monta a cadeia completa de providers:
EnvProvider → BrandProvider → CountryProvider → TranslationProvider
- EnvProvider — disponibiliza
clientEnv(17 campos) viauseClientEnv() - BrandProvider — disponibiliza
BrandConfigviauseBrand() - CountryProvider — configuração por país (moeda, locale, validações) via
useCountry() - TranslationProvider — setup i18next com namespace e idioma da marca via
useTranslation()
Acesso nos componentes
import { useBrand } from '~/context/brand';
import { useClientEnv } from '~/context/env';
function MyComponent() {
const brand = useBrand();
const env = useClientEnv();
// brand.appearance.logo
// brand.features.maintenanceMode
// brand.settings.name
// env.API_BASE_URL
}
clientEnv — 17 campos
O loader monta clientEnv com todas as variáveis necessárias no client-side:
interface ClientEnv {
// Obrigatórios (5)
API_BASE_URL: string;
ORIGIN_DOMAIN: string;
BRAND_LANGUAGE: string;
BRAND_COUNTRY: string;
BRAND_CURRENCY: string;
// Smartico — opcionais (5)
SMARTICO_LABEL_KEY?: string;
SMARTICO_BRAND_KEY?: string;
SMARTICO_VISITOR_LABEL_KEY?: string;
SMARTICO_VISITOR_BRAND_KEY?: string;
SMARTICO_LIBRARY_URL?: string;
// Sports — opcionais (7)
SPORTS_MAIN_PROVIDER?: string;
SPORTS_TEST_PROVIDER?: string;
SPORTS_ALTENAR_INTEGRATION?: string;
SPORTS_ALTENAR_LIBRARY_URL?: string;
SPORTS_BETBY_BRAND_ID?: string;
SPORTS_BETBY_THEME?: string;
SPORTS_BETBY_LIBRARY_URL?: string;
}
Nota: BRAND_TIMEZONE é server-only e não faz parte da ClientEnv.
Meta function — SEO
A _layout.tsx exporta uma meta function que gera tags SEO abrangentes a partir do BrandConfig:
<title>,<meta name="description">— dados debrand.settings- Open Graph tags (og:title, og:description, og:image, og:url)
- Twitter Card tags
- Favicon e theme-color
- Canonical URL
Loader completo
Além do brand, o loader da _layout.tsx também:
- Carrega o auth profile (token extraído do cookie do request)
- Gera o smarticoHash (MD5 para identificação no Smartico)
- Monta o clientEnv completo (17 campos)
export async function loader({ context, request }: Route.LoaderArgs) {
const env = context.cloudflare?.env;
const result = await loadBrandConfig(env);
if (!result.ok) return { brandError: result.error };
const clientEnv: ClientEnv = { /* 17 campos */ };
const auth = await loadAuthProfile(request, env);
const smarticoHash = auth?.user
? generateSmarticoUserHash(String(auth.user.id), env)
: null;
return { brand: result.brand, clientEnv, auth, smarticoHash };
}
Arquivos relevantes
| Arquivo | Papel |
|---|---|
app/services/brand.server.ts | loadBrandConfig() — BrandResult com cache |
app/services/api.server.ts | createClient() — ApiClient SSR |
app/routes/_layout.tsx | Loader (brand + clientEnv + auth + smarticoHash) + meta function |
app/context/brand.tsx | BrandProvider + useBrand() |
app/context/env.tsx | EnvProvider + useClientEnv() |
app/context/country.tsx | CountryProvider + useCountry() |
app/context/i18n.tsx | TranslationProvider |
app/layouts/DefaultLayout.tsx | Monta cadeia de providers |
app/components/common/BrandErrorPage.tsx | Página de erro quando brand falha |