# @supabase/ssr
`@supabase/ssr` is a framework-agnostic library for using the Supabase JavaScript client in server-side rendering (SSR) frameworks. It provides unified cookie-based session management that works consistently across Next.js, Remix, SvelteKit, and other SSR environments. This package consolidates and replaces the deprecated `@supabase/auth-helpers-*` packages.
The library handles the complexity of persisting authentication sessions via cookies, including automatic cookie chunking for large session payloads, Base64-URL encoding for cross-browser compatibility, and proper session refresh handling. It provides two main client factories: `createBrowserClient` for client-side usage with automatic `document.cookie` integration, and `createServerClient` for server-side middleware, routes, and components with configurable cookie handlers.
## createBrowserClient
Creates a Supabase client for browser environments with automatic cookie management using `document.cookie`. Implements a singleton pattern by default to prevent multiple client instances.
```typescript
import { createBrowserClient } from '@supabase/ssr'
// Basic usage - uses document.cookie automatically
const supabase = createBrowserClient(
'https://your-project.supabase.co',
'your-anon-key'
)
// Get user session
const { data: { session }, error } = await supabase.auth.getSession()
if (session) {
console.log('User:', session.user.email)
}
// Sign in with email/password
const { data, error: signInError } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password123'
})
// Custom cookie handling (optional)
const supabaseCustom = createBrowserClient(
'https://your-project.supabase.co',
'your-anon-key',
{
cookies: {
getAll: () => {
// Return all cookies as { name, value }[]
return document.cookie.split(';').map(c => {
const [name, ...rest] = c.trim().split('=')
return { name, value: rest.join('=') }
})
},
setAll: (cookies) => {
// Set multiple cookies at once
cookies.forEach(({ name, value, options }) => {
document.cookie = `${name}=${value}; path=${options.path}; max-age=${options.maxAge}`
})
}
},
isSingleton: true // Default: true in browser
}
)
```
## createServerClient
Creates a Supabase client for server-side environments (middleware, routes, server components). Requires explicit cookie handlers and supports lazy session initialization.
```typescript
import { createServerClient } from '@supabase/ssr'
// Next.js Middleware example
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
const response = NextResponse.next()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => {
return request.cookies.getAll().map(cookie => ({
name: cookie.name,
value: cookie.value
}))
},
setAll: (cookies) => {
cookies.forEach(({ name, value, options }) => {
response.cookies.set(name, value, options)
})
}
}
}
)
// Refresh session if expired - required for Server Components
const { data: { user } } = await supabase.auth.getUser()
// Protect routes
if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return response
}
// Next.js Server Component example
// app/page.tsx
import { cookies } from 'next/headers'
import { createServerClient } from '@supabase/ssr'
export default async function Page() {
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => cookieStore.getAll(),
// setAll omitted - cookies cannot be set from Server Components
}
}
)
const { data: { user } } = await supabase.auth.getUser()
return
Hello {user?.email}
}
// Next.js Route Handler example
// app/api/user/route.ts
import { cookies } from 'next/headers'
import { createServerClient } from '@supabase/ssr'
import { NextResponse } from 'next/server'
export async function GET() {
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options)
})
}
}
}
)
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
return NextResponse.json({ user })
}
```
## Cookie Configuration Options
Configure cookie behavior including custom names, encoding strategies, and serialization options.
```typescript
import { createServerClient, createBrowserClient } from '@supabase/ssr'
// Custom cookie options
const supabase = createServerClient(
'https://your-project.supabase.co',
'your-anon-key',
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cookies) => {
cookies.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options)
})
}
},
// Custom cookie name (default: 'sb--auth-token')
cookieOptions: {
name: 'my-app-auth',
path: '/',
sameSite: 'lax',
httpOnly: false,
maxAge: 60 * 60 * 24 * 400 // 400 days
},
// Encoding: 'base64url' (default) or 'raw'
cookieEncoding: 'base64url'
}
)
// Tokens-only encoding (experimental) - stores only access/refresh tokens in cookies
// Reduces cookie size by storing user object in localStorage/memory
const supabaseBrowser = createBrowserClient(
'https://your-project.supabase.co',
'your-anon-key',
{
cookies: {
encode: 'tokens-only', // Experimental feature
getAll: () => document.cookie.split(';').map(c => {
const [name, ...v] = c.trim().split('=')
return { name, value: v.join('=') }
}),
setAll: (cookies) => {
cookies.forEach(({ name, value, options }) => {
document.cookie = `${name}=${value}; path=${options.path}; max-age=${options.maxAge}`
})
}
}
}
)
```
## parseCookieHeader
Utility function to parse the `Cookie` HTTP header into an array of name-value objects.
```typescript
import { parseCookieHeader } from '@supabase/ssr'
// Parse Cookie header from HTTP request
const cookieHeader = 'sb-auth-token=abc123; theme=dark; session_id=xyz789'
const cookies = parseCookieHeader(cookieHeader)
console.log(cookies)
// Output:
// [
// { name: 'sb-auth-token', value: 'abc123' },
// { name: 'theme', value: 'dark' },
// { name: 'session_id', value: 'xyz789' }
// ]
// Use with createServerClient in generic HTTP handlers
import { createServerClient } from '@supabase/ssr'
function handleRequest(req, res) {
const cookies = parseCookieHeader(req.headers.cookie || '')
const supabase = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{
cookies: {
getAll: () => cookies,
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) => {
res.setHeader('Set-Cookie',
`${name}=${value}; Path=${options.path}; Max-Age=${options.maxAge}; SameSite=${options.sameSite}`
)
})
}
}
}
)
return supabase
}
```
## serializeCookieHeader
Utility function to serialize a cookie into a valid `Set-Cookie` header string.
```typescript
import { serializeCookieHeader } from '@supabase/ssr'
// Create Set-Cookie header
const setCookieHeader = serializeCookieHeader(
'session',
'abc123',
{
path: '/',
maxAge: 60 * 60 * 24 * 7, // 7 days
sameSite: 'lax',
httpOnly: true,
secure: true
}
)
console.log(setCookieHeader)
// Output: 'session=abc123; Max-Age=604800; Path=/; HttpOnly; Secure; SameSite=Lax'
// Use in custom HTTP response
function setAuthCookie(res, sessionToken) {
const header = serializeCookieHeader('auth-token', sessionToken, {
path: '/',
maxAge: 60 * 60 * 24 * 400,
sameSite: 'lax',
httpOnly: false
})
res.setHeader('Set-Cookie', header)
}
```
## SvelteKit Integration
Example integration with SvelteKit using hooks and load functions.
```typescript
// src/hooks.server.ts
import { createServerClient } from '@supabase/ssr'
import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
const supabase = createServerClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
{
cookies: {
getAll: () => event.cookies.getAll(),
setAll: (cookies) => {
cookies.forEach(({ name, value, options }) => {
event.cookies.set(name, value, {
...options,
path: options.path ?? '/'
})
})
}
}
}
)
// Refresh session
const { data: { session } } = await supabase.auth.getSession()
event.locals.supabase = supabase
event.locals.session = session
return resolve(event)
}
// src/routes/+page.server.ts
import type { PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ locals }) => {
const { data: { user } } = await locals.supabase.auth.getUser()
if (!user) {
return { user: null }
}
const { data: profile } = await locals.supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single()
return { user, profile }
}
```
## Remix Integration
Example integration with Remix using loaders and actions.
```typescript
// app/utils/supabase.server.ts
import { createServerClient } from '@supabase/ssr'
import { parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
export function createSupabaseClient(request: Request) {
const headers = new Headers()
const cookies = parseCookieHeader(request.headers.get('Cookie') || '')
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => cookies,
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) => {
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
})
}
}
}
)
return { supabase, headers }
}
// app/routes/_index.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { createSupabaseClient } from '~/utils/supabase.server'
export async function loader({ request }: LoaderFunctionArgs) {
const { supabase, headers } = createSupabaseClient(request)
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return json({ user: null, posts: [] }, { headers })
}
const { data: posts } = await supabase
.from('posts')
.select('*')
.eq('user_id', user.id)
return json({ user, posts }, { headers })
}
export default function Index() {
const { user, posts } = useLoaderData()
return (
Welcome {user?.email}
{posts.map(post => (
- {post.title}
))}
)
}
```
## Authentication Flow Examples
Common authentication patterns including sign-in, sign-up, sign-out, and OAuth.
```typescript
import { createBrowserClient } from '@supabase/ssr'
const supabase = createBrowserClient(
'https://your-project.supabase.co',
'your-anon-key'
)
// Email/Password Sign Up
async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) {
console.error('Sign up error:', error.message)
return null
}
return data.user
}
// Email/Password Sign In
async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
})
if (error) {
console.error('Sign in error:', error.message)
return null
}
return data.session
}
// OAuth Sign In (Google, GitHub, etc.)
async function signInWithOAuth(provider: 'google' | 'github' | 'discord') {
const { data, error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) {
console.error('OAuth error:', error.message)
}
// User will be redirected to OAuth provider
}
// Sign Out
async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) {
console.error('Sign out error:', error.message)
}
// Session cookies will be cleared automatically
window.location.href = '/login'
}
// Get verified user (recommended for authorization)
async function getVerifiedUser() {
// getUser() contacts Auth server - use for authorization decisions
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return null
}
return user
}
// Get session from cookies (NOT verified - don't use for authorization)
async function getSessionFromCookies() {
// getSession() reads directly from cookies without verification
// The user object is NOT verified - could be spoofed
const { data: { session } } = await supabase.auth.getSession()
return session
}
// Listen to auth state changes
supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth event:', event)
switch (event) {
case 'SIGNED_IN':
console.log('User signed in:', session?.user.email)
break
case 'SIGNED_OUT':
console.log('User signed out')
window.location.href = '/login'
break
case 'TOKEN_REFRESHED':
console.log('Token refreshed')
break
case 'USER_UPDATED':
console.log('User updated:', session?.user)
break
}
})
```
## Summary
`@supabase/ssr` provides the essential building blocks for implementing authentication in server-side rendered applications. The library's main use cases include: protecting server-rendered routes via middleware patterns, maintaining consistent session state between browser and server, and implementing OAuth flows with proper cookie handling. The middleware pattern is critical - it ensures sessions are refreshed before pages render and that updated tokens are properly sent back to the browser via `Set-Cookie` headers.
When integrating with any SSR framework, the key pattern is to create a server client in middleware with both `getAll` and `setAll` cookie methods, call `getUser()` or `getSession()` early in the request lifecycle to trigger any necessary token refresh, and ensure the response includes the updated cookies. For server components where cookies cannot be set, always rely on middleware to handle session updates. Use `getUser()` for authorization decisions as it contacts the Auth server for verification, while `getSession()` reads unverified data directly from cookies and should only be used for non-sensitive display purposes.