Why Supabase + Next.js?
Supabase has emerged as a powerful open-source alternative to Firebase, built on PostgreSQL and offering a comprehensive backend-as-a-service platform. When combined with Next.js, developers get a seamless full-stack development experience that leverages the best of both worlds: Next.js's React framework capabilities and Supabase's managed backend services.
This guide covers the essential aspects of integrating Supabase with Next.js, from initial setup to advanced authentication patterns using the App Router. Whether you're building a startup MVP or an enterprise application, this integration provides the foundation for scalable, secure full-stack applications.
Key Benefits
- PostgreSQL Foundation: Industry-standard database with full SQL capabilities
- Native TypeScript Support: End-to-end type safety across client and server
- Server-Side Rendering: Optimized for Next.js App Router with cookie-based sessions
- Real-Time Capabilities: Live data subscriptions and presence features
- Open-Source Flexibility: No vendor lock-in with self-hosting options available
The Evolution from Firebase
For teams considering a backend-as-a-service solution, Supabase offers a compelling alternative to Firebase with its PostgreSQL foundation. Unlike Firebase's NoSQL document store, Supabase provides full SQL capabilities, making it ideal for applications requiring complex queries, relationships, and data portability. The active open-source community and transparent roadmap ensure continuous improvement and feature additions.
For teams exploring backend-as-a-service options, our Supabase vs Firebase comparison provides an in-depth analysis of key differences and use cases.
The four key areas of Supabase + Next.js integration
Client Setup
Install and configure @supabase/supabase-js and @supabase/ssr packages for optimal Next.js integration with proper client/server separation.
Server Components
Leverage server-side database access with proper cookie handling, RLS integration, and type-safe queries.
Middleware
Implement session refresh and route protection with Next.js middleware for seamless auth state management.
Auth Helpers
Use cookie-based authentication helpers for seamless auth state management across server and client boundaries.
Client Setup
Installation
npm install @supabase/supabase-js @supabase/ssr
The @supabase/ssr package is essential for Next.js applications as it provides utilities for handling authentication state across server and client boundaries, including cookie management for server-side rendering. The @supabase/supabase-js package provides the core Supabase client functionality.
Environment Variables
Create a .env.local file in your project root:
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
You can find these credentials in your Supabase dashboard under Project Settings > API. The URL should follow the format https://your-project-id.supabase.co and the publishable key (also called the anon key) is safe to expose in client-side code.
Creating the Browser Client
For Client Components, use the browser client to handle user interactions and client-side operations:
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)
}
Store this helper in a dedicated file like lib/supabase/client.ts and import it wherever you need client-side Supabase access. This pattern ensures consistent client initialization across your application.
For teams building modern web applications, combining Supabase with professional web development services ensures proper architecture and scalable implementation from the start.
Server Components Integration
Understanding Client Types
Next.js requires two types of Supabase clients because of its server/client component architecture:
- Client Component Client: For code running in the browser, handling user interactions and client-side operations
- Server Component Client: For code running on the server, with access to cookies and proper session handling
This separation is crucial for maintaining authentication state across server-side rendering requests and keeping sensitive operations server-side.
Server Component Client Setup
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_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
)
}
Data Fetching in Server Components
export default async function Page() {
const supabase = await createClient()
const { data: posts, error } = await supabase.from('posts').select('*')
if (error) {
console.error('Error fetching posts:', error)
return <div>Error loading content</div>
}
return (
<ul>
{posts?.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Server Components can directly access the database with full Row Level Security policy enforcement, providing secure data access without exposing credentials to the client.
Middleware for Authentication
Middleware is crucial for handling authentication in Next.js with Supabase. It manages session refresh, route protection, and cookie synchronization between server and client. Without proper middleware, sessions may expire during page navigation and users could unexpectedly lose authentication state.
Creating Auth Middleware
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_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
request.cookies.set(name, value)
)
supabaseResponse = NextResponse.next({ request })
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// Refresh session if expired - this keeps users logged in
await supabase.auth.getUser()
return supabaseResponse
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico).*)',
],
}
Protected Routes Pattern
To protect routes from unauthenticated users, extend the middleware to check for valid sessions and redirect accordingly:
// Add this check after supabase.auth.getUser()
const {
data: { user },
} = await supabase.auth.getUser()
const protectedRoutes = ['/dashboard', '/profile', '/settings']
const isProtectedRoute = protectedRoutes.some(route =>
request.nextUrl.pathname.startsWith(route)
)
if (isProtectedRoute && !user) {
const redirectUrl = request.nextUrl.clone()
redirectUrl.pathname = '/login'
redirectUrl.searchParams.set('redirectTo', request.nextUrl.pathname)
return NextResponse.redirect(redirectUrl)
}
This pattern ensures authenticated users can access protected areas while unauthenticated users are redirected to login with the original destination preserved for post-login redirect.
Auth Helpers and Session Management
Understanding Auth Helpers
Auth helpers provide cookie-based session storage and automatic token refresh, solving the challenges of maintaining authentication state across server-side rendering requests. The @supabase/ssr package handles the complex work of synchronizing session state between the browser and server.
Sign-In Implementation
'use client'
import { createClient } from '@/lib/supabase/client'
export function SignInForm() {
const handleSignIn = async (email: string, password: string) => {
const supabase = createClient()
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
console.error('Sign in error:', error.message)
return { error: error.message }
}
return { success: true }
}
return <form onSubmit={handleSignIn}>{/* form fields */}</form>
}
Sign-Up with Additional Data
Supabase allows you to include additional user metadata during registration:
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
full_name: formData.get('full_name'),
avatar_url: avatarUrl,
},
},
})
This metadata is stored in the auth.users table and can be accessed through the user object in authentication operations. For more complex user profiles, consider creating a separate profiles table with a foreign key reference.
Sign-Out Implementation
// In a Server Action or Server Component
export async function signOut() {
const supabase = await createClient()
await supabase.auth.signOut()
// Redirect to home or login page
}
Always ensure sign-out clears cookies properly. The server-side sign-out handles cookie cleanup automatically through the server client's configuration.
Server Actions for Auth
Modern Next.js applications can leverage Server Actions for type-safe, secure authentication operations without exposing client-side code.
Login with Server Actions
'use server'
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
export async function login(formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
const supabase = await createClient()
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
return { error: error.message }
}
redirect('/dashboard')
}
Benefits of Server Actions for authentication:
- Form submissions: Native form handling with built-in validation
- Type-safe operations: Full TypeScript inference for auth methods
- Native redirect: Built-in redirect handling without client-side router manipulation
- Server-side error: Errors occur server-side with full context
Register with Server Actions
export async function register(formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
const fullName = formData.get('full_name') as string
const supabase = await createClient()
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: { full_name: fullName },
},
})
if (error) {
return { error: error.message }
}
// Check if email confirmation is required
if (data.session === null) {
return { message: 'Please check your email for confirmation' }
}
redirect('/dashboard')
}
Server Actions provide a clean separation between client UI and authentication logic, keeping sensitive operations secure on the server.
Client Component Auth Patterns
Auth State Management
Client Components need to react to authentication state changes. Use the onAuthStateChange listener to keep your UI in sync with the user's session:
'use client'
import { createClient } from '@/lib/supabase/client'
import { useEffect, useState } from 'react'
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const supabase = createClient()
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
setLoading(false)
})
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user ?? null)
setLoading(false)
})
return () => subscription.unsubscribe()
}, [])
if (loading) {
return <div>Loading...</div>
}
return <>{children}</>
}
OAuth Authentication
Supabase supports multiple OAuth providers including Google, GitHub, Facebook, and more. Configure them in your Supabase dashboard under Authentication > Providers:
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://yourapp.com/auth/callback',
scopes: 'email profile',
},
})
Handling OAuth Callbacks
Create a callback route to handle the OAuth redirect:
// app/auth/callback/route.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
if (code) {
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{ cookies: { getAll: () => cookieStore.getAll() } }
)
await supabase.auth.exchangeCodeForSession(code)
}
return NextResponse.redirect(`${origin}/dashboard`)
}
Configure redirect URLs in your Supabase project under Authentication > URL Configuration to match your application's domains.
Best Practices
Security Considerations
- Never expose service role key on client: The service role bypasses RLS and should only be used in server-side code, typically in Server Actions or API routes for administrative tasks
- Enable RLS policies: Always enable Row Level Security on your tables to ensure users can only access their own data
- Input validation: Validate all user inputs before database operations using libraries like Zod
- CSRF protection: Supabase handles this automatically for API requests, but be cautious with custom implementations
- Session timeout: Configure appropriate session expiration times in Supabase authentication settings
Performance Optimization
- Leverage Server Components: Minimize client-side auth calls by fetching data in Server Components whenever possible
- Use React cache(): For repeated client creation in the same request, cache the client instance
- Implement loading states: Use Next.js streaming and Suspense boundaries for smooth user experiences
- Lazy load auth-dependent components: Only load components that require authentication when needed
For applications requiring advanced AI-powered features, integrating Supabase with AI automation services can unlock powerful capabilities while maintaining secure data access patterns.
TypeScript Integration
Generate types from your Supabase schema for type-safe database queries:
npx supabase gen types typescript --project-id "your-project-id" > types/database.types.ts
Use these types for type-safe database operations:
import { Database } from '@/types/database.types'
const client = createClient<Database>()
// Now autocomplete works for table names and columns
const { data } = await client.from('posts').select('title, content')
Error Handling Pattern
Implement consistent error handling across your application:
try {
const { data, error } = await supabase.from('table').select('*')
if (error) throw error
return data
} catch (error) {
console.error('Database error:', error)
return null
}
Supabase returns errors with consistent structures including message, code, and details properties for debugging.
Frequently Asked Questions
What's the difference between @supabase/supabase-js and @supabase/ssr?
@supabase/supabase-js is the core client library providing basic database and auth operations, while @supabase/ssr provides Next.js-specific utilities for handling cookies, session refresh, and server-side rendering. Use both together for full Next.js support.
How do I handle session persistence across server requests?
Use the @supabase/ssr package which handles cookie-based session storage. Configure your server client with cookie handlers to read and write session cookies automatically. Middleware ensures sessions stay refreshed.
Can I use Supabase with Next.js Pages Router?
Yes, but the @supabase/ssr package is designed for the App Router. For Pages Router, you'll need to use different patterns with getServerSideProps and manage cookies manually. Consider migrating to App Router for better Supabase integration.
How do I protect routes from unauthenticated users?
Use Next.js middleware to check authentication state before allowing access. Redirect unauthenticated users to a login page. Store the original URL to redirect back after successful login using search parameters.
What happens if a user's session expires?
The @supabase/ssr middleware automatically attempts to refresh expired sessions using the refresh token stored in cookies. If refresh fails due to logout or token revocation, users are redirected to log in again.
Conclusion
Supabase + Next.js represents a powerful combination for modern web development. By understanding the distinction between client and server clients, implementing proper middleware for session management, and following authentication best practices, you can build secure, scalable applications with excellent developer experience.
The @supabase/ssr package specifically addresses the challenges of server-side rendering and cookie-based authentication, making this integration smoother than ever before. For teams transitioning from Firebase or building new applications, this stack offers the familiarity of SQL, the convenience of a managed backend, and the flexibility of an open-source solution.
Start by setting up your client and server clients, implement middleware for session management, and progressively add authentication and data features as your application grows. The modular nature of this integration allows you to adopt features incrementally while maintaining a solid security foundation.
For more advanced features, explore Supabase Realtime for live data updates and Supabase Edge Functions for serverless backend logic.
If you need expert guidance implementing Supabase with Next.js for your project, our web development team can help architect and build a scalable solution tailored to your requirements.
Supabase Getting Started
Learn the basics of Supabase and how to set up your first project with database, authentication, and storage services.
Learn moreSupabase Authentication
Deep dive into Supabase auth including email/password, OAuth, magic links, and multi-factor authentication strategies.
Learn moreSupabase Database
PostgreSQL fundamentals, schema design, migrations, and performance optimization for Supabase projects.
Learn moreSources
- Supabase: Use Supabase with Next.js - Official Supabase documentation for Next.js quickstart and template setup
- Supabase: Creating a Supabase client for SSR - Primary source for SSR client configuration, cookies, and auth helpers
- Supabase: Next.js Auth Quickstart - Next.js-specific authentication documentation
- Zestminds: How to Set Up Supabase Auth in Next.js - Third-party implementation guide