The Right Way to Use Supabase with Next.js App Router
Next.js App Router introduced Server Components, which changed how authentication and data fetching work. The old approach of a single Supabase client doesn't work correctly anymore. Here's the complete, correct setup for 2025.
Installation
npm install @supabase/supabase-js @supabase/ssr
Setting Up Multiple Clients
In the App Router, you need different Supabase clients for different contexts:
Server Component Client
// lib/supabase/server.ts
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)
)
},
},
}
)
}
Client Component Client
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
Setting Up Middleware for Auth
// middleware.ts
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 }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({ request })
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
const { data: { user } } = await supabase.auth.getUser()
if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return supabaseResponse
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
Using Supabase in Server Components
// 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')
const { data: profile } = await supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single()
return Hello, {profile?.full_name}
}
Type-Safe Queries with Generated Types
Generate TypeScript types directly from your Supabase schema:
npx supabase gen types typescript --project-id your-project-ref > types/supabase.ts
// Now your queries are fully typed
import type { Database } from '@/types/supabase'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient<Database>(URL, KEY)
// TypeScript knows the shape of 'profiles'
const { data } = await supabase.from('profiles').select('*')
Route Handlers with Supabase
// app/api/posts/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET() {
const supabase = await createClient()
const { data, error } = await supabase.from('posts').select('*')
if (error) return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json(data)
}
Migrating from Lovable Cloud and need your Next.js app connected to Supabase properly? We handle the full migration and integration.
Categorized In
Frequently Asked Questions
Why do I need two Supabase clients in Next.js App Router?
Server and client components run in different environments. The server client reads/writes cookies via the next/headers API, while the browser client uses document.cookie. Using the wrong client causes auth to break.
How do I handle Supabase auth in Next.js middleware?
Use @supabase/ssr's createServerClient in your middleware.ts, call supabase.auth.getUser() to check auth status, and redirect unauthenticated users. Always use getUser() not getSession() as it validates the token server-side.
Can I use Supabase Realtime in Next.js Server Components?
No. Realtime subscriptions require WebSocket connections which only work in client components. Use 'use client' for any component that subscribes to real-time changes.
Share This Intelligence
Start Your Migration Strategy
Don't let vendor lock-in stifle your growth. Get a professional roadmap to Supabase excellence today.
Free Architectural Audit