Repository navigation
Description
Contexto
Toda a identidade pública do site é derivada de SITE.url (src/lib/site.ts:8):
const RAW_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://coderacer.app";NEXT_PUBLIC_SITE_URL não está setada no build de produção, então o fallback literal vale — e ele
aponta para um domínio que não é o que serve o site. Conferido na produção real agora (2026-08-01):
| Onde | O que a produção publica hoje |
|---|---|
GET /robots.txt |
Host: https://coderacer.app · Sitemap: https://coderacer.app/sitemap.xml |
GET /sitemap.xml |
<loc>https://coderacer.app</loc> e <loc>https://coderacer.app/leaderboard</loc> |
GET / (HTML) |
<link rel="canonical" href="https://coderacer.app"/> · og:url · og:image idem |
GET /room/ABC123 |
herda og:image = https://coderacer.app/opengraph-image?... |
E o destino não responde:
curl -sIL https://coderacer.app/opengraph-image -> 503 (redireciona para www.coderacer.app)
curl -sI https://code-racer-three.vercel.app/opengraph-image -> 200 image/png 153 KB
Ou seja: o asset de OG existe e funciona — só a URL anunciada está errada.
Por que isso é P1 de crescimento (não cosmético)
- Todo link compartilhado sai sem imagem. O
og:imageaponta para um host 503, então WhatsApp,
Discord, Slack, X e Telegram renderizam o card sem preview. Isso atinge exatamente os dois motores
de crescimento que a frota já construiu: o botão "Compartilhar" do Resultado (Crescimento: tela de Resultado não tem "compartilhar" — transformar cada corrida terminada em convite (loop viral) #22) e o convite de
sala (copyLinkemsrc/components/Lobby.tsx:60-65, e o chip "copiar link" em
src/components/RoomView.tsx:265-269). O convite é o loop de aquisição nº1 de um jogo sem login —
hoje ele chega no chat do amigo como um link cru. - O canonical entrega o SEO para outro domínio.
<link rel="canonical" href="https://coderacer.app">
instrui o buscador a atribuir o conteúdo a uma origem que não serve o site. Somado aositemap.xml
com<loc>do domínio errado e aoHost:dorobots.txt, o resultado prático é que a produção
real não acumula indexação — todo o trabalho de SEO já feito (metadata, OG/Twitter cards, JSON-LD,
next/fontself-hosted) não converte em nada. - O JSON-LD tem o mesmo defeito (
src/app/layout.tsx:82,:103-104).
Escopo
src/lib/site.ts:8— o fallback não pode ser um domínio arbitrário. Derive a origem real do deploy
quandoNEXT_PUBLIC_SITE_URLnão estiver setada, e deixe o literal só como último recurso de dev..env.example:26eREADME.md(linhas ~109, ~295, ~407) — hoje ensinamhttps://coderacer.app
como valor de produção; devem ensinar "a URL real do seu deploy".- Cobrir com teste o contrato de
SITE.url(normalização e precedência do fallback).
Não decida o domínio nesta issue. Se o dono pretende usar coderacer.app no futuro, isso continua
funcionando: basta setar a env ou apontar o domínio na Vercel. O que esta issue conserta é o site
anunciar como canônica uma origem que ele não serve. Se o Conselho entender que escolher o domínio
público é chamada do dono (§7.1), aplique decisao-dono — mas o fallback quebrado é bug em qualquer
cenário.
Acceptance criteria (verificáveis)
- Sem
NEXT_PUBLIC_SITE_URLno ambiente,SITE.urlresolve para a origem real do deploy — e não
para um domínio hardcoded que o deploy não serve. - Com
NEXT_PUBLIC_SITE_URLsetada, ela continua tendo precedência (comportamento atual preservado),
com a normalização de barra final mantida (src/lib/site.ts:14). - Teste novo (vitest, no padrão de
src/lib/share.test.ts) cobrindo: env setada, env ausente com
origem do deploy disponível, env ausente sem nada disponível (fallback de dev), e trailing slash. - Após o deploy:
GET /robots.txt,GET /sitemap.xml, o<link rel="canonical">e oog:imageda
home e de/room/<code>apresentam a origem que efetivamente serve o site. Cole os 4curlno PR. -
og:imageda produção retorna200 image/pngquando buscada exatamente como publicada no HTML. -
.env.examplee o README não sugerem mais um domínio concreto de terceiro como valor de produção. - Gate:
pnpm typecheck·pnpm build·pnpm testverdes.
Dica de abordagem
Na Vercel, o build expõe a origem do deploy em variáveis próprias (a de produção do projeto e a do
deploy corrente). Uma cadeia NEXT_PUBLIC_SITE_URL → origem de produção do provedor → origem do deploy
→ literal de dev resolve o caso sem exigir nenhuma ação no painel. Cuidado: src/lib/site.ts é
importado por src/lib/share.ts, que roda no cliente — variável sem prefixo NEXT_PUBLIC_ vira
undefined no bundle do browser e pode divergir do valor do servidor. Como share.ts só usa SITE.url
como default quando não há window (src/lib/share.ts:18), o risco prático é baixo, mas confirme que
nada renderizado no cliente passa a divergir do HTML do servidor (hydration). Se preferir cortar o
problema pela raiz, mantenha a resolução restrita aos arquivos de servidor (layout, robots,
sitemap, manifest, opengraph-image) e deixe share.ts explicitamente com o default de dev.
Relacionada (não é duplicata): #84 acrescenta /practice ao sitemap.ts — este aqui conserta a
origem de todas as entradas, inclusive as que a #84 vai adicionar.
Activity
caioross commentedon Aug 2, 2026
Resolvida em PR #123 — #123
SITE.urldeixa de cair num literal e passa a ser descoberta:NEXT_PUBLIC_SITE_URL→domínio de produção do provedor → domínio do deploy corrente →
http://localhost:3000(dev).Precedência da env e normalização de barra final preservadas;
.env.examplee README (pt/en)não sugerem mais um domínio concreto de terceiro.
Prova além do gate: build com
VERCEL_PROJECT_PRODUCTION_URLsimulada gerarobots.txt,sitemap.xml, canonical,og:url/og:imagee JSON-LD todos emcode-racer-three.vercel.app— e esse
og:imagejá responde200 image/png. Gate: typecheck · 186 testes (13 novos) ·build · validate-persistence 72/72 · validate-metrics 45/45.
Os dois AC de pós-deploy (os 4
curlna produção) só podem ser fechados depois do merge —comando pronto no corpo do PR.
fix(seo): reconstrói a origem do parse e falha alto sem origem na Ver…
caioross commentedon Aug 2, 2026
Status pelo PR Doctor: a PR #123 passou por dois quóruns adversariais (3 lentes cada) e está parada em DRAFT +
decisao-dono— não por falta de trabalho, e sim porque o desfecho depende de um fato do ambiente que nenhum agente consegue observar: as System Environment Variables do projeto na Vercel (get_projectresponde 403; o preview está atrás do SSO). Com elas ligadas, a PR resolve esta issue; desligadas, ela trocahttps://coderacer.app(503) porhttp://localhost:3000.Contexto, medições e opções: [Decisão] em #126 — a opção recomendada (definir
NEXT_PUBLIC_SITE_URLno painel) resolve esta issue hoje, sem código.O que a PR já entrega e não está em disputa: o saneamento da origem antes dela virar
Host:/<loc>/JSON-LD — a lente AppSec provou que amainde hoje injeta o valor cru dentro do<script type="application/ld+json">.