Caching
O template usa caching em camadas para performance e resiliência no Cloudflare Workers. Esta página descreve o estado vigente após o cache overhaul (Fases 0-5).
Para a visão sistêmica de "como tudo se encaixa" (incluindo front-service-api e o browser), veja Arquitetura > Cache architecture.
Para "como invalidar X em produção", veja Infraestrutura > Cache operations.
:::info Mudança recente (cache overhaul) Três coisas mudaram em relação à versão anterior:
api-clientserver-side foi desabilitado no brand worker viaserverCache.delegateCaching: true. A única camada de cache de dados de API é o platform-cache engine — fim da duplicação que causava postmortems de staleness (/payment-providers,/appearance).- Cache tags foram introduzidas. Cada resource declara
tags: [...]emfront-ops/config/cache/defaults.yml. Permite purges comotags: ["brand"]ouglob: "games:*". - TTL do
brandConfigcaiu pra 60s + SWR 300s (era 3600s). Mudanças via backoffice / overrides refletem em ≤1min sem precisar de purge manual. :::
Camadas de cache
Requisição
|
v
[1] CF Zone Edge — SSR HTML/.data (CF Cache API + KV "ssr:${BUILD_ID}")
| TTL: HTML 60s + SWR 120s · .data 30s + SWR 30s
| Invalida via: BUILD_ID bump (auto no deploy) ou zone purge_everything
v
[2] Brand Worker Middleware — API gateway cache (rotas /api/*)
| Whitelist: /api/casino-games/*, /api/appearance, /api/country, etc
| TTLs hardcoded em workers/middleware.ts
v
[3] platform-cache engine — dados lógicos (memory → CF Cache API → KV snapshot)
| Policy: PLATFORM_CACHE_POLICY_JSON (do front-ops)
| Tags: brand, catalog, games:*, stats, legal, payments, sports, seo
| Tier 1 (memory): per-isolate
| Tier 2 (CF Cache API): per-DC, namespace "platform-cache-apcsrasj-v3"
| Tier 3 (KV snapshot): cross-DC, opcional por resource
v
[4] front-service-api proxy worker (Frankfurt, Smart Placement)
| CF Cache API + KV API_CACHE_KV
| TTLs por path: SERVICE_API_CACHE_POLICY_JSON (do front-ops)
v
[5] BFF
Camada 1 — SSR Response Cache (Worker Middleware)
Cacheia HTML de SSR (e .data do RR7) no edge da Cloudflare para visitantes anônimos. Implementação em workers/middleware.ts.
Regras
| Condição | Comportamento |
|---|---|
| Método ≠ GET | BYPASS |
Path começa com /api/, /proxy/ | API cache (camada 2) |
Cookie jwt_token presente | SSR cache autenticado (chave por sessionHash) |
Path é asset (/assets/*) | Asset cache (1y immutable + R2 archive fallback) |
| Demais paths whitelisted | SSR cache público |
Política
- Anônimo:
Cache-Control: public, s-maxage=60, stale-while-revalidate=120 - Autenticado:
private, s-maxage=60, stale-while-revalidate=120(chave inclui sessionHash do JWT) - Header
X-Cache:HIT-CACHE | HIT-KV | MISS-API | BYPASS-APIpara debug
KV namespace e KV_SSR_TTL
Namespace: ssr:${BUILD_ID} (CF Cache API) e key ssr:${BUILD_ID}:${md5(method:url)} (KV).
O BUILD_ID (12-char commit SHA) muda a cada deploy → entries antigas ficam órfãs e expiram naturalmente dentro do KV_SSR_TTL.
KV_SSR_TTL é a constante que determina quanto tempo o HTML SSR anônimo sobrevive em KV. Foi reduzida de 12h para 1h (3600s) — esse era o motivo real de banners/mudanças de brand demorarem horas a aparecer após um deploy (o brandConfig já era 60s, mas o HTML cacheado em KV continha o banner baked da renderização antiga).
Com KV_SSR_TTL = 3600, o HTML fica stale por no máximo 1h antes de ser reaprovisionado. Para sub-1h (ex: banner deve aparecer em segundos), é necessário um zone purge ou excluir a rota home do SSR-HTML caching. Não precisa de purge manual desta camada para uso normal. Para invalidar tudo no edge, use zone purge_everything (acionado automaticamente pelo deploy ou via manual-cache-purge.yml scope: zone).
Smartico Proxy
/proxy/smartico.js:
- Origem:
https://libs.s.cactusgaming.net/scactus.js - Cache:
s-maxage=7200, stale-while-revalidate=86400 - CORS:
Access-Control-Allow-Origin: *
Em dev, vite-plugins/dev-proxy-mirror.ts espelha a rota no Vite. Brands devem apontar gamificationConfig.smartico.libraryUrl para /proxy/smartico.js.
Camada 2 — API gateway cache (rotas /api/*)
Cacheia GETs para um conjunto whitelisted de rotas internas /api/* que fazem proxy ao BFF. Independente da camada 3 (platform-cache).
Ceiling 1h: todos os TTLs desta camada estão limitados a 3600s. Um runtime
Math.min(ttl, 3600)emgetApiCacheRulegarante que edits futuros não possam reintroduzir TTLs maiores.
| Rota | TTL |
|---|---|
/api/casino-games/* | 1h |
/api/appearance | 1h |
/api/country | 1h |
/api/configurations/casino | 1h |
/api/bff/features | 1h |
/api/bff/games/top-wins | 1h |
/api/bff/games/last-wins | 1h |
/api/bff/games/statistics | 1h |
/api/cactus-sportbook/search | 5min |
Implementação: workers/middleware.ts:441-467. Namespace api:${BUILD_ID}.
Camada 3 — platform-cache engine
Cache lógico de domínio (resources como brandConfig, homeRows, gameStats). Detalhes completos no SDK: @cactus-agents/platform-cache.
Wire-up no brand worker
import {
createCacheEngine,
parseCachePolicy,
CacheApiStore,
KvSnapshotStore,
NoopSnapshotStore,
} from "@cactus-agents/platform-cache";
const engine = createCacheEngine({
policy: parseCachePolicy(env.PLATFORM_CACHE_POLICY_JSON),
primaryStore: sharedPrimaryStore, // singleton CacheApiStore module-level
snapshotStore: env.PLATFORM_CACHE_KV
? new KvSnapshotStore(env.PLATFORM_CACHE_KV)
: new NoopSnapshotStore(),
domain: env.CACHE_NAMESPACE ?? env.ORIGIN_DOMAIN,
});
TTLs canônicos (PROD)
Definidos em front-ops/config/cache/defaults.yml. Resumo:
| Resource | TTL | SWR | KV | Tags |
|---|---|---|---|---|
brandConfig | 60s | 300s | Sim | brand, config |
homeRows | 3600s | 600s | Sim | catalog, rows, games:list, home |
casinoRows | 3600s | 600s | Sim | catalog, rows, games:list, casino |
casinoLiveRows | 3600s | 600s | Sim | catalog, rows, games:list, casino, live |
gamesBase | 3600s | 300s | Sim | catalog, games:base |
allGames | 3600s | 600s | Sim | catalog, games:list, games:all |
topGames | 3600s | 600s | Sim | catalog, stats, games:high-payers |
gameStats | 1800s | 1800s | Não | catalog, stats, games:stats |
gameDetail | 3600s | 600s | Não | catalog, games:detail |
gameCategory | 3600s | 600s | Não | catalog, games:category |
gameProvider | 3600s | 600s | Não | catalog, games:provider |
gameListPage | 300s | 300s | Não | catalog, games:list, games:listPage |
gameTopWins | 300s | 120s | Não | catalog, stats, games:wins |
topWins | 1800s | 120s | Não | catalog, stats, games:wins |
lastWins | 1800s | 120s | Não | catalog, stats, games:wins |
legalTerms | 3600s | 300s | Não | legal, config |
Brands podem fazer override por env em config/brands/<brand>/environments/<env>/cache.yml (full replace por nível).
Pipeline de fetch
- Primary store (memory → CF Cache API)
- Se cache-only, pára aqui
- KV snapshot (se
fallbackEnabled: true) - Origin fetch
- Em erro de origin: retry primary com staleIfError grace
- Em erro de origin sem cache: KV snapshot last-resort
Single-flight coalescing por isolate evita thundering herd. SWR escreve resultado novo em background via ctx.waitUntil.
Camada 4 — service-api proxy
front-service-api é um Worker separado em Frankfurt (Smart Placement) chamado via Service Binding API_SERVICE. Cacheia respostas HTTP do BFF em CF Cache API + KV.
TTLs e tags em front-ops/config/cache/service-api-defaults.yml, injetados como SERVICE_API_CACHE_POLICY_JSON no deploy do worker (via pnpm deploy → build-cache-policy.mjs).
Não chama-se direto — apenas via Service Binding.
Camada 5 — Client-side (api-client localStorage)
Brand worker tem serverCache.delegateCaching: true, então o api-client não cacheia nada no server. No browser, o api-client mantém um LocalStorageCache com TTL curtos (60s) só pra UX offline:
cac:api:<method>:<url>em localStorage- Limpa em logout (
purgeAllClientCaches) - Geração unificada
apcsrasj-v3— um bump de prefix invalida entradas do browser localStorage na próxima visita
Purge
Endpoints
| Endpoint | Método | Auth | Função |
|---|---|---|---|
/api/cache/purge | POST | X-Cache-Secret | Purge platform-cache + cascade service-api. Aceita segments, tags, glob, only |
/api/cache/inspect | GET | X-Cache-Secret | Estado live do platform-cache (TTL restante por entry) |
/api/dev/cache-policy | GET | open em dev | Lista da policy carregada |
/api/dev/cache-clear | POST | só em dev | purgeAll() |
Body do /api/cache/purge
// Por tags (preferido)
{ "tags": ["catalog", "brand"] }
// Por glob de tag
{ "glob": "games:*" }
// Por segments (legado, ainda funciona)
{ "segments": ["homeRows", "casinoRows"] }
// Scope (gate qual camada atua)
{ "tags": ["brand"], "only": "platform-cache" } // só engine local
{ "tags": ["brand"], "only": "service-api" } // só proxy
// (omitir "only" = ambos em paralelo, padrão)
// Sem nada = purgeAll
{}
Workflows GH Actions
| Workflow | Trigger | Função |
|---|---|---|
manual-cache-purge.yml | workflow_dispatch | Purge ondemand. Inputs: environment, tags/glob/segments, scope, dry_run |
cache-status.yml | workflow_dispatch | Inspeciona estado live (TTLs restantes, tags por resource) |
deploy.yml | push / dispatch | Inclui post-deploy purge + warmup expandido (10 paths) |
Env vars
| Variável | Função |
|---|---|
PLATFORM_CACHE_POLICY_JSON | Policy serializada do brand worker. Injetada pelo front-ops. Ausente = BYPASS mode |
PLATFORM_CACHE_KV | Binding KV para snapshot tier. Provisionado automaticamente |
CACHE_NAMESPACE | Worker name — isola stage/prod que compartilham ORIGIN_DOMAIN |
CACHE_PURGE_SECRET | Auth do /api/cache/purge e /api/cache/inspect. Mirrored no service-api |
SERVICE_API_CACHE_POLICY_JSON | Rules do proxy worker. Injetada pelo pnpm deploy do front-service-api |
BUILD_ID | 12-char SHA do deploy. Usado em namespace do SSR cache e header X-LOG-INFO |
Arquivos
| Arquivo | Descrição |
|---|---|
workers/middleware.ts | SSR/API/asset cache + Smartico proxy |
app/services/platform-cache.server.ts | Singleton sharedPrimaryStore + factory do engine |
app/services/brand.server.ts | engine.fetch("brandConfig", ...) |
app/services/games.cache.server.ts | engine.fetch("homeRows", ...) etc — todos os resources de games |
app/services/api.server.ts | createCactusServerClient com delegateCaching: true |
app/routes/api/cache/purge.ts | Endpoint de purge (tags/glob/segments) |
app/routes/api/cache/inspect.ts | Endpoint de introspecção live |
app/routes/api/dev/cache-policy.ts | Validação da policy carregada |