name: lovable-vps-502-debug
description: Debug 502/blank-screen errors on Lovable/TanStack Start apps deployed to VPS behind Cloudflare CDN
category: devops
Lovable/TanStack Start VPS — 502 / Tela Branca Debug
Sintomas
- - App serve 200 localmente (
curl localhost:3010) mas browser mostra 502 - - Assets falham com
Failed to load resource: the server responded with a status of 502 - - Tudo funcionava antes de um rebuild/deploy
- - O asset antigo pode não existir mais
- - Cloudflare pode ter cacheado o 502 do asset antigo
- - Email da conta Cloudflare
- - Global API Key (Cloudflare Dashboard → Profile → API Tokens → Global API Key)
- -
{"success":true}→ cache purgado, abrir site normalmente - -
{"success":false}→ credenciais erradas ou token inválido - - O módulo ESM exporta
server.default(nãoserver) - - Erro:
server.fetch(req)→ crash silencioso → HTTP 500 - - Correção:
server.default.fetch(req) - - 502: nginx não consegue alcançar o upstream (servidor node offline ou crashou)
- - 500: servidor node está a funcionar mas retorna erro interno (JS exception no server code)
- - O
(439:6)é character offset NO OUTPUT TRANSPILED, não no source - - TS transpileModule geralmente diz "OK" enquanto esbuild falha
- - O erro raramente está realmente na linha indicada
Ordem de Diagnóstico (do mais rápido ao mais profundo)
Passo 1 — Verificar se o problema é cache do Cloudflare
`bash
Testar do VPS (bypassa Cloudflare)
curl -s -o /dev/null -w '%{http_code}' https://documentos.rochasalesseguros.com.br/
curl -s -o /dev/null -w '%{http_code}' https://documentos.rochasalesseguros.com.br/assets/index-XXX.js
curl -s -X POST https://documentos.rochasalesseguros.com.br/api/gmail/_serverFn -H 'Content-Type: application/json' -d '{}'
Se retornam 200 → problema é Cloudflare CDN cache
Se retornam 502 → problema é upstream (nginx ou node server)
`
Passo 2 — Verificar nginx error logs (sempre fazer primeiro!)
`bash
No VPS
tail -50 /var/log/nginx/error.log | grep documentos
journalctl -u nginx --since '10 minutes ago' | grep error
Padrões comuns:
"upstream prematurely closed connection" → Node.js server.js CRASHOU
"connect() failed (111: Unknown error)" → serviço completamente fora
"conflicting server name" → config duplicada no nginx
`
Passo 3 — Verificar estado do serviço Node
`bash
systemctl status
ss -tlnp | grep -E '3010|3000|8001' # portas a ouvir
ps aux | grep 'node server.js' | grep -v grep
`
Passo 4 — Testar direto na porta do node (bypassa nginx)
`bash
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3010/
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3010/assets/index-XXX.js
curl -s -X POST http://127.0.0.1:3010/api/gmail/_serverFn -H 'Content-Type: application/json' -d '{}'
`
Causas Comuns
Causa 1: Node server.js crashou ao receber pedidos
`
upstream prematurely closed connection while reading response header from upstream
`
Solução: Ver logs do node (journalctl ou output do serviço), corrigir o crash no server.js, reiniciar.
Causa 2: Cloudflare CDN tem cache de resposta 502
Isto acontece quando:
1. Node server ficou offline
2. Cloudflare guardou o 502 em cache
3. Depois o server voltou, mas Cloudflare continua a servir 502 cacheado
Solução: Purge do Cloudflare cache.
Causa 3: Nginx proxy para porta errada
`bash
Verificar config
nginx -T 2>&1 | grep -A5 'location /api/'
Problema comum: Cloudflare Workers build inclui Docker que ocupa porta 8001
Nginx mostra proxy para :8001 mas o app Node está em :3010
OU vice-versa
`
Causa 4: Build gerou novos hashes de assets (arquivos renomeados)
Se o rebuild gerou index-NOVOHASH.js mas o HTML referencia index-HASHANTIGO.js:
Solução: Purge do Cloudflare cache.
Purge do Cloudflare via API (sem ir ao Dashboard)
Precisas de:
Encontrar Zone ID do domínio:
`bash
curl -s "https://api.cloudflare.com/client/v4/zones" \
-H "X-Auth-Email:
-H "X-Auth-Key:
`
Purge Everything:
`bash
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/
-H "X-Auth-Email:
-H "X-Auth-Key:
-H "Content-Type: application/json" \
-d '{"purge_everything": true}'
`
Verificar resposta:
Prevenção — Headers anti-cache no Nginx
Para evitar que Cloudflare guarde 502 em cache no futuro, adicionar no bloco server do nginx:
`nginx
Para assets (já tem max-age alto, mas com query string anti-cache)
location /assets/ {
proxy_pass http://127.0.0.1:3010;
proxy_set_header Host $host;
add_header Cache-Control "public, no-cache, no-store, must-revalidate";
add_header X-Content-Type-Options "nosniff";
}
Para index e HTML (nunca fazer cache)
location = / {
proxy_pass http://127.0.0.1:3010;
proxy_set_header Host $host;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
`
Padrão nginx comum para apps Node.js VPS
`nginx
server {
server_name
location / {
proxy_pass http://127.0.0.1:
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
add_header Cache-Control "no-cache, no-store";
}
location /assets/ {
proxy_pass http://127.0.0.1:
proxy_http_version 1.1;
proxy_set_header Host $host;
expires 7d;
add_header Cache-Control "public, immutable";
}
}
`
Dois Apps Documentos no Mesmo VPS (Armadilha Comum)
`
/var/www/documentos/ → projeto original (Supabase: dauftiqcvgaydddoxqhh)
/var/www/documentos/deploy-vps-nodejs/app/ → mesmo que acima (mesmo .env)
/var/www/documentos2/app/ → CÓPIA (pode ter .env DIFERENTE!)
`
BUG CRÍTICO: documentos2 pode ter .env de um projeto Supabase DIFFERENTE
(lzoiuxulhnvtmadoymrb em vez de dauftiqcvgaydddoxqhh).
Se o site está a funcionar mas o login/auth está partido, verificar se o .env
está a apontar para o projeto Supabase correto. O .env com as credenciais
válidas está em /var/www/documentos/deploy-vps-nodejs/app/.env.
TanStack Start — server.default.fetch() em Vez de server.fetch()
Ao criar um wrapper HTTP manual para o TanStack Start server.js:
`javascript
// ❌ ERRADO — causa HTTP 500
const webRes = await server.fetch(webReq);
// ✅ CERTO
const webRes = await server.default.fetch(webReq);
`
HTTP 500 do TanStack Start (Não 502)
Para debug 500 no TanStack Start:
`bash
Testar localmente no VPS
cd /var/www/
source ~/.nvm/nvm.sh && nvm use 20
timeout 5 node dist/server/server.js 2>&1 | head -20
Se não mostrar nada, o server importou silenciosamente mas não iniciou — verificar .env
`
Verificar Saúde do Server Rapidamente
`bash
HTTP 200 = server ok; HTTP 500 = server respondendo mas com erro interno
curl -s http://127.0.0.1:3010/ --max-time 3 -I | head -5
curl -s https://
Comparar: se localhost 200 mas dominio 500 → problema no server code
Se ambos 502 → node server offline/crashado
`
Notas Importantes
1. Sempre verificar nginx error logs PRIMEIRO — indicam se o upstream está a responder, a crashar, ou se há conflitos de config.
2. Testar sempre do VPS com curl antes de assumir que é Cloudflare — elimina o cache como variável.
3. Hashes de assets mudam em cada rebuild — o HTML gerado referencia os hashes do build atual. Se o Cloudflare tem cache do HTML antigo com hashes antigos, os assets não existem.
4. Porta 8001 vs 3010: Cloudflare Workers builds frequentemente incluem Docker/PM2 que juga 8001. O app Node.js real pode estar em 3010. Verificar qual porta o systemctl status mostra e se o nginx está a apontar para lá.
5. Two-app trap: Quando existir /var/www/documentos E /var/www/documentos2, verificar sempre qual está a servir a porta no nginx e se o .env desse app é o correto. O app "2" pode ter sido criado como backup mas tem .env de projeto Supabase diferente.
Build Failures — JSX SyntaxError com line numbers enganosos
Quando npm run build falha com erro tipo:
`
SyntaxError: Expected corresponding JSX closing tag for
`
Ordem de debug
1. Transpilar com TS para confirmar se é parseável:
`bash
node -e "
const ts=require('typescript'),fs=require('fs');
const r=ts.transpileModule(fs.readFileSync('src/routes/index.tsx','utf8'),{compilerOptions:{jsx:ts.JsxEmit.React,target:ts.ScriptTarget.ESNext},fileName:'test.tsx',reportDiagnostics:true});
if(r.diagnostics?.length)r.diagnostics.forEach(d=>console.log(ts.flattenDiagnosticMessageText(d.messageText,' ')));
else console.log('TS: OK — problema é esbuild/Vite, não TS');
"
`
2. Contar divs manualmente — grep -n "
3. Simplificar JSX quando TS diz OK mas build falha:
- Substituir Shadcn por nativos
- Substituir por com className simples
- Evitar template literals dentro de className em linhas complexas
Build para este app
`bash
cd /var/www/documentos2/app
rm -rf dist node_modules/.vite
cp /var/www/documentos2/src/routeTree.gen.ts src/routeTree.gen.ts
PATH="/root/.nvm/versions/node/v20.20.2/bin:$PATH" npm run build
sudo systemctl restart documentos
`