1

Pitch: Meu site de palpites da Copa lançou hoje. O ranking podia ser forjado por qualquer um via REST — e eu quase não vi

A bola rola hoje na abertura da Copa 2026 — e o Pitacos da Copa está no ar: um site onde o torcedor crava palpite nos 104 jogos, acumula pontos quando o resultado oficial sai e disputa um ranking com a galera. Tem bancada de discussão por jogo, página por seleção e um termômetro de "quem vai ganhar a Copa" agregando os palpites de campeão da torcida.

Stack: Next.js 16 (App Router) + Supabase + Vercel, construído "build in public" com uma particularidade: nas levas finais, trabalhei com agentes de IA em paralelo — cada um num git worktree isolado, e uma sessão "integradora" revisando o PR dos outros antes do merge. Em um dia entraram 4 PRs: SSG, páginas de grupo, o termômetro do campeão e o site inteiro em 3 idiomas.

Mas o que vale o post são as três rasteiras que levei no caminho. Talvez te poupem uma.

1. RLS do Supabase protege a LINHA — não a COLUNA

A clássica policy "o dono pode escrever":

create policy "own predictions write" on predictions
  for update using (auth.uid() = user_id);

Parece segura. Só que o Supabase concede UPDATE em todas as colunas para authenticated — então um cliente mal-intencionado, batendo direto na REST API (ignorando minha UI), conseguia setar points_awarded e is_scored no próprio palpite. Tradução: forjar o próprio score no ranking. A RLS responde "essa linha é sua?", não "você pode escrever esse campo?".

A correção é privilégio por coluna:

revoke insert, update on public.predictions from anon, authenticated;
grant update (predicted_result, predicted_home_score, predicted_away_score)
  on public.predictions to authenticated;

Bônus do mesmo capítulo: toda função nova no Postgres do Supabase nasce com EXECUTE pra anon e authenticated — minha função score_match (SECURITY DEFINER!) estava exposta via REST pra qualquer visitante anônimo. revoke from public não basta; tem que revogar de anon e authenticated explicitamente. Hoje o teste de segurança que eu rodo é: o que um cliente autenticado hostil faz batendo direto no endpoint, ignorando a UI? — "funciona pro usuário legítimo" e "resiste ao usuário hostil" são dois testes diferentes.

2. O Google "descobriu" 90 páginas e nunca visitou nenhuma — a pista estava em 1969

Search Console: 90 URLs em "Detectada, mas não indexada". Abri o relatório e o "último rastreamento" de todas era 1969 — o epoch do Unix, ou seja, nunca rastreadas. Domínio novo não tem problema de conteúdo: tem problema de orçamento de rastreamento.

O que mudou o jogo:

  • Ler cookie força render dinâmico no Next. Meu client do Supabase chamava cookies() pra saber quem está logado — isso anulava até os revalidate que eu achava que tinha. A solução foi separar o público do pessoal: a página vira estática/ISR com a visão de visitante (force-static + generateStaticParams), e o "seu palpite" hidrata no client depois. O Googlebot recebe HTML pronto e barato; o usuário logado continua vendo o dele.
  • Sitemap honesto: tirei os ~40% de URLs de mata-mata que ainda eram "A definir x A definir" (thin content, com noindex até o chaveamento preencher) e parei de mentir lastmod = agora a cada revalidação — lastmod sempre-agora ensina o Google a ignorar o sinal.

3. Site em 3 idiomas sem mudar UMA URL (e sem lib de i18n)

Precisava de /en e /es pra busca global da Copa, mas as URLs em português já estavam indexadas — mudar URL na véspera seria suicídio de SEO. A saída foi um recurso nativo do App Router que pouca gente usa: route groups com dois root layouts.

app/
  (pt)/      → todas as rotas pt, SEM prefixo (URLs idênticas por construção)
  [locale]/  → /en e /es, com <html lang> correto

Nada de middleware reescrevendo rota em runtime (que ia na frente das páginas estáticas do item 2), nada de next-intl: dicionários próprios, hreflang recíproco com x-default e redirect 308 das URLs antigas. O Google nem percebeu mudança nas páginas que já rankeavam.


O detalhe que mais me diverte: o "quem vai ganhar" mostra a porcentagem real dos palpites de campeão da plataforma — dado próprio que nenhum portal tem, atualizando sozinho conforme a torcida crava.

Tá no ar, de graça, sem cadastro obrigatório pra dar o primeiro pitaco: pitacosdacopa.com.br

E aí: em quem você crava o caneco? (e se quebrar alguma coisa, me conta aqui que eu conserto — a Copa só tá começando 😄)

Carregando publicação patrocinada...