Sessions & Cookies
cookies() function from next/headers.Reading a session cookie
A Server Component reading a session cookie
import { cookies } from 'next/headers'
export default function DashboardPage() {
const sessionToken = cookies().get('session')?.value
if (!sessionToken) {
return <p>Not logged in.</p>
}
return <p>Session token present — look it up to load the user.</p>
}cookies().get(...) works anywhere in a Server Component's render — reading is always fine. But cookies().set(...) can only be called from a Server Action or a Route Handler, never during the plain render of a Server Component or layout. Setting a cookie is a side effect on the outgoing HTTP response, and a component's render function has no well-defined moment at which that response is still mutable — calling .set() there throws at runtime.app/actions.ts — setting a cookie from a Server Action
'use server'
import { cookies } from 'next/headers'
export async function login(formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
const sessionToken = await verifyCredentials(email, password)
cookies().set('session', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7, // 1 week
})
}Two session strategies
Strategy | What the cookie holds | Pros | Cons |
|---|---|---|---|
Stateless JWT | A signed, self-contained token encoding the user ID and claims | No database lookup needed to validate a request; scales easily across servers | Hard to revoke a single session early; token grows with the data it carries |
Server-side session store | Just an opaque session ID; the actual session data lives in a database or Redis | Instantly revocable (delete the row); session data can be updated without reissuing a token | Every authenticated request needs a lookup against the store |
strategy: 'jwt' vs. strategy: 'database'), which is one of the more consequential settings to get right for a given app — see the next page for how it fits together.A worked example: reading and validating a session
lib/session.ts
import { cookies } from 'next/headers'
export async function getSession() {
const token = cookies().get('session')?.value
if (!token) return null
// Stateless JWT: verify + decode.
// Server-side store: look the ID up against the database instead.
try {
return await verifySessionToken(token)
} catch {
return null
}
}app/dashboard/page.tsx
import { getSession } from '@/lib/session'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await getSession()
if (!session) redirect('/login')
return <p>Welcome back, {session.userName}.</p>
}cookies()fromnext/headersreads and writes cookies in the App Router.Reading a cookie works during a Server Component render; setting one only works in a Server Action or Route Handler.
A stateless JWT trades easy revocation for not needing a database lookup on every request.
A server-side session store trades a lookup-per-request for instant revocation and easy session updates.