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
| Arquivo | Lido por | Contexto |
|---|---|---|
.dev.vars | Wrangler (SSR) | Server-side (loaders/actions/middleware). Existem .dev.vars.<brand> por marca |
.env.local | Vite | Precedência maior que .env; sobrevive a troca de brand. É onde vive o DEV_PORT |
.env | Vite | Fallback de dev (o repo versiona só o .env.example) |
wrangler.toml | Wrangler | Config 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ável | Obrigatória | Na ClientEnv? | Descrição |
|---|---|---|---|
API_BASE_URL | Sim | Não | Base URL do BFF. Server-only (ver invariante acima) |
ORIGIN_DOMAIN | Sim | Sim | Domínio da marca. Resolve o brand override (. → -), o header tenant e o fallback de namespace de cache |
PUBLIC_DOMAIN | Não | Sim | Domínio público canônico quando difere do ORIGIN_DOMAIN (caso bet7k.cl, servido por worker com ORIGIN_DOMAIN=cl.bet7k.com). Consumido só pelas superfícies de SEO (robots, canonical, og:url, JSON-LD) |
BRAND_LANGUAGE | Sim | Sim | Ex: pt-br |
BRAND_COUNTRY | Sim | Sim | Alpha-3, ex: BRA |
BRAND_CURRENCY | Sim | Sim | Ex: BRL |
BRAND_TIMEZONE | Sim | Não | Ex: America/Sao_Paulo. Server-only |
Captcha e kill-switches de feature
| Variável | Na ClientEnv? | Descrição |
|---|---|---|
TURNSTILE_SITE_KEY | Sim | Site key pública do Turnstile (captcha de registro, quando habilitado via authConfig) |
CASSINO_MODE | Sim | legacy | api_new. Ausente/inválido → legacy. O valor mixed do server é exposto ao client como legacy |
RUT_VALIDATION | Sim (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_SPORTBOOK | Sim | Forç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_AUTH | Não | Kill-switch do doc compartilhado pro logado. Default OFF — ver Caching |
SSR_SWR_ENABLED | Não | Kill-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ável | Descrição |
|---|---|
CF_WORKER_KEY | Enviada ao BFF como header cf-worker-key |
SMARTICO_SALT_KEY | Salt do hash de identificação Smartico |
SPORTS_ROGUE_API_KEY | Header 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_KEY | Header 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_SECRET | Auth (header X-Cache-Secret) de /api/cache/purge e /api/cache/inspect. Espelhada no front-service-api |
Cache — ciclo de vida
| Variável | Descrição |
|---|---|
BUILD_ID | SHA 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_TS | Timestamp 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_GENERATION | Token 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_NAMESPACE | Prefixo 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_JSON | Policy 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ável | Default efetivo | Descrição |
|---|---|---|
SSR_CACHE_HTML_TTL | 60 | s-maxage do documento HTML público |
SSR_CACHE_DATA_TTL | 60 | s-maxage das respostas .data públicas (navegação SPA) |
SSR_CACHE_HTML_BROWSER_TTL | = SSR_CACHE_HTML_TTL | max-age de browser do HTML público. 0 desliga |
SSR_CACHE_DATA_BROWSER_TTL | 0 (omitido) | max-age de browser da resposta .data pública |
SSR_CACHE_KV_TTL | 180 | TTL do snapshot em KV do doc/.data anônimo |
AUTH_SSR_CACHE_HTML_TTL | 60 | s-maxage do HTML no caminho autenticado (por sessão) |
AUTH_SSR_CACHE_DATA_TTL | 30 | s-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ável | Descrição |
|---|---|
CACHE_WARMER_ENABLED | "true" liga. Qualquer outro valor/ausente = o cron dispara e o handler retorna na hora |
CACHE_WARMER_PATHS | CSV 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_ORIGIN | Host 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).
| Binding | Tipo | Função |
|---|---|---|
ASSETS | Assets | Serve ./build/client |
ASSETS_ARCHIVE | R2 (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_KV | KV | Snapshot tier do platform-cache + fallback do SSR cache |
API_SERVICE | Service 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ável | Onde | Função |
|---|---|---|
DEV_PORT | .env.local | Porta 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 / .env | Repassados 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):
| Assunto | Base | Override |
|---|---|---|
| Gamificação (Smartico) | app/config/gamification/gamification.ts e gamification.server.ts | overrides/<brand-key>/app/config/gamification/… |
| Sportsbook | app/config/sports/sports.ts | overrides/<brand-key>/app/config/sports/sports.ts |
<brand-key> vem do ORIGIN_DOMAIN normalizado com . → -.
Prioridade de resolução da salt key do Smartico
SMARTICO_SALT_KEYdo env do Worker (secret)SMARTICO_SALT_KEYviaprocess.env(.dev.vars/ Node)smarticoHashConfig.saltKeydo config file (fallback — normalmente vazio)
Nunca commite a salt key real em arquivo de override.
Removidas
| Variável | Situação |
|---|---|
LEGACY_CASINO_ENABLED | Substituída por CASSINO_MODE |
GAMES_CACHE_TTL | Cache passou a ser controlado 100% via PLATFORM_CACHE_POLICY_JSON |
CASSINO_FRONT_CUSTOM_BASE | Removida junto com o mode front_custom |
BRAND_CACHE_TTL | Nunca 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_KEY | Resí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_KEY | Resí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.varsnã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 oclientEnvmontado no loader. - O Wrangler lê
.dev.varsautomaticamente quando rodapnpm dev. BRAND_TIMEZONEé frequentemente confundido com client-side, mas é server-only.- Os kill-switches
SHARED_PUBLIC_DOC_FOR_AUTHeSSR_SWR_ENABLEDsobem inertes (default OFF) e mudam a semântica de cache quando ligados. Ver Caching.