Pular para o conteúdo
🔧 AJUDA

Troubleshooting.

Erros que aparecem com frequência e como resolver. Estruturado por categoria. Se seu erro não estiver aqui, manda email pra contato@superdb.com.br e ele entra na próxima versão.

💡

Antes de mais nada: abre o DevTools → Network e vê o request que falhou. 90% dos bugs ficam óbvios vendo o status code, o response body e os headers. Tire um print disso antes de pedir ajuda.

Conexão & rede

"Failed to fetch" / "Network error"

Causa: client não conseguiu nem completar o TCP handshake. CORS bloqueado, URL errada, ou serviço offline.

Solução:

  1. Cheque NEXT_PUBLIC_SUPERDB_URL — deve ser https://api.superdb.com.br (sem trailing slash, sem path)
  2. Abra o Network tab; se o request é blocked by CORS, veja seção CORS
  3. Teste o endpoint cru: curl https://api.superdb.com.br/ -H "apikey: SUA_ANON_KEY" — sem prefixo /rest/v1/ (PostgREST do SuperDB serve na raiz). Se isso falha, o problema é seu network (proxy, firewall)
  4. Status do SuperDB: status.superdb.com.br

"ECONNREFUSED" em self-host

Causa: container do PostgREST não está respondendo. Pode estar caindo no boot por falta de schema/role.

Solução: docker logs superdb-rest. Erros típicos: role "authenticated" does not exist (rode db reset ou refaça init); connection refused a Postgres (DB ainda subindo — espere 30s).

"SSL error: certificate verify failed"

Causa: client com root CA desatualizado, ou DNS errado apontando pra IP sem cert.

Solução: atualize Node/curl pra versão recente. Se for self-host com cert auto-assinado, configure o client pra confiar ou use Let's Encrypt (recomendado).

Request timeout em produção

Causa: query lenta (full scan), connection pool cheio, ou rede congestionada.

Solução: rode EXPLAIN ANALYZE da query no Studio. Sem índice = full scan. Use CREATE INDEX e mede de novo.

Autenticação

"JWT expired"

Causa: access token caducou (1h) e o SDK não fez refresh — ou o user passou tempo offline.

Solução: await db.auth.refreshSession(). Se falhar, force re-login: await db.auth.signOut() → redirect pra /login. Verifique também se está usando o SDK oficial — implementações próprias podem esquecer de fazer refresh.

"Invalid JWT" / "signature is invalid"

Causa: JWT secret do projeto foi rotacionado, mas o token foi emitido com o antigo. Ou token foi adulterado.

Solução: force re-login. Em produção, rotacionar secret invalida todos os JWTs ativos — comunique users que vão precisar logar de novo.

"Invalid login credentials"

Causa: senha errada, email não confirmado, ou usuário banned.

Solução: ofereça "esqueci senha". Se email não foi confirmado: peça pra checar inbox (incluindo spam) e reenvie com db.auth.resend({ type: 'signup', email }).

"Email not confirmed"

Causa: setting Confirm email está ativo (default em Pro+) e o user nunca clicou no link.

Solução: reenvie a confirmação via db.auth.resend(). Ou, em dev, desligue Confirm email no Dashboard → Auth → Settings.

"User already registered"

Causa: tentando signup com email que já existe.

Solução: redirecione pro login. Se quer permitir múltiplos providers no mesmo email, habilite Link identical email em Auth Settings (cuidado com phishing).

Row Level Security

"new row violates row-level security policy"

Causa: INSERT/UPDATE bloqueado pelo WITH CHECK da policy. Provavelmente user_id da row não bate com auth.uid().

Solução: ou seta user_id = auth.uid() antes de inserir, ou use DEFAULT auth.uid() na coluna:

migration
ALTER TABLE tasks ALTER COLUMN user_id SET DEFAULT auth.uid();
-- Aí o client nem precisa passar user_id

"permission denied for table X"

Causa: a role do JWT (authenticated ou anon) não tem GRANT na tabela. RLS não é o problema — é o grant de Postgres.

Solução:

sql
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE tasks TO authenticated;
-- Pra leitura pública, adicione anon
GRANT SELECT ON TABLE posts TO anon;

SELECT retorna vazio mas tem dados

Causa #1: RLS está bloqueando — sua policy não inclui o user atual.

Causa #2: Está usando anon key onde precisava de JWT de user. Anon não tem auth.uid().

Solução: no Studio, rode SET ROLE authenticated; SET request.jwt.claims = '{"sub":"USER_ID"}'::jsonb; SELECT * FROM tasks; pra simular o que o user vê. Se aparece tudo na role postgres mas vazio em authenticated, é RLS.

RLS configurada mas retorna tudo pra todo mundo

Causa: esqueceu ALTER TABLE x ENABLE ROW LEVEL SECURITY. Sem isso, policies são ignoradas.

Solução: rode SELECT relname, relrowsecurity FROM pg_class WHERE relname = 'sua_tabela';relrowsecurity tem que ser t.

PostgREST / API REST

"Range not satisfiable" / 416

Causa: paginação pediu Range: 1000-1999 mas só existem 50 rows.

Solução: obtenha o Content-Range: 0-49/50 primeiro, calcule a página final. Ou use offset/limit do SDK que cuida disso.

"PGRST204: column X not found"

Causa: coluna não existe ou cache do schema está stale.

Solução: verifica spelling. Se a coluna existe mesmo, force reload do schema cache: NOTIFY pgrst, 'reload schema'; no SQL Editor.

"PGRST301: JWT expired"

Veja "JWT expired" acima.

Queries com count=exact ficam lentas

Causa: Postgres faz SELECT count(*) que escaneia a tabela inteira.

Solução: use count=estimated em tabelas grandes — usa estatísticas do pg_class, é instantâneo.

sdk
const { data, count } = await db
  .from('events')
  .select('*', { count: 'estimated' })  // ← em vez de 'exact'
  .limit(20)

Embed ?select=*,author:users(*) não funciona

Causa: não há FK entre posts.author_id e users.id. PostgREST precisa da FK pra inferir o join.

Solução: ALTER TABLE posts ADD CONSTRAINT posts_author_fk FOREIGN KEY (author_id) REFERENCES users(id);

Storage

"Bucket not found"

Causa: bucket não foi criado ou nome está errado (case-sensitive).

Solução: Studio → Storage → "New bucket". Bucket name é case-sensitive: Avatarsavatars.

Upload retorna 403

Causa: policy do bucket nega INSERT, ou bucket é público mas client tá enviando JWT vazio.

Solução: cheque policies em Storage → Bucket → Policies. Pra upload por usuário logado, a policy padrão é INSERT TO authenticated WITH CHECK (bucket_id = 'avatars').

Signed URL retorna 403 mesmo recém-criada

Causa: servidor com clock dessincronizado, ou TTL muito curto.

Solução: garante NTP no server (ntpdate). Aumente TTL pra 3600s pra teste.

"Payload too large" / 413

Causa: arquivo passa do limite do bucket (default 50MB no plano Free, 500MB Pro).

Solução: ajuste o limite em Bucket settings, ou use upload resumable (TUS) que aceita arquivos gigantes em chunks.

Realtime

Subscription criada mas eventos não chegam

Causa #1: tabela não está em publication supabase_realtime.

Causa #2: RLS está bloqueando — você não recebe eventos de rows que não consegue ler.

Solução: ALTER PUBLICATION supabase_realtime ADD TABLE tasks; + garante que a policy SELECT cobre o user.

WebSocket desconecta a cada 30s

Causa: proxy/load balancer com idle timeout curto, ou heartbeat desabilitado.

Solução: SDK manda heartbeat a cada 25s por default. Se está atrás de proxy, garante que proxy_read_timeout ≥ 60s no nginx.

"too many subscriptions"

Causa: excedeu o limite do plano (Free: 200 conn simultâneas).

Solução: consolide subscriptions (1 canal com filtros vs N canais), ou upgrade plano.

SMTP / Emails

"Email rate limit exceeded"

Causa: SMTP default tem limite de ~30 emails/dia (anti-abuse). Em produção, isso é insuficiente.

Solução: configure SMTP próprio (Resend, SendGrid, Postmark) em Dashboard → Auth → Email Settings. Free tier desses providers cobre milhares de emails.

Email chegando no spam

Causa: usando o SMTP default do SuperDB (compartilhado, sem SPF/DKIM no seu domínio).

Solução: use SMTP próprio com domínio verificado (SPF + DKIM + DMARC). Resend tem setup guiado em 10 min.

Variável {{ .ConfirmationURL }} não substitui

Causa: erro de sintaxe no template — case-sensitive e precisa do ponto.

Solução: use exatamente {{ .ConfirmationURL }} (Go template syntax). Veja todas as variáveis disponíveis em Auth → Email Templates.

CORS

"blocked by CORS policy"

Causa: origem do seu app (https://meu-app.com.br) não está na allowlist do projeto.

Solução: Dashboard → Settings → API → Allowed origins. Adicione com o protocolo: https://meu-app.com.br (não meu-app.com.br).

Funciona em prod mas não em localhost:3000

Causa: http://localhost:3000 não está na allowlist de dev.

Solução: adicione http://localhost:3000 (HTTP, não HTTPS) em Allowed origins. Pra Vite default: http://localhost:5173.

"CORS policy: credentials flag must be true"

Causa: SDK tentando enviar cookie mas server não tem Access-Control-Allow-Credentials: true.

Solução: esse header é setado automaticamente pelo SuperDB pra origens da allowlist. Se aparece, é porque a origem não está allowed — volte e adicione exata.

OAuth / providers

"Invalid redirect_uri"

Causa: URL de callback que o SuperDB envia ao Google/GitHub não está nas Authorized redirect URIs do provider.

Solução: no console do provider (ex: Google Cloud Console), adicione exatamente https://api.superdb.com.br/auth/v1/callback — byte a byte, sem trailing slash.

"OAuth provider not configured"

Causa: você chamou signInWithOAuth({ provider: 'google' }) mas não setou Client ID/Secret no Dashboard.

Solução: Dashboard → Auth → Providers → Google → ative + cole credenciais.

OAuth redireciona pra localhost em produção

Causa: setting Site URL aponta pra http://localhost:3000.

Solução: Dashboard → Auth → URL Configuration → Site URL = seu domínio de prod. Adicione localhost em Redirect URLs (allowlist) pra dev continuar funcionando.

MFA

"MFA challenge expired"

Causa: challenge tem TTL de 5 minutos. Usuário demorou demais entre challenge() e verify().

Solução: reinicie o fluxo — chame db.auth.mfa.challenge() de novo.

"Invalid TOTP code" mesmo com código certo

Causa #1: clock skew — celular e servidor com horário diferente em mais de 30s.

Causa #2: usou código de outro factor (você tem 2 enrolled).

Solução: sincronize horário do celular (auto). Liste factors enrolled: db.auth.mfa.listFactors() e mire no certo.

Não consigo desativar MFA — quero entrar mas perdi o app

Causa: esperado — desativar MFA requer prova de posse (recovery codes ou MFA atual).

Solução: use recovery codes (gerados no enroll). Se perdeu também: admin com service-role pode chamar db.auth.admin.deleteFactor(). Comunicação pelo email cadastrado é boa prática antes de fazer isso.

ℹ️

Não achou seu erro? Manda email pra contato@superdb.com.br com: (1) o erro exato, (2) o que você tava tentando fazer, (3) print do Network tab. A gente responde no mesmo dia útil e adiciona aqui.

Essa página ajudou?