📄 SKILL.md

← Vault

name: supabase-react-auth-black-screen

description: Debug and fix "tela preta" (black screen) hang on login in Supabase + React apps — when RequireAuth gets stuck in loading state.

category: devops


Supabase React Auth — Tela Preta (Black Screen) Debug

Problem

User authenticates with Google OAuth successfully, but app shows a black/blank screen indefinitely. Browser console shows no errors (or only favicon.ico 404). The RequireAuth component is stuck in loading=true, user=null state.

Root Cause Pattern

In useAuth.ts (or similar AuthProvider), the loadProfile() function makes Supabase queries (profiles, user_roles) that are silently blocked by RLS (Row Level Security) with the anon key. Because there's no error handling or timeout, profileLoading stays true forever → RequireAuth stays in loading state → blank screen.

`typescript

// ❌ BROKEN — no error handling, no timeout

async function loadProfile(uid: string) {

setProfileLoading(true);

const [{ data: rs }, { data: profile }] = await Promise.all([

supabase.from("user_roles").select("role").eq("user_id", uid), // RLS blocks → hangs forever

supabase.from("profiles").select("full_name").eq("id", uid).maybeSingle(),

]);

// never reaches here if RLS silently blocks

setProfileLoading(false);

}

`

Also common: wrong field name — select("role") when the column is role_id.

Diagnosis Steps

1. Browser console — look for [useAuth] loadProfile falhou: messages

2. Network tab — queries to profiles or user_roles return empty [] or 403 → RLS blocking

3. Supabase dashboard — verify profiles table has the user's row; check allowed_emails if using that pattern

4. Wrong field name — check if select("role") should be select("role_id")

Fix (useAuth.ts)

Add try/catch/finally with an 8-second timeout:

`typescript

async function loadProfile(uid: string) {

setProfileLoading(true);

try {

// 8s timeout prevents infinite hang on RLS block

const timeout = new Promise((_, reject) =>

setTimeout(() => reject(new Error("timeout")), 8000)

);

const [{ data: rs, error: rsErr }, { data: profile, error: profErr }] = await Promise.all([

supabase.from("user_roles").select("role_id").eq("user_id", uid),

supabase.from("profiles").select("full_name").eq("id", uid).maybeSingle(),

timeout,

]);

if (!mounted) return;

console.warn("[useAuth] user_roles:", rsErr?.message ?? "ok", "profiles:", profErr?.message ?? "ok");

setRoles((rs?.map((r) => r.role_id as AppRole)) ?? []);

setFullName(profile?.full_name ?? "");

} catch (e) {

if (!mounted) return;

console.error("[useAuth] loadProfile falhou:", e);

setRoles([]);

setFullName("");

} finally {

if (mounted) setProfileLoading(false);

}

}

`

Rebuild & Redeploy (Documents app example)

`bash

On VPS

cd /var/www/documentos/deploy-vps/app && npm run build

systemctl restart documentos

`

Alternative Root Cause: Browser Cache with Stale JS

If console shows errors from files that don't exist on server (e.g., adm.usuarios-CfN-Fu9m.js 404), it's a browser cache problem — old JavaScript bundle is cached but server has a newer build.

Fix: Ctrl+Shift+R (hard refresh) or DevTools → Network → "Disable cache" → reload.

Key Distinction Table

ErrorCauseFix
-------------------
GET /auth/v1/admin/users 403 from file that doesn't exist on serverBrowser cacheCtrl+Shift+R
Black screen, no console errors, no network activityprofileLoading never resolvesAdd timeout + error handling to loadProfile
allowed_emails returns empty via REST APIRLS blocks anon key readsCheck table directly in Supabase dashboard
RAISE EXCEPTION 'Email não autorizado' in Supabase logshandle_new_user trigger fired but email not in allowed_emailsAdd email to allowed_emails table

Projects Using This Pattern

Key Insight

The blank screen is RequireAuth rendering

while loading=true && !user. The loading flag is controlled by profileLoading from loadProfile(). If that function hangs (RLS block, wrong query, network timeout), the user is stuck forever. Always wrap Supabase profile queries with timeout + error handling.