Pular para o conteúdo principal

Environment Variables

O template lê variáveis do env do Worker (Cloudflare) em produção e de arquivos locais em dev.

:::info Fonte de verdade O inventário canônico é a interface Env em front-web-base/worker-configuration.d.ts — cada entrada lá tem um doc comment explicando semântica e default. Esta página espelha esse arquivo agrupado por assunto; ao adicionar uma var nova, declare lá primeiro. :::

Arquivos locais

ArquivoLido porContexto
.dev.varsWrangler (SSR)Server-side (loaders/actions/middleware). Existem .dev.vars.<brand> por marca
.env.localVitePrecedência maior que .env; sobrevive a troca de brand. É onde vive o DEV_PORT
.envViteFallback de dev (o repo versiona só o .env.example)
wrangler.tomlWranglerConfig do Worker + bindings (assets, R2, KV, service binding) e Cron Trigger

:::caution [vars] não vive no git As variáveis dos deploys de Worker são injetadas pelo CI do front-ops no momento do deploy (ele faz patch do wrangler.toml acrescentando [vars] + [[kv_namespaces]] antes de wrangler deploy --keep-vars). O próprio wrangler.toml do repo diz explicitamente para não hardcodar [vars] ali — seria sobrescrito pelo CI e envelheceria rápido. Para sincronizar a policy de cache local com o front-ops, rode pnpm check:dev-vars --write. :::

Invariante de segurança — o browser NUNCA recebe o host do BFF

API_BASE_URL é server-only. app/context/env.tsx carrega a proibição em cima da interface ClientEnv:

"⚠️ NUNCA adicionar API_BASE_URL (nem qualquer URL do BFF) aqui. O host final da API não pode chegar ao browser — o client só fala com rotas same-origin /api/*."

Quem lê API_BASE_URL é app/services/api.server.ts (createClient), sempre no server. Todo consumo client-side de dados passa por uma rota interna /api/*, que proxia pro BFF via o service binding API_SERVICE.

Interface ClientEnv (9 campos)

// app/context/env.tsx
export interface ClientEnv {
ORIGIN_DOMAIN: string;
PUBLIC_DOMAIN?: string;
BRAND_LANGUAGE: string;
BRAND_COUNTRY: string;
BRAND_CURRENCY: string;
TURNSTILE_SITE_KEY?: string;
CASSINO_MODE?: CassinoModeClient; // "legacy" | "api_new"
FORCE_SPORTBOOK?: SportsProvider; // já validado no loader
RUT_VALIDATION?: boolean; // já resolvido pra boolean no loader
}

O objeto é montado no loader do _layout.tsx e entregue pelo EnvProvider. Note que dois campos sofrem transformação no loader (não são o valor bruto do env): RUT_VALIDATION vira boolean (=== "true") e FORCE_SPORTBOOK passa por resolveForceSportbook (~/config/sports/provider).

Marca / identidade

VariávelObrigatóriaNa ClientEnv?Descrição
API_BASE_URLSimNãoBase URL do BFF. Server-only (ver invariante acima)
ORIGIN_DOMAINSimSimDomínio da marca. Resolve o brand override (.-), o header tenant e o fallback de namespace de cache
PUBLIC_DOMAINNãoSimDomínio público canônico quando difere do ORIGIN_DOMAIN (caso bet7k.cl, servido por worker com ORIGIN_DOMAIN=cl.bet7k.com). Consumido pelas superfícies de SEO (robots, canonical, og:url, JSON-LD)
BRAND_LANGUAGESimSimEx: pt-br
BRAND_COUNTRYSimSimAlpha-3, ex: BRA
BRAND_CURRENCYSimSimEx: BRL
BRAND_TIMEZONESimNãoEx: America/Sao_Paulo. Server-only

Captcha e kill-switches de feature

VariávelNa ClientEnv?Descrição
TURNSTILE_SITE_KEYSimSite key pública do Turnstile (captcha de registro, quando habilitado via authConfig)
CASSINO_MODESimlegacy | api_new. Ausente/inválido → legacy. O valor mixed do server é exposto ao client como legacy
RUT_VALIDATIONSim (boolean)Kill switch do fluxo de identidade Chile (RUT/Didit). "true" liga RUT + validação + travas + envio no payload. Só surte efeito quando o país é CHL; togglar em prod exige redeploy
FORCE_SPORTBOOKSimForça o provider do sportsbook por ambiente: first | altenar | betby | rogue. Vazio/inválido = usa o config estático da marca
SHARED_PUBLIC_DOC_FOR_AUTHNãoKill-switch do doc compartilhado pro logado. Default OFF — ver Caching
SSR_SWR_ENABLEDNãoKill-switch do serve-stale emulado no Cache API. Default OFF — ver Caching

Secrets (server-only)

Configurar como secret do Worker (wrangler secret put <NOME>), nunca em [vars].

VariávelDescrição
CF_WORKER_KEYEnviada ao BFF como header cf-worker-key
SMARTICO_SALT_KEYSalt do hash de identificação Smartico
SPORTS_ROGUE_API_KEYHeader x-api-key server-to-server pros endpoints de token do sportsbook (/cactus-sportbook/auth/*). Só necessária com o provider rogue ativo
APPSFLYER_API_KEYHeader x-api-key do proxy multibrand AppsFlyer. Distinta por ambiente. Ausente = /api/tracking/appsflyer responde 503 em vez de chamar upstream sem autenticação
CACHE_PURGE_SECRETAuth (header X-Cache-Secret) de /api/cache/purge e /api/cache/inspect. Espelhada no front-service-api

Cache — ciclo de vida

VariávelDescrição
BUILD_IDSHA do commit (12 chars) estampado em toda chave de cache e no payload de /api/version. Injetada pelo front-ops a partir do GITHUB_SHA. Em dev cai no __BUILD_ID__ do Vite e depois em "dev"
BUILD_TSTimestamp do deploy (epoch em segundos). Alimenta o gate de build-currency (isBuildCurrent): SHA não tem ordenação, então esse timestamp é o critério monotônico que permite um worker detectar que virou versão órfã e parar de ler/gravar o SSR cache. Ausente = gate falha aberto (no-op)
CACHE_GENERATIONToken global de geração de cache — a rotação "nuke from orbit", acionada sob demanda pelo workflow bump-cache-generation. Distinta do BUILD_ID (que roda a cada deploy)
CACHE_NAMESPACEPrefixo por deployment que isola entradas do CF Cache API entre ambientes que compartilham ORIGIN_DOMAIN (derivado do worker_name). Ausente = cai no ORIGIN_DOMAIN
PLATFORM_CACHE_POLICY_JSONPolicy do platform-cache serializada, resolvida em deploy-time pelo front-ops. Ausente = engine em bypass (chama as APIs direto — o comportamento normal em dev local)

Cache — tuning do SSR

Todos opcionais; os defaults vivem em workers/middleware.ts. Ver Caching para o significado de cada janela.

VariávelDefault efetivoDescrição
SSR_CACHE_HTML_TTL60s-maxage do documento HTML público
SSR_CACHE_DATA_TTL60s-maxage das respostas .data públicas (navegação SPA)
SSR_CACHE_HTML_BROWSER_TTL= SSR_CACHE_HTML_TTLmax-age de browser do HTML público. 0 desliga
SSR_CACHE_DATA_BROWSER_TTL0 (omitido)max-age de browser da resposta .data pública
SSR_CACHE_KV_TTL180TTL do snapshot em KV do doc/.data anônimo
AUTH_SSR_CACHE_HTML_TTL60s-maxage do HTML no caminho autenticado (por sessão)
AUTH_SSR_CACHE_DATA_TTL30s-maxage do .data no caminho autenticado

:::caution O doc comment de SSR_CACHE_DATA_TTL está defasado O comment em worker-configuration.d.ts diz "Default: 30", mas o DEFAULT_TTLS do workers/middleware.ts usa 60 — o .data foi realinhado ao documento de propósito (janelas diferentes faziam o primeiro paint e a volta por navegação SPA servirem fotos de idades distintas do mesmo conteúdo). O valor efetivo é o do middleware. :::

Cache warmer (Cron Trigger)

O cron (crons = ["*/2 * * * *"]) dispara fleet-wide, mas o handler scheduled é inerte a menos que a flag esteja ligada.

VariávelDescrição
CACHE_WARMER_ENABLED"true" liga. Qualquer outro valor/ausente = o cron dispara e o handler retorna na hora
CACHE_WARMER_PATHSCSV de paths públicos a aquecer, por marca (ex: "/,/cassino,/cassino/ao-vivo,/esportes,/promocoes"). Vazio = no-op mesmo com a flag ligada. Cada path é aquecido em 2 variantes de device (a chave de cache inclui a dimensão device)
CACHE_WARMER_ORIGINHost usado nas URLs do warm. DEVE ser o host servido ao tráfego real. Ausente = cai no PUBLIC_DOMAIN quando setado, senão no ORIGIN_DOMAIN

:::warning Armadilha stage vs prod A chave de cache inclui o host. Em prod ORIGIN_DOMAIN e host servido coincidem, então o default funciona. Em stage o CACHE_WARMER_ORIGIN precisa ser setado explicitamente (o worker_url), senão o warm esquenta uma chave que ninguém lê. :::

Bindings

Não são variáveis de texto — são bindings declarados no wrangler.toml (o KV é acrescentado pelo CI do front-ops).

BindingTipoFunção
ASSETSAssetsServe ./build/client
ASSETS_ARCHIVER2 (front-assets-archive)Arquivo persistente de assets content-hashed entre deploys. Quando o ASSETS atual devolve 404 (aba antiga aberta), o Worker cai nesse bucket — evita 404 de chunk JS durante deploy. Populado pelo CI via node scripts/sync-assets-r2.mjs. Não está declarado na interface Env (é lido com cast)
PLATFORM_CACHE_KVKVSnapshot tier do platform-cache + fallback do SSR cache
API_SERVICEService binding (front-service-api)Proxy com Smart Placement co-locado com o BFF. Quando presente, o ApiClient usa API_SERVICE.fetch() em vez de globalThis.fetch()

Dev-only

VariávelOndeFunção
DEV_PORT.env.localPorta do dev server. Não é sempre 5173 — workspaces paralelos usam 5191-5195
DEV_USER, DEV_USER_TOKEN, DEV_ACCESS_ID, DEV_ACCESS_SECRET.dev.vars / .envRepassados ao createCactusServerClient para autenticação de dev contra o BFF

Configuração por arquivo (não por env)

Duas integrações que costumam ser confundidas com env var são configuradas por arquivo, com override por marca (substituição de arquivo inteiro):

AssuntoBaseOverride
Gamificação (Smartico)app/config/gamification/gamification.ts e gamification.server.tsoverrides/<brand-key>/app/config/gamification/…
Sportsbookapp/config/sports/sports.tsoverrides/<brand-key>/app/config/sports/sports.ts

<brand-key> vem do ORIGIN_DOMAIN normalizado com .-.

Prioridade de resolução da salt key do Smartico

  1. SMARTICO_SALT_KEY do env do Worker (secret)
  2. SMARTICO_SALT_KEY via process.env (.dev.vars / Node)
  3. smarticoHashConfig.saltKey do config file (fallback — normalmente vazio)

Nunca commite a salt key real em arquivo de override.

Removidas

VariávelSituação
LEGACY_CASINO_ENABLEDSubstituída por CASSINO_MODE
GAMES_CACHE_TTLCache passou a ser controlado 100% via PLATFORM_CACHE_POLICY_JSON
CASSINO_FRONT_CUSTOM_BASERemovida junto com o mode front_custom
BRAND_CACHE_TTLNunca existiu no código atual — o TTL do brand vem da policy do platform-cache (resource brandConfig, hoje 60s + SWR 300s em front-ops/config/cache/defaults.yml)
RECAPTCHA_SITE_KEYResíduo no .env.example/.dev.vars. Não está na ClientEnv, não está na Env e não tem consumidor em app/ nem workers/
FAV_GAMES_API_URL / FAV_GAMES_API_KEYResíduo no .env.example. Favoritos migraram pro BFF padrão em 2026-05 — ver Favorite Games

Acesso no código

Server-side (loaders/actions)

export async function loader({ context }: Route.LoaderArgs) {
const cf = context?.cloudflare?.env; // produção (Workers)
const originDomain = cf?.ORIGIN_DOMAIN ?? process.env.ORIGIN_DOMAIN ?? "";
}

O padrão em todo o código é cf?.X ?? process.env.X ?? <default>context.cloudflare.env em produção, process.env em dev/SSR Node.

Client-side (componentes)

import { useClientEnv } from "~/context/env";

const env = useClientEnv();
// env.ORIGIN_DOMAIN, env.BRAND_CURRENCY, env.CASSINO_MODE, …
// (nunca env.API_BASE_URL — o campo não existe)

Para código client que roda fora da árvore React (hoje, os clientLoaders), use getClientEnvSnapshot() do mesmo módulo — devolve null antes da hidratação, então o consumidor precisa de um fallback conservador. Atalho tipado pro modo do cassino: useCassinoMode(), que devolve "legacy" fora do EnvProvider em vez de lançar.

Gotchas

  • .dev.vars não suporta interpolação de variáveis.
  • O Vite só expõe ao client variáveis com prefixo VITE_ — o template não usa esse mecanismo, e sim o clientEnv montado no loader.
  • O Wrangler lê .dev.vars automaticamente quando roda pnpm dev.
  • BRAND_TIMEZONE é frequentemente confundido com client-side, mas é server-only.
  • Os kill-switches SHARED_PUBLIC_DOC_FOR_AUTH e SSR_SWR_ENABLED sobem inertes (default OFF) e mudam a semântica de cache quando ligados. Ver Caching.