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

ItemCookieSession
Storage placeVisitor browserServer or database
Typical sizeSmall textAny size
Visitor can read itYesNo
Best usePreferences, session IDUser 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

OptionWhat It Does
httpOnlyBlocks browser scripts from reading the cookie
secureSends the cookie only over HTTPS
sameSiteLimits sending the cookie from other websites
maxAgeSets lifetime in seconds
pathSets 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.