Back to Top

Handling Sessions and Cookies in Next.js

Updated 18 July 2026

Almost every web application needs to remember its users. Once someone logs in, they expect to stay signed in while moving between pages.

Cookies and sessions make this possible. They work together to maintain a user’s login state and provide a seamless authentication experience.

Next.js offers a simple server-side API for working with cookies in the App Router, making it an essential part of modern Next.js development for building secure, scalable, and dynamic web applications.

You can also use middleware to verify authentication and protect routes before a request reaches your application. For a deeper understanding, read Next.js Auth Using Middleware.

In this guide, you’ll learn how cookies work, how to build a simple session, and how to keep it secure.

session and cookie in nextjs

Cookies vs. Sessions

Cookies and sessions are closely related, but they serve different purposes.

A cookie is a small piece of data stored in the browser. The browser automatically sends it with every request to your application.

A session represents a user’s authenticated state. In most applications, the browser stores a session token or ID in a cookie, allowing the server to identify the user on future requests.

There are two common ways to manage sessions. You can store the session data directly in a cookie (stateless sessions) or store only a session ID in the cookie while keeping the session data on the server (stateful sessions).

We’ll start with the stateless approach because it’s simple and easy to implement.

The cookies() API

Next.js provides the cookies() API through next/headers for reading and writing cookies on the server.

Starting with Next.js 15, cookies() is asynchronous, so you need to await it before accessing cookie values.

Here’s how you read a cookie value.

import { cookies } from "next/headers";

const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value;

You can set and delete cookies too.

cookieStore.set("theme", "dark");
cookieStore.delete("theme");

Where You Can Set Cookies

Reading cookies is supported in most server-side environments.

Writing or deleting cookies, however, is only allowed where Next.js can modify the response.

That means you can set cookies inside Server Actions, Route Handlers, and Middleware.

You cannot set cookies inside a Server Component because the response may have already started streaming to the browser.

If you need to update a cookie from a page, move that logic into a Server Action instead.

Keeping Cookies Secure

Cookie attributes play an important role in protecting user sessions.

A secure session cookie usually looks like this:

cookieStore.set("session", token, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 7, // 7 days
});

Building a Simple Session

Let’s build a simple stateless session using JWT.

We’ll use the jose library because it works in both the Node.js and Edge runtimes.

npm install jose

Now create two helper functions to sign and verify session tokens.

// lib/session.ts

import "server-only";
import { SignJWT, jwtVerify } from "jose";
const key = new TextEncoder().encode(process.env.SESSION_SECRET);
export async function encrypt(payload: { userId: string; email: string }) {
return new SignJWT(payload)
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime("7d")
.sign(key);
}

export async function decrypt(token?: string) {
if (!token) return null;
try {
const { payload } = await jwtVerify(token, key);
return payload as { userId: string; email: string };
} catch {
return null;
}
}

encrypt() creates a signed token, while decrypt() verifies the token and returns its payload.

Creating the Session

After a user signs in successfully, create a session cookie.

// app/actions/session.ts

"use server";

import { cookies } from "next/headers";
import { encrypt } from "@/lib/session";
export async function createSession(userId: string, email: string) {
const token = await encrypt({ userId, email });
(await cookies()).set("session", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 7,

});

}

This signs the user’s data and saves it as a session cookie.

Reading the Session

Create a helper to read and verify the session anywhere on the server.

// lib/get-current-user.ts

import { cookies } from "next/headers";
import { decrypt } from "@/lib/session";
export async function getCurrentUser() {
const token = (await cookies()).get("session")?.value;
return decrypt(token);
}

If the session is valid, this function returns the authenticated user. Otherwise, it returns null.

You can now protect any page.

// app/dashboard/page.tsx

import { getCurrentUser } from "@/lib/get-current-user";
import { redirect } from "next/navigation";
export default async function Dashboard() {
const user = await getCurrentUser();
if (!user) redirect("/login");
return <h1>Welcome back, {user.email}</h1>;

}

If no valid session exists, the user is redirected to the login page.

Refreshing Sessions with Middleware

Middleware runs before a request reaches your application, making it a great place to validate or refresh user sessions.

If you’d like to learn more about protecting routes with middleware, check out Next.js Auth Using Middleware.

import { NextResponse, type NextRequest } from "next/server";
import { decrypt } from "@/lib/session";

export async function middleware(req: NextRequest) {
const token = req.cookies.get("session")?.value;
const session = await decrypt(token);
if (!session) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*"] }

This checks the session on protected routes and redirects if it’s missing.

Stateless vs. Stateful Sessions

The approach used above is called a stateless session because the session data is stored inside the token. It’s simple to implement and doesn’t require a database. The downside is that you can’t revoke a token until it expires.

A stateful session stores only a session ID in the cookie while keeping the actual session data on the server.This allows features like instant logout, session revocation, and device management.

Choose stateless sessions for simplicity. Switch to stateful sessions when your application requires more control.

Security Checklist

Follow these best practices to keep session cookies secure.

  • Use stateful sessions when you need complete control over active sessions.
  • Always set httpOnly, secure, and sameSite.
  • Store secret keys in environment variables.
  • Write cookies only in Server Actions, Route Handlers, or Middleware.

Conclusion

Cookies and sessions work together to provide a secure authentication experience in Next.js.

Once you understand where cookies can be read and written, managing user sessions becomes much simpler.

Start with a stateless session for smaller applications. As your project grows, you can move to a stateful approach when you need features like instant logout, session management, or device tracking.

. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


Be the first to comment.

Back to Top

Message Sent!

If you have more details or questions, you can reply to the received confirmation email.

Back to Home