Every mechanism this domain has covered works exactly as described — and still produces genuinely confusing failures in three specific, recurring situations, each worth knowing by name before hitting it cold in a real project.
3 min read
Vercel Serverless Function, invoked 50 times concurrently
→ each invocation opens its OWN Postgres connection
→ Postgres has a hard connection limit (often a few hundred)
→ exceeded quickly under real concurrent serverless traffic
Vercel's own domain already established that a Serverless Function can scale to many concurrent invocations — each one, left to its own devices, would open its own direct Postgres connection, and Postgres's hard connection ceiling gets exhausted far faster than a traditional, fixed-size server pool ever would. Supabase's connection pooler (Supavisor, built on the same idea as the standalone tool PgBouncer) sits between serverless functions and Postgres itself, multiplexing many logical client connections onto a smaller, stable pool of actual database connections — using the pooler's connection string (not the direct one) from any serverless environment is the real, specific fix.
service_role key bypasses RLS entirely — a real, easy-to-misuse power// service_role key: bypasses EVERY RLS policy — use ONLY server-side, NEVER in a browser
const supabaseAdmin = createClient(url, SERVICE_ROLE_KEY);Every RLS policy this domain has covered (lesson 2 onward) is checked against the authenticated user's identity — the service_role key instead authenticates as Postgres's own superuser-equivalent role, which RLS policies don't apply to at all by design, intended specifically for trusted server-side operations that genuinely need to bypass per-user access control (an admin dashboard, a batch job). Shipping this key to a browser — even accidentally, the exact NEXT_PUBLIC_ mistake Vercel's own domain named directly — hands any visitor full, unrestricted read/write access to every table in the database, with every RLS policy this entire domain is built around rendered completely irrelevant.
-- RLS policy says: users CAN see their own rows
CREATE POLICY "..." ON documents FOR SELECT USING (auth.uid() = owner_id);
-- but if the underlying Postgres ROLE was never granted SELECT on this
-- table at all, the query still fails — RLS and GRANT are TWO SEPARATE
-- permission layers, and BOTH have to allow the access
GRANT SELECT ON documents TO authenticated;RLS policies and Postgres's own underlying GRANT-based permission system are genuinely separate mechanisms that both apply — a correct RLS policy on a table the authenticated role was never actually granted SELECT on still denies access, confusingly, even though the policy itself looks entirely correct. This is exactly the kind of gap real production setups hit when a table is created through a path that skips Supabase's own default grants (a raw SQL migration run outside the CLI's usual flow, say) — the fix is explicit: confirm both the RLS policy and the underlying grant actually permit the access, rather than assuming a correct-looking policy alone is sufficient.
Each of these is a case where Supabase's usual, convenient default (automatic pooling, automatic grants, the dashboard's default key usage) quietly stops applying — a serverless environment, a deliberately privileged key, or a table created outside the normal path. Recognizing "this situation is one of the exceptions to the usual default" is the actual skill, more valuable than memorizing any one fix in isolation.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why can serverless functions exhaust Postgres's connection limit without a connection pooler?
2. Why is shipping the service_role key to a browser catastrophic?
3. Why can a correctly-written RLS policy still deny access unexpectedly?
Supabase