You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
NEXT_PUBLIC_SITE_URLnã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):
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:image aponta 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 (copyLink em src/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 ao sitemap.xml
com <loc> do domínio errado e ao Host: do robots.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/font self-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
quando NEXT_PUBLIC_SITE_URL não estiver setada, e deixe o literal só como último recurso de dev.
.env.example:26 e README.md (linhas ~109, ~295, ~407) — hoje ensinam https://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_URL no ambiente, SITE.url resolve para a origem real do deploy — e não
para um domínio hardcoded que o deploy não serve.
Com NEXT_PUBLIC_SITE_URL setada, 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 o og:image da
home e de /room/<code> apresentam a origem que efetivamente serve o site. Cole os 4 curl no PR.
og:image da produção retorna 200 image/png quando buscada exatamente como publicada no HTML.
.env.example e o README não sugerem mais um domínio concreto de terceiro como valor de produção.
Gate: pnpm typecheck · pnpm build · pnpm test verdes.
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.
Contexto
Toda a identidade pública do site é derivada de
SITE.url(src/lib/site.ts:8):NEXT_PUBLIC_SITE_URLnão está setada no build de produção, então o fallback literal vale — e eleaponta para um domínio que não é o que serve o site. Conferido na produção real agora (2026-08-01):
GET /robots.txtHost: https://coderacer.app·Sitemap: https://coderacer.app/sitemap.xmlGET /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:imageidemGET /room/ABC123og:image=https://coderacer.app/opengraph-image?...E o destino não responde:
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)
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" emsrc/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.
<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 ao
sitemap.xmlcom
<loc>do domínio errado e aoHost:dorobots.txt, o resultado prático é que a produçãoreal 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.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 deployquando
NEXT_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.appcomo valor de produção; devem ensinar "a URL real do seu deploy".
SITE.url(normalização e precedência do fallback).Não decida o domínio nesta issue. Se o dono pretende usar
coderacer.appno futuro, isso continuafuncionando: 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 qualquercenário.
Acceptance criteria (verificáveis)
NEXT_PUBLIC_SITE_URLno ambiente,SITE.urlresolve para a origem real do deploy — e nãopara um domínio hardcoded que o deploy não serve.
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).src/lib/share.test.ts) cobrindo: env setada, env ausente comorigem do deploy disponível, env ausente sem nada disponível (fallback de dev), e trailing slash.
GET /robots.txt,GET /sitemap.xml, o<link rel="canonical">e oog:imagedahome 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.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 prefixoNEXT_PUBLIC_viraundefinedno bundle do browser e pode divergir do valor do servidor. Comoshare.tssó usaSITE.urlcomo default quando não há
window(src/lib/share.ts:18), o risco prático é baixo, mas confirme quenada 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 deixeshare.tsexplicitamente com o default de dev.Relacionada (não é duplicata): #84 acrescenta
/practiceaositemap.ts— este aqui conserta aorigem de todas as entradas, inclusive as que a #84 vai adicionar.