Execute choose and implement Supabase validated architecture blueprints for different scales. Use when designing new Supabase integrations, choosing between...
Every Supabase createClient configuration turns on two questions: where the client runs (browser vs server) and which key it uses (anon respects RLS; service_role bypasses it). This skill supplies production-ready patterns for five architectures โ Next.js SSR, SPA, Mobile, Serverless Edge Functions, and Multi-tenant isolation.
@supabase/supabase-js v2+ installed@supabase/ssr package for Next.js SSR (v0.5+)anon key, and service_role keysupabase gen types typescript)Pick the architecture that matches the target stack, then follow the linked walkthrough for the full, copy-ready client setup.
| Architecture | Client(s) | Key | Session storage |
|---|---|---|---|
| Next.js SSR | Server (cookies) + browser + admin | anon in-request, service_role server-only |
HTTP cookies |
| SPA (React/Vue) | Single browser client | anon only |
localStorage |
| Mobile (React Native) | Single native client | anon only |
AsyncStorage |
| Serverless (Edge Functions) | Per-request client | anon (forwarded JWT) or service_role |
none (stateless) |
| Multi-tenant | Any of the above | anon + RLS, or schema-per-tenant |
per host pattern |
Next.js App Router needs two separate clients: a server client that reads/writes auth cookies via @supabase/ssr, and a browser client for client components. A third service_role admin client is used only in Server Actions/Route Handlers and must never reach the browser. The browser client is the minimal skeleton:
// lib/supabase/client.ts
'use client'
import { createBrowserClient } from '@supabase/ssr'
import type { Database } from '../database.types'
export function createSupabaseBrowser() {
return createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // anon key only โ respects RLS
)
}
Full walkthrough โ server client, admin client, middleware session refresh, server component usage, Server Actions, and the OAuth callback route: Next.js SSR patterns.
SPAs and mobile apps both use a single browser/native client with the anon key; all authorization is enforced by RLS and the service_role key is never bundled. They differ only in session storage (localStorage for SPA, AsyncStorage for mobile) and OAuth handling (URL detection for SPA, deep links for mobile). Minimal SPA skeleton:
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
export const supabase = createClient<Database>(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
{ auth: { autoRefreshToken: true, persistSession: true, detectSessionInUrl: true } }
)
Full walkthrough โ SPA singleton + auth-state listener, React Query hooks, React Native AsyncStorage client, mobile OAuth with deep links, and Expo app.json config: SPA and mobile patterns.
Edge Functions create a per-request client from the forwarded JWT (stateless, no session persistence), escalating to service_role only for privileged operations. Multi-tenant isolation is either RLS-based (a tenant_members lookup gates every row) or schema-per-tenant.
Full walkthrough โ Edge Function per-request clients, admin escalation, RLS multi-tenant isolation, and tenant-scoped SDK queries: serverless and multi-tenant patterns.
service_role for privileged operationsAsyncStorage, deep link OAuth, and in-app browsertenant_members lookup and scoped queries| Issue | Cause | Solution |
|---|---|---|
AuthSessionMissingError in Server Component |
Cookies not passed to Supabase client | Use createServerClient from @supabase/ssr with cookie handlers |
| OAuth redirect fails in React Native | Missing deep link scheme | Add scheme to app.json and configure Supabase redirect URL |
service_role key in client bundle |
Wrong env var prefix (NEXT_PUBLIC_) |
Remove NEXT_PUBLIC_ prefix; only server code should access it |
| Multi-tenant data leak | Missing RLS policy or missing tenant_id filter |
Verify RLS is enabled and policies check tenant_members |
Edge Function auth.getUser() returns null |
Missing Authorization header | Forward user's JWT from the client call |
| Session not persisting on mobile | AsyncStorage not configured |
Pass AsyncStorage in auth config; ensure package is installed |
Verify tenant isolation by impersonating a JWT and confirming RLS scopes the result:
-- Test that RLS properly isolates tenants
SET request.jwt.claims = '{"sub": "user-uuid-1"}';
-- Should only return projects for user-uuid-1's tenant
SELECT * FROM public.projects;
More runnable examples โ the Next.js OAuth callback route and further end-to-end flows: Next.js SSR patterns and examples.
After wiring the client for your architecture, review supabase-known-pitfalls for common mistakes and anti-patterns to avoid, then generate database types with supabase gen types typescript and enable RLS on every table before shipping.