Handler Cookies

Cookies and Headers in Route Handlers

There are two ways in. request.cookies and request.headers read what arrived. The cookies() and headers() functions from next/headers do the same but work anywhere in the call stack, including a helper three modules deep that never received the request. Both are async — awaiting them is required since Next.js 15 10,514 .

The difference that matters: inside a route handler cookies() is writable. In a Server Component it is read-only, because response headers are already on their way by the time the component renders. A handler that sets a session cookie therefore lives here.

Reading and writing in one handler (app/api/session/route.ts)TypeScript
import { cookies, headers } from 'next/headers'
export async function GET() {
  const cookieStore = await cookies()
  const headerList = await headers()
  const visits = Number(cookieStore.get('visits')?.value ?? 0) + 1
  cookieStore.set('visits', String(visits), {
    httpOnly: true, sameSite: 'lax', path: '/', maxAge: 60 * 60,
  })
  return Response.json({ visits, userAgent: headerList.get('user-agent') })
}
Output
$ curl -s -i -c jar.txt http://localhost:3000/api/session
set-cookie: visits=1; Path=/; Expires=Tue, 22 Sep 2026 07:14:04 GMT; Max-Age=3600;
            HttpOnly; SameSite=lax
{"visits":1,"userAgent":"curl/8.19.0"}
$ curl -s -b jar.txt http://localhost:3000/api/session
{"visits":2,"userAgent":"curl/8.19.0"}

Note what maxAge: 3600 produced: both Max-Age and a matching Expires, because old clients understand only the latter. httpOnly keeps the value out of document.cookie and sameSite: 'lax' stops it riding along on cross-site form posts — the defaults you want for a session (Session Cookies).

headers() is read-only in every context; to send a header back, put it on the Response you return. Deleting a cookie is not magic either: cookieStore.delete('visits') emits a Set-Cookie of visits=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT. Cookie writes also survive redirect(), even though that function works by throwing, so a logout handler can clear the session and send the browser onward in one pass — the 307 carries the Set-Cookie headers with it.