Lesson 18 of 81
A theme preference, a session token, an auth header forwarded to an upstream API — all of it lives in cookies and headers, and the App Router's next/headers module is how a Server Component, Route Handler, or Server Action gets at it. Getting a value out is easy; knowing where you're allowed to set one, and what reading one silently costs you, is the actual lesson.
10 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
Inside a Server Component's render, you try to call `(await cookies()).set('theme', 'dark')`. What happens?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
next/headers exports two functions, cookies() and headers(), both async — you await them (or unwrap with React's use() in a Client Component) before you get the object you actually want to read from. This is current behavior, not a historical detail: earlier Next.js versions had these as synchronous functions, but as of the version this app runs, both are promises.
The two functions aren't symmetric in what they let you do:
cookies() returns a cookie jar you can .get(), .getAll(), .has() anywhere — but .set() and .delete() only work inside a Server Function (a Server Action) or a Route Handler, never during Server Component rendering. That's not an arbitrary restriction: HTTP doesn't let you set a cookie after the response has started streaming, and a Server Component's render is exactly that streaming response.headers() returns a read-only Headers object, full stop, everywhere. There's no code path that lets you set outgoing request headers through this function — if you need to send a header back, you build a new Response(..., { headers: {...} }) yourself.// app/actions.ts
"use server";
import { cookies, headers } from "next/headers";
export async function setTheme(theme: "light" | "dark") {
const cookieStore = await cookies();
cookieStore.set("theme", theme, { httpOnly: true, path: "/" });
}
export async function getRequestOrigin() {
const headersList = await headers();
return headersList.get("origin");
}
setTheme can call .set() because it's a Server Action, not a Server Component render — Next.js can attach a Set-Cookie header to the response this action produces. getRequestOrigin only reads, so it would work identically inside a Server Component too.
cookies() and headers() are async now — await them before reading. Code copied from an older tutorial that does cookies().get('theme') directly, without awaiting, is out of date; the current app relies on the promise-returning behavior.headers() is read-only, with no exceptions. There's no Server-Action-only escape hatch the way there is for cookies() — if you need to influence outgoing headers, you construct a new Response and set them there instead.cookies().set() and .delete() only work in a Server Function or Route Handler, never in a Server Component. This isn't Next.js being restrictive for no reason — the server can only ever send instructions (a Set-Cookie header) telling the browser to store a cookie, and that instruction has to be attached before the response finishes.cookies() just to check a feature flag has opted the whole route out of static prerendering, even if nothing else about the page is actually dynamic."Why did adding a single cookies().get() call to a previously-static page suddenly make it slower / opt it into dynamic rendering?" is a real debugging scenario that tests whether a candidate understands Request-time APIs as a category, not just as a two-function cookies/headers API to memorize.