Pular para o conteúdo principal
← Voltar ao changelog

Changelog - 09/05/2026

Geo-SEO multi-país da rede 7k (BR / CL / NG / FI)

  • Cluster recíproco de hreflang sem x-default: cada um dos quatro domínios da família 7k declara as outras três variantes como alternates explícitas — 7k.bet.br (pt-BR, self) + cl.bet7k.com (es-CL) + ng.7k.bet (en-NG) + fi.7k.bet (fi-FI). Os três overrides novos (overrides/{cl-bet7k-com,fi-7k-bet,ng-7k-bet}/app/config/seo/hreflang.ts) e a atualização do 7k-bet-br removem o x-default que apontava pro Brasil — cada brand é geo-exclusiva (KYC, regulação e moeda locais), então não existe "world fallback" legítimo. Resultado: o Google só serve a variante quando o idioma/região do usuário matcha um alternate explícito (ex: russo ou alemão não vê nenhum domínio, em vez de cair no 7k.bet.br).
  • Schema.org/Organization enriquecida com sinais de país: buildOrganizationJsonLd() (app/utils/seo.ts) agora aceita addressCountry (ISO-3166 alpha-2) + areaServedName (nome do país em inglês) e emite address.addressCountry + areaServed.@type=Country. O _layout.tsx resolve ambos via getCountryByAlpha3 de @cactus-agents/country-config — mesmo catálogo usado pelo api-client pra derivar geo headers, evita duplicar mapping alpha-3 → alpha-2 + nome do país.
  • Novo JSON-LD WebSite com inLanguage: segundo blob de Schema.org reforçando o idioma da página de forma independente do <html lang>. Google parseia inLanguage separadamente, então emitir os dois dá ao algoritmo dois sinais redundantes de locale.
  • Header HTTP Content-Language injetado em toda response HTML pelo app/entry.server.tsx (text/html only — JSON/JS unaffected). Sourced do define __BRAND_LANGUAGE__ do Vite e mapeado pra forma BCP 47. Adiciona uma terceira camada de sinal de locale (header HTTP + JSON-LD + <html lang>).
  • Helper compartilhado app/utils/locale.ts consolida os mapas BRAND_LANGUAGE → BCP 47 (pra <html lang>, Content-Language, inLanguage, hreflang) e BRAND_LANGUAGE → og:locale (pra OpenGraph). Antes os dois mapas ficavam inline em root.tsx e _layout.tsx — havia risco de drift entre <html lang="pt-BR"> e Content-Language: pt. Cobertura: oito códigos ativos (pt-br, pt, en, es, es-mx, es-cl, en-ng, fi-fi) com fallback pra pt-BR.
  • useCasinoNomenclature agora é guiado por i18n, não mais por regex country-aware. O hook colapsa qualquer string vinda do BFF (campo casinoNomenclature) num enum de 2 valores ("casino" | "games") e resolve o substantivo traduzido via t("layout:sidebar.casino") / t("layout:sidebar.nomenclature_games"). Adicionar país novo virou uma linha em cada layout.json do core — zero mudança no front. Dependência de useBrand().features.country?.code removida.
  • Doc operacional fora-do-código: o sinal mais forte de geo-targeting pra gccTLDs (.com, .bet) continua sendo o Google Search Console → International Targeting → Country, que é manual. Walkthrough escrito em docs/superpowers/specs/2026-05-09-google-search-console-geo-targeting.md pro time de SEO/marketing.

Redirects e cobertura de 404 legados

  • Cross-brand redirects pro grupo 7k (PR #782): novo arquivo base app/config/routes/legacy-redirects-cross-brand.ts (default vazio, brand-overridable) carrega o mapa de redirects entre os domínios 7k. Cada uma das quatro brands (7k-bet-br, cl-bet7k-com, fi-7k-bet, ng-7k-bet) ganha um override idêntico de 242 entradas com os patterns PT-BR / ES / EN — o filtro activeLegacyRedirects dropa entries from === to em runtime, então cada brand só consome o que aponta pra fora do seu próprio path. Resolve o cenário em que o Cloudflare roteia entre brands irmãs (geo mismatch) e o visitor caía num path foreign-shaped (ex: /casino/juego/evolution/fan-tan aterrissando em 7k.bet.br).
  • Cobertura ampliada de redirects do legado Vue (PR #784): app/config/routes/legacy-redirects.ts cresceu de poucas dezenas pra cobrir o umbrella inteiro /casino/... (live, category/:slug, providers, providers/:slug, :provider/:game, :provider/:game/faq) — recriando o catchall /casino/[...casino].vue que o Nuxt tinha e o novo stack nunca reproduziu. Inclui ainda variantes localizadas (/cassino/... PT-BR, /casino/{en-vivo,categoria,proveedores,juego/...} ES, /casino/play/... EN), esportes (/esportes, /deportes), help (/ajuda, /ayuda, /help), gamification em todos os locales, área de usuário (/usuario/*, /jugador/*, /player/*), histórico legado (/user/history/{casino,full,poker,sports}), /user/irpfuser.incomeReport, e LPs estáticas (/cassino-online, /cassino-pix, /casa-de-aposta, /poker, etc.). Repro fixado: /casino/live → 301 /cassino/ao-vivo.
  • Mitigação dos top 404 do Cloudflare Analytics (PR #786) em três frentes:
    • Filtro de garbage paths no Worker (~30k 404s/dia eliminados): handleMiddlewareRoute() curto-circuita paths com URL/HTML injection ([http://, [https://, ://, <, >, %3a, %2f, %3c, %3e) com 204 No Content + cache imutável de 24h. Bots e marketing tags com macros não-substituídas (ex: /cassino/jogar/evolution/[https://bat.bing.net/bat.js]) param de poluir o Observability — o CF cacheia o body vazio e hits repetidos nem chegam ao Worker. 42 unit tests fixam o regex.
    • Restaura /twa.js para 7k-bet-br e cassino-bet-br (~4.6k 404s/dia + bridge AppsFlyer corrigida): script restaurado byte-a-byte do legado Nuxt (md5 799d7078, idêntico nas três brands BR antigas) bootstrappa window.twa.getPostMessageService() consumido pelo useAppsFlyer.ts dentro do APK Android (br.bet.k.twa, cassino.bet.br.twa). Sem o script, o AppsFlyer caía silenciosamente pro path HTTP S2S.
    • /cassino/category/:slug híbrido PT+EN (~5.9k 404s/dia): paths indexados como /cassino/category/cassino-ao-vivo/popular (PT-prefix + EN-segment + filtro /popular legado) agora redirecionam pra casino.category (/cassino/categoria/:slug).
  • Combinado com PR #784, a plataforma mitiga ~46k 404s/dia entre todas as brands.

404 dentro do layout

  • Catch-all splat (app/routes/$.tsx) registrado como último filho de _layout, então URLs não-matched (ex: /asda) preservam header / sidebar / footer / topbar em vez de cair na ErrorBoundary do root com tela cheia "nua".
  • ErrorBoundary de layout reusa o loader data via useRouteLoaderData e renderiza <ErrorPage variant="inline"> como children do layout. Aplica também a child routes que dão throw 404 (promotions/$slug, lp/$slug, blog/$slug, recents com flag desligada, etc.).
  • ErrorPage ganha variant: "standalone" | "inline" — standalone mantém o full-screen wrapper usado pelo root.tsx em falha catastrófica; inline drop-a o wrapper pra fluir dentro do main do layout. Removidos o CTA verde "Voltar ao início" (as 3 nav pills cobrem) e o label "OU NAVEGUE PARA". Pills encolhem em telas pequenas (gap, padding, font-size, icon size) pra caber em 320px sem wrap.
  • Novo bloco ErrorDetails sempre visível abaixo das nav pills: payload copy-pasteable em texto puro (URL, user id, build, timestamp, referrer, full UA) pro suporte triar quando o cliente printar/filmar um 404. Auth-reactive via useAccountsStore com o pattern padrão authHydrated + SSR fallback.
  • Status HTTP agora retorna 404 real (era 200 em alguns casos bubbleados) com Cache-Control: no-store pra crawlers e monitoring verem o code certo.
  • Bump do @cactus-agents/i18n pra ^0.87.0 pelas novas chaves error_page.details_* (pt-br, pt, es, en).

Sitemap reorganizado

  • Sitemap de jogos splittado por provider sob namespace /sitemap-games/ (PR #781). Estrutura nova:
/sitemap.xml (root index)
├── /sitemap-static.xml
├── /sitemap-games/<provider>.xml (NEW — um urlset por provider)
├── /sitemap-games/categories.xml (renomeado de /sitemap-categories.xml)
├── /sitemap-games/providers.xml (renomeado de /sitemap-providers.xml)
├── /sitemap-promotions.xml
├── /sitemap-blog.xml
├── /sitemap-faq.xml
└── /sitemap-legal.xml (NEW — incorpora PR #743)
  • Por que splittar por provider: Search Console reporta indexing status por sitemap → granularidade por provider expõe qual está falhando crawl; invalidação de cache CDN isolada por família (game novo da PG Soft não bust-a o sitemap da Pragmatic); payloads naturalmente abaixo do limite de 50k URLs sem precisar shard. Formato directory-style (/sitemap-games/<provider>.xml) escolhido em vez do dash-style porque o compilePath do React Router 7 só matcha tokens :param precedidos de /.
  • Slugs reservados: categories e providers não podem ser claimados por provider real — as rotas literais irmãs vencem por especificidade no RR e o loop do index pula essas keys defensivamente. A rota legada /sitemap-games.xml flat foi removida (404). Recomendação pós-deploy: ressubmeter /sitemap.xml no Search Console pra acelerar reindex.

API e classificação de erros

  • Helper app/utils/proxy-error.server.ts (proxyErrorResponse(err, fallback, init?) + unauthorizedNoToken()) substituiu o envelope { ok: false, error, detail } em 60 rotas internas /api/* (wallet, payments, user, kyc, rewards, income-report, validation, sports, games, address, favorites, auth/{profile,recheck-spa,refresh}). Em 401/403 o body do BFF é passado intacto pro classifier de @cactus-agents/api-client — antes, o top-level error: "wallet_fetch_failed" lockava a lookup e a reason real do BFF (escondida em detail) nunca era avaliada. Resultado: todo 401 colapsava em EM0005 (catch-all), todo 403 em EM0011, perdendo EM0001 no_token, EM0003 expired, EM0006 ip_changed, EM0009 liveness_required. Reportado em 05/2026: usuário cassino.bet.br tomou #EP0710-EM0005 no /api/wallet/refresh mid-game enquanto o log do CF mostrava reason: "wallet_fetch_failed" (o envelope, não a causa).
  • Fluxos pre-auth (login, register, recovery, social, validate-document, logout) intencionalmente preservados — não disparam handleUnauthorized e dependem de mapBff*Error / FormErrorCode pra UX form-level.
  • 32 testes novos no helper, mais 5 ponta-a-ponta contra o classifier real. Suite total: 71 files / 685 tests passando.

Fix de hidratação dos widgets de Wins

  • Eliminada a flash de skeleton no F5 dos widgets BiggestWinsCarousel, LatestBetsCarousel, WinsCombinedWidget, BiggestWinsListWidget e HomeWinsRightColumn. Duas causas combinadas:
    • Widgets liam topWins / lastWins da Zustand store gated em _isHomeHydrated, que só virava true num useEffect de rota — SSR + primeiro render mostravam skeleton até o mount.
    • Todos estavam atrás de React.lazy() + <Suspense fallback> — mesmo após corrigir #1 via props, o Suspense substituía brevemente o HTML SSR pelo SectionSkeleton enquanto o chunk carregava.
  • Fix espelha o pattern do TopGamesSection: widgets recebem topWins / lastWins como props threaded de cada loader → HubRowRendererrenderWidget. Reads internos da store + gates _isHomeHydrated + skeleton blocks removidos. Wins widgets convertidos de lazy() pra static imports (custo de bundle ~4-6KB gzip — deps compartilhadas como Marquee, WinCard, SectionTitle já residentes). SectionSkeleton mantido pros outros lazy widgets (ProvidersCarousel, RecommendedBanners, FeaturedGame, Tournaments, Missions, MainLeagues) que ficam deep below the fold.
  • Casos resolvidos: cassino-bet-br row 2 da home (biggest-wins-today, above-the-fold), todas as 4 brands no row 3 do casino hub & casino live (biggest-wins-and-latest-bets, dentro de EAGER_CASINO_ROW_COUNT=4), e o aside desktop do vera-bet-br (HomeWinsRightColumn).
  • Slices topWins / lastWins da Zustand continuam populadas por hydrateHome por backward compat — sem readers agora, safe pra cleanup em follow-up.

Brand Overrides

  • fi-7k-bet (Finlândia): tradução completa pra finlandês das 17 entries de SEO page-descriptions (home, casino, casino.live, casino.category, casino.provider, casino.providers, blog, promotions, vip + 8 sub-páginas VIP, casino.category:todos-os-jogos), usando terminologia domain-aware (kasino, verkkovedonlyönti, kolikkopelit, live-kasino, krash-pelit, talletus, nosto, pelaa vastuullisesti, VIP-ohjelma). Antes era cópia do EN — a brand já roda com BRAND_LANGUAGE=fi-fi, mas o copy above-the-footer ainda renderizava em inglês ("7K: Your trusted online sportsbook"). Recomendado um pass de revisão por nativo antes de qualquer copy regulada (responsible-gambling, KYC, payments) — o Finnish gambling regulation tem fraseologia específica que tradução automática não pega. Nomes de ligas no sidebar-sports.ts padronizados pra PT (Brasileirão Série A, Liga Europa, Copa do Brasil) — antes divergia do home-leagues.ts em três casos. Removido o fallback label: "Store" hardcoded no override de sidebar-buttons — agora intent: "store" resolve pra "Kauppa" via i18n core.
  • 7k-bet-br: refactor dos 4 arquivos de analytics (analytics.ts, appsflyer.ts, hotjar.ts, mixpanel.ts) pro pattern isDev() ? DEV_* : PROD_* (consistente com o resto do arquivo). Webtrends desativado (webtrendsAccountId: undefined em dev e prod) — token preservado comentado pra referência caso precise reativar. CSS anti-flicker do Webtrends removido (estava causando deadlock no initial page load).
  • 7k-bet-br + cassino-bet-br: /twa.js restaurado em public/twa.js + registro <script src="/twa.js" defer> em app/config/scripts/scripts.ts (parte do PR #786 — bridge AppsFlyer pro APK Android). Vera-bet-br já tinha sua variante e ficou intocada.
  • cl-bet7k-com, fi-7k-bet, ng-7k-bet: três brands ganharam override de app/config/seo/hreflang.ts com o cluster recíproco da família 7k (auto-referência + outras 3 sister-domains, sem x-default).

Ops

  • Deploy configs (front-ops): deploy.yml atualizado pra mudar a default branch de cl-bet7k-com, fi-7k-bet e ng-7k-bet pra main-inter e setar BRAND_LANGUAGE correto por env (es-cl, fi-fi, en-ng respectivamente). Mesmas mudanças de BRAND_LANGUAGE aplicadas aos staging equivalentes (stagecl-bet7k-com, stagefi-7k-bet, stageng-7k-bet). stagecl-bet7k-com também trocou default branch de performance pra stage pra alinhar com a strategy de branching atual.
  • Default branch do prod-7k-bet-br ajustada em deploy config separado.

Core / SDK

  • @cactus-agents/i18n bumped para ^0.88.0 — ship das novas keys: error_page.details_* (pt-br, pt, es, en) usadas pelo bloco ErrorDetails, layout:sidebar.nomenclature_games consumida pelo useCasinoNomenclature refatorado, e layout:sidebar-buttons.store que destrava a remoção do fallback "Store" hardcoded no fi-7k-bet. Adicionar país novo agora é uma linha em cada layout.json do core, sem mudança no front.
  • @cactus-agents/api-client bumped para ^0.14.1.
  • Dependências do core estabilizadas (PR #774 + #775): vários @cactus-agents/* saíram de tag beta pra stable em package.json do front-web-base; @cactus-agents/i18n e @cactus-agents/payments bumpados pra stable em commit separado.
  • Suite de regressão no core (commit 68d3728 mencionado no PR #779) com 11 cases pinned cobrindo o classifier real contra /api/wallet/refresh, /api/kyc/status, /api/user/login-history etc. — garante que mexer no classifyApiError não regrida a granularidade dos códigos EM*.

Documentação

  • docs-internal/endpoints/error-codes.md: nova seção "Envelope das rotas internas — preservar reason em 401/403" documentando a regra dura, o helper proxyErrorResponse / unauthorizedNoToken, as exceções legítimas (fluxos pre-auth: login, register, recovery, social, validate-document) e o procedimento pra adicionar rotas novas. Worked example baseado no caso real cassino.bet.br #EP0710-EM0005 reportado em 05/2026.