Cookies & Headers
Read and set cookies and headers on the server with the cookies() and headers() APIs from next/headers.
Recipe
Quick-reference recipe card -- copy-paste ready.
import { cookies, headers } from "next/headers";
// Reading cookies (async in Next.js 15+)
export default async function Page() {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value ?? "light";
const token = cookieStore.get("auth-token")?.value;
// Reading headers
const headersList = await headers();
const userAgent = headersList.get("user-agent") ?? "";
const ip = headersList.get("x-forwarded-for") ?? "unknown";
return <div>Theme: {theme}</div>;
}
// Setting cookies in a Server Action
"use server";
import { cookies } from "next/headers";
export async function setTheme(theme: string) {
const cookieStore = await cookies();
cookieStore.set("theme", theme, {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 365, // 1 year
});
}When to reach for this: You need to read authentication tokens, locale preferences, feature flags, or any other per-request data from cookies or headers.
Working Example
// app/layout.tsx (Server Component)
import { cookies, headers } from "next/headers";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value ?? "system";
const locale = cookieStore.get("locale")?.value ?? "en";
const headersList = await headers();
const acceptLanguage = headersList.get("accept-language");
return (
<html lang={locale} data-theme={theme}>
<body>{children}</body>
</html>
);
}// app/actions/preferences.ts
"use server";
import { cookies } from "next/headers";
import { revalidatePath } from "next/cache";
export async function setTheme(formData: FormData) {
const theme = formData.get("theme") as string;
if (!["light", "dark", "system"].includes(theme)) {
return { error: "Invalid theme" };
}
const cookieStore = await cookies();
cookieStore.set("theme", theme, {
httpOnly: false, // allow client JS to read for immediate UI update
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
revalidatePath("/", "layout");
}
export async function setLocale(locale: string) {
const cookieStore = await cookies();
cookieStore.set("locale", locale, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
revalidatePath("/", "layout");
}// app/components/theme-switcher.tsx
"use client";
import { useTransition } from "react";
import { setTheme } from "@/app/actions/preferences";
export function ThemeSwitcher({ currentTheme }: { currentTheme: string }) {
const [isPending, startTransition] = useTransition();
const themes = ["light", "dark", "system"] as const;
return (
<div className="flex gap-2">
{themes.map((theme) => (
<button
key={theme}
onClick={() =>
startTransition(async () => {
const fd = new FormData();
fd.set("theme", theme);
await setTheme(fd);
})
}
className={`px-3 py-1 rounded border ${
currentTheme === theme
? "bg-blue-600 text-white"
: "bg-white text-gray-700"
} ${isPending ? "opacity-50" : ""}`}
disabled={isPending}
>
{theme}
</button>
))}
</div>
);
}// middleware.ts -- reading and setting cookies/headers in Middleware
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Read a cookie
const locale = request.cookies.get("locale")?.value ?? "en";
// Read a header
const country = request.headers.get("x-vercel-ip-country") ?? "US";
// Set a header for downstream use
const response = NextResponse.next();
response.headers.set("x-locale", locale);
response.headers.set("x-country", country);
// Set a cookie
if (!request.cookies.has("visitor-id")) {
response.cookies.set("visitor-id", crypto.randomUUID(), {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 365,
});
}
return response;
}What this demonstrates:
- Reading cookies and headers in Server Components and layouts
- Setting cookies in Server Actions with proper security options
- Using Middleware to read/set cookies and headers at the edge
- A theme switcher pattern combining server cookies with client interactivity
Deep Dive
How It Works
cookies()andheaders()are async functions in Next.js 15+ that return the cookie store and headers map for the current request.- Calling either function opts the route into dynamic rendering -- the page cannot be statically generated because it depends on request-time data.
cookies()returns aReadonlyRequestCookiesobject when read in Server Components. In Server Actions and Route Handlers, it returns a writable store that supports.set()and.delete().headers()returns a read-onlyHeadersobject. You cannot set response headers from a Server Component -- use Middleware or Route Handlers for that.- Cookies set in Server Actions are sent back as
Set-Cookieresponse headers. The browser applies them immediately. - Middleware runs before all route handling and can modify both request and response cookies/headers.
Variations
Deleting a cookie:
"use server";
import { cookies } from "next/headers";
export async function logout() {
const cookieStore = await cookies();
cookieStore.delete("auth-token");
cookieStore.delete("session");
}Reading all cookies:
const cookieStore = await cookies();
const allCookies = cookieStore.getAll();
// [{ name: "theme", value: "dark" }, { name: "locale", value: "en" }]Checking if a cookie exists:
const cookieStore = await cookies();
const hasAuth = cookieStore.has("auth-token");Setting response headers in a Route Handler:
// app/api/data/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const data = await fetchData();
return NextResponse.json(data, {
headers: {
"Cache-Control": "public, max-age=3600",
"X-Custom-Header": "my-value",
},
});
}TypeScript Notes
import { cookies, headers } from "next/headers";
// cookies() returns Promise<ReadonlyRequestCookies> in Server Components
// and Promise<RequestCookies> in Server Actions (writable)
const cookieStore = await cookies();
const value: string | undefined = cookieStore.get("key")?.value;
// headers() returns Promise<ReadonlyHeaders>
const headersList = await headers();
const value: string | null = headersList.get("x-custom");
// Cookie options type
type CookieOptions = {
name: string;
value: string;
domain?: string;
path?: string;
maxAge?: number;
expires?: Date;
httpOnly?: boolean;
secure?: boolean;
sameSite?: "strict" | "lax" | "none";
};Gotchas
-
cookies()andheaders()make the route dynamic -- Any Server Component or layout that calls these functions cannot be statically generated. Fix: If you need the cookie value only for interactivity, read it on the client withdocument.cookieor a library likejs-cookieinstead. -
Cannot set cookies in Server Components -- The cookie store is read-only outside of Server Actions and Route Handlers. Fix: Use a Server Action to set cookies, or set them in Middleware.
-
cookies()is async in Next.js 15+ -- Callingcookies()withoutawaitreturns a Promise, not the cookie store. Fix: Alwaysconst cookieStore = await cookies(). -
Middleware cookies vs Server Component cookies -- Cookies set in Middleware via
response.cookies.set()are available to downstream Server Components viacookies()in the same request. However, cookies set in Server Actions are only available on the next request. Fix: Understand the request lifecycle; use Middleware for per-request cookie injection. -
sameSite: "none"requiressecure: true-- Browsers rejectSameSite=Nonecookies without theSecureflag. Fix: Always pairsameSite: "none"withsecure: true. -
Cookie size limits -- Browsers limit individual cookies to approximately 4 KB and total cookies per domain to roughly 80. Fix: Store minimal data in cookies; use a session ID pointing to server-side storage for large payloads.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Middleware cookies | You need to read or set cookies before route handling | You only need cookies inside a Server Action |
document.cookie (client) | You need to read a non-httpOnly cookie for immediate UI updates | You need server-side access or httpOnly cookies |
js-cookie library | You want a friendlier API for client-side cookie management | You are working in Server Components |
| Session storage (e.g., iron-session) | You need encrypted, tamper-proof session data | Simple key-value preferences suffice |
searchParams | Data should be visible in the URL and shareable | Data is sensitive or user-specific |
FAQs
Why does calling cookies() or headers() make a route dynamic?
- These functions depend on request-time data that is different for every visitor
- The page cannot be statically generated because the cookie/header values are unknown at build time
- If you only need a cookie for interactivity, read it on the client with
document.cookieinstead
Why must cookies() and headers() be awaited in Next.js 15+?
- In Next.js 15+, both are async functions returning Promises
- Calling
cookies()withoutawaitgives you a Promise object, not the cookie store - Always use
const cookieStore = await cookies()
Can you set cookies inside a Server Component?
- No. The cookie store is read-only in Server Components and layouts
- Use a Server Action or Route Handler to set cookies
- Alternatively, set cookies in Middleware
What is the difference between cookies set in Middleware vs Server Actions?
- Cookies set in Middleware are available to downstream Server Components in the same request
- Cookies set in Server Actions are sent as
Set-Cookieheaders and only available on the next request - Use Middleware for per-request cookie injection
How do you delete a cookie in a Server Action?
"use server";
import { cookies } from "next/headers";
export async function logout() {
const cookieStore = await cookies();
cookieStore.delete("auth-token");
cookieStore.delete("session");
}What happens if you use sameSite: "none" without secure: true?
- Browsers reject the cookie entirely
SameSite=Nonerequires theSecureflag to be set- Always pair
sameSite: "none"withsecure: true
How do you set custom response headers in Next.js?
- You cannot set response headers from a Server Component
- Use a Route Handler with
NextResponse.json(data, { headers: {...} }) - Or use Middleware with
response.headers.set("key", "value")
What is the TypeScript type difference between cookies in Server Components vs Server Actions?
// Server Component: ReadonlyRequestCookies (read-only)
const cookieStore = await cookies();
cookieStore.get("key"); // OK
// cookieStore.set(...) // Error
// Server Action: RequestCookies (writable)
const cookieStore = await cookies();
cookieStore.set("key", "value", { httpOnly: true }); // OKWhat are the browser limits on cookies?
- Individual cookies are limited to approximately 4 KB
- Total cookies per domain are roughly limited to 80
- Store minimal data in cookies; use a session ID pointing to server-side storage for large payloads
How do you type the cookie options object in TypeScript?
type CookieOptions = {
name: string;
value: string;
domain?: string;
path?: string;
maxAge?: number;
expires?: Date;
httpOnly?: boolean;
secure?: boolean;
sameSite?: "strict" | "lax" | "none";
};How do you read all cookies at once?
const cookieStore = await cookies();
const allCookies = cookieStore.getAll();
// [{ name: "theme", value: "dark" }, { name: "locale", value: "en" }]Why should you set httpOnly: false for a theme cookie but httpOnly: true for an auth token?
httpOnly: falseallows client JavaScript to read the cookie for immediate UI updates (e.g., theme)httpOnly: trueprevents JavaScript access, protecting sensitive tokens from XSS attacks- Always use
httpOnly: truefor authentication and session cookies
Related
- Server Actions -- Setting cookies after mutations
- Search Params -- Alternative request-time data via URL
- Fetching -- How cookies/headers opt routes into dynamic rendering
- Static vs Dynamic -- Dynamic functions and rendering mode