Back to Resources
Development

Next.js + Supabase Integration Guide: The Complete Setup for 2025

BI
Bilal Nazam
March 17, 20258 min read

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

nextjssupabaseintegrationapp-routertypescript

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