Execute choose and implement Supabase validated architecture blueprints for different scales.
Use when designing new Supabase integrations, choosing between monolith/service/microservice
architectures, or planning migration paths for Supabase applications.
Trigger with phrases like "supabase architecture", "supabase blueprint",
"how to structure supabase", "supabase project layout", "supabase microservice".
Different application architectures require fundamentally different Supabase createClient configurations. The critical distinction is where the client runs (browser vs server) and which key it uses (anon key respects RLS; service_role bypasses it). This skill provides production-ready patterns for five architectures: Next.js SSR (server components with service_role, client components with anon), SPA (React/Vue with browser-only client), Mobile (React Native with deep link auth), Serverless (Edge Functions with per-request clients), and Multi-tenant (RLS-based or schema-per-tenant isolation).
Prerequisites
@supabase/supabase-js v2+ installed
@supabase/ssr package for Next.js SSR (v0.5+)
Supabase project with URL, anon key, and service role key
TypeScript project with generated database types (supabase gen types typescript)
For mobile: React Native with Expo or bare workflow
Instructions
Step 1 — Next.js SSR (App Router with Server and Client Components)
Next.js App Router requires two separate clients: a server-side client using cookies for auth (with @supabase/ssr) and a browser client for client components. Never expose to the client.
service_role
Server-Side Client (for Server Components, Route Handlers, Server Actions)
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import type { Database } from '../database.types'
export async function createSupabaseServer() {
const cookieStore = await cookies()
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// Called from Server Component — cookies are read-only
}
},
},
}
)
}
// Admin client for server-only operations (bypasses RLS)
// NEVER import this in client components or expose to the browser
import { createClient } from '@supabase/supabase-js'
export function createSupabaseAdmin() {
return createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // NOT NEXT_PUBLIC_ — server only
{
auth: { autoRefreshToken: false, persistSession: false },
}
)
}
Client-Side Client (for Client Components)
// lib/supabase/client.ts
'use client'
import { createBrowserClient } from '@supabase/ssr'
import type { Database } from '../database.types'
let client: ReturnType<typeof createBrowserClient<Database>> | null = null
export function createSupabaseBrowser() {
if (client) return client
client = createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // anon key only — respects RLS
)
return client
}
Step 3 — Serverless (Edge Functions) and Multi-Tenant
See serverless and multi-tenant patterns for Edge Function per-request clients, admin operation escalation, RLS-based multi-tenant isolation with schema, and tenant-scoped SDK queries.
Output
Next.js SSR setup with server client (cookies-based auth), browser client, and middleware
Server Actions using admin client with service_role for privileged operations
SPA pattern with singleton client, React Query integration, and auth state listener
React Native setup with AsyncStorage, deep link OAuth, and in-app browser
Edge Function patterns for per-request auth and admin escalation
Multi-tenant RLS isolation with tenant_members lookup and scoped queries
Decision matrix for choosing the right architecture per stack
Error Handling
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
Examples
Test Auth Flow End-to-End (Next.js)
// app/auth/callback/route.ts
import { createSupabaseServer } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const code = searchParams.get('code')
if (code) {
const supabase = await createSupabaseServer()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (error) {
return NextResponse.redirect(new URL('/login?error=auth_failed', request.url))
}
}
return NextResponse.redirect(new URL('/dashboard', request.url))
}
Verify Tenant Isolation
-- 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;