Next.js Cookies and Sessions
Web servers forget visitors between requests. Each page load arrives as a fresh stranger. Cookies and sessions solve this memory problem. A cookie stores a small piece of text in the browser, and a session uses that text to recognize a returning visitor. Sign-in systems, shopping carts, and theme preferences all rely on this pair.
The Everyday View
Picture a coat check at a theater. You hand over your coat and receive a numbered ticket. The ticket holds no coat. The ticket holds only a number. When you return, the attendant matches the number to a hook and retrieves your coat.
The cookie acts as the ticket. The session data acts as the coat, safely stored on the server.
Session Flow
1. Visitor signs in
|
2. Server creates session record --> [ID: abc123 | user: 42]
|
3. Server sends cookie --> session=abc123
|
4. Browser stores cookie
|
5. Later request carries cookie --> session=abc123
|
6. Server looks up abc123 --> finds user 42
Cookies Versus Sessions
| Item | Cookie | Session |
|---|---|---|
| Storage place | Visitor browser | Server or database |
| Typical size | Small text | Any size |
| Visitor can read it | Yes | No |
| Best use | Preferences, session ID | User identity, cart contents |
Reading and Writing Cookies
The cookies function from next/headers works inside Server Components, Server Actions, and Route Handlers. Recent versions return a promise, so await the call.
Reading a Cookie
import { cookies } from 'next/headers';
export default async function Greeting() {
const cookieStore = await cookies();
const theme = cookieStore.get('theme');
return <p>Current theme: {theme ? theme.value : 'light'}</p>;
}Writing a Cookie
Writing works only inside Server Actions and Route Handlers. A Server Component renders while the response streams, and headers have already left by then.
'use server';
import { cookies } from 'next/headers';
export async function saveTheme(formData) {
const cookieStore = await cookies();
cookieStore.set('theme', formData.get('theme'), {
path: '/',
maxAge: 60 * 60 * 24 * 365,
});
}Deleting a Cookie
const cookieStore = await cookies();
cookieStore.delete('theme');Important Cookie Options
| Option | What It Does |
|---|---|
httpOnly | Blocks browser scripts from reading the cookie |
secure | Sends the cookie only over HTTPS |
sameSite | Limits sending the cookie from other websites |
maxAge | Sets lifetime in seconds |
path | Sets which URLs receive the cookie |
Session cookies deserve the strictest settings. Use httpOnly, secure, and sameSite: 'lax' as a starting point.
Building a Simple Session
The example below stores a random ID in a cookie and keeps the real data in a server-side store. Production apps place the store in a database or a fast key-value service.
import { cookies } from 'next/headers';
import { randomUUID } from 'crypto';
const sessions = new Map();
export async function createSession(userId) {
const sessionId = randomUUID();
sessions.set(sessionId, { userId });
const cookieStore = await cookies();
cookieStore.set('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7,
});
}
export async function getSession() {
const cookieStore = await cookies();
const id = cookieStore.get('session')?.value;
return id ? sessions.get(id) : null;
}An in-memory Map loses all data when the server restarts and breaks across multiple servers. Treat it as a teaching tool only.
Stateless Sessions
Some apps store the session data inside the cookie itself and sign or encrypt it. The server needs no database lookup because the cookie carries everything. Libraries such as jose handle signed tokens. The tradeoff is size and revocation: a signed cookie stays valid until it expires, unless you add extra checks.
Using Cookies in Middleware
Middleware can read cookies through request.cookies and redirect visitors who lack a session cookie. Keep these checks light. Perform the full session verification near the data access code.
Security Habits
- Never store passwords or card numbers in cookies.
- Generate session IDs with a cryptographic random source.
- Set an expiry date on every session.
- Delete the session record when a visitor signs out.
- Use HTTPS on every production site.
- Respect privacy rules and inform visitors about non-essential cookies.
Common Mistakes
Setting Cookies in a Server Component
The call raises an error. Move the write into a Server Action or a Route Handler.
Forgetting to Await
Recent versions return a promise from cookies(). A missing await leaves you with a promise object instead of the cookie store.
Storing Too Much Data
Browsers limit cookies to roughly four kilobytes each. Store large data on the server and keep only the ID in the cookie.
Quick Summary
Cookies carry small pieces of text between browser and server. Sessions use a cookie ID to find private data stored safely on the server. Set strict cookie options, validate sessions on the server, and keep sensitive data out of the browser.
