Crash Courses · Masters
Wiring Next.js + Supabase on Vercel
Auth, data fetching, and environment variables connected end-to-end — the exact setup that works in production.
15 min read+150 XP on completionCert: Ship It Crash Course
Tap any word in the text below to start reading from there.
Wiring Next.js + Supabase on Vercel
The Full Auth Architecture
Browser → Next.js Middleware (check session cookie)
↓ authenticated ↓ unauthenticated
Protected page Redirect → /login
↓
Server Component (read session, fetch user data)
↓
Supabase (RLS uses auth.uid() from session)---
Step 1: Install Dependencies
npm install @supabase/ssr @supabase/supabase-js---
Step 2: Create Supabase Clients
// src/lib/supabase/client.ts — browser client
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}// src/lib/supabase/server.ts — server client (reads cookies)
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() { return cookieStore.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
}---
Step 3: Middleware — Protect Routes
// middleware.ts (root of project)
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() { return request.cookies.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
const { data: { user } } = await supabase.auth.getUser()
const path = request.nextUrl.pathname
// Redirect unauthenticated users away from protected routes
if (!user && path.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Redirect authenticated users away from auth pages
if (user && (path === '/login' || path === '/signup')) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return supabaseResponse
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
}---
Step 4: Server Component with Auth
// src/app/dashboard/page.tsx
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) redirect('/login')
// RLS uses auth.uid() = user.id — only fetches this user's courses
const { data: courses } = await supabase
.from('courses')
.select('*')
.order('created_at', { ascending: false })
return (
<main>
<h1>Welcome, {user.email}</h1>
<ul>
{courses?.map(c => <li key={c.id}>{c.title}</li>)}
</ul>
</main>
)
}---
Vercel Deployment Checklist
Before pushing:
# 1. Type check
npx tsc --noEmit
# 2. Test build locally
npm run build
# 3. Check .gitignore includes .env.local
cat .gitignore | grep env
# 4. Verify Vercel has all env vars set
# Dashboard → Settings → Environment VariablesReading progress
0% read
In this module
Middleware
Server Action
Session Cookie
Protected Route
0%