How to Read and Set Cookies in Next.js

Read cookies anywhere on the server with the async cookies function, and set or delete them from a Server Action or a Route Handler.

7 min read

Next.js reads and writes cookies through the cookies function from next/headers. It is asynchronous, so you await it once and then call get, set, or delete on the store it returns.

Reading works in any server-rendered code. Writing is more limited, because a cookie reaches the browser as a response header, so it can only be set from a Server Action or a Route Handler.

Here is the smallest read, inside a Server Component page.

App.tsxApp.tsx
// app/dashboard/page.tsx
import { cookies } from 'next/headers'
 
export default async function DashboardPage() {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')?.value ?? 'light'
 
  return <p>Current theme: {theme}</p>
}

If the browser sends a theme cookie with the value dark, the page renders "Current theme: dark". If the cookie is missing, get returns undefined and the fallback value is used, which is why the optional chaining matters.

Why the call is awaited

The function returns a promise, so you must use async and await, or React's use API in a Client Component tree. It became asynchronous in Next.js 15, and an official codemod is available for projects upgrading from Next.js 14 or earlier where it was synchronous.

Awaiting it also has a rendering consequence. Without Cache Components, reading cookies opts the route into dynamic rendering.

With Cache Components enabled, the cookie-reading subtree is runtime content and must sit behind Suspense, while the rest of the page can remain in the prerendered shell. The related behavior is covered in dynamic APIs and static rendering.

The store returned by the call exposes five methods you will actually use: get, getAll, has, set, and delete.

A Server Action runs on the server in response to a form submission or a client call, and the response it produces can carry cookie headers. That makes it the natural place to write a preference or a session.

typescripttypescript
// app/actions.ts
'use server'
import { cookies } from 'next/headers'
export async function setTheme(theme: string) {
  if (theme !== 'light' && theme !== 'dark') {
    throw new Error('Invalid theme')
  }
  const cookieStore = await cookies()
  cookieStore.set('theme', theme, { httpOnly: true, sameSite: 'lax' })
}

The 'use server' directive at the top of the file marks every export as a Server Function, so this code never ships to the browser. The allowlist is still required because a client can call a Server Function with arbitrary arguments. When this function is used as a Server Action, Next.js can re-render the current UI in the same roundtrip and the browser stores the cookie for later requests.

A Route Handler is the right place when the write is triggered by a fetch call rather than a form, for example from a settings widget that posts JSON.

typescripttypescript
// app/api/theme/route.ts
import { cookies } from 'next/headers'
export async function POST(request: Request) {
  const body: unknown = await request.json().catch(() => null)
  const theme = body && typeof body === 'object' && 'theme' in body ? body.theme : null
  if (theme !== 'light' && theme !== 'dark')
    return Response.json({ error: 'Invalid theme' }, { status: 400 })
  const cookieStore = await cookies()
  cookieStore.set('theme', theme, { httpOnly: true, sameSite: 'lax' })
  return Response.json({ theme })
}

A POST to /api/theme returns the JSON body and, in the browser network panel, a Set-Cookie header on the response. The cookie is applied by the browser, not by the server, so it only appears in requests that happen after this response.

The options that matter

The third argument controls how the browser stores and returns the cookie. These are the ones worth setting deliberately.

OptionEffect
httpOnlyHides the cookie from client-side JavaScript
secureSends the cookie only over HTTPS
sameSiteControls whether the cookie travels on cross-site requests
maxAgeLifetime in seconds, counted from now
expiresExact expiry date, an alternative to maxAge
pathLimits the cookie to a path prefix, defaults to the site root

For anything that identifies a user, set httpOnly and sameSite. Without httpOnly, a script injected into your page can read the value directly. Session-specific guidance lives in session management with cookies.

Deletion is a write, so it has the same placement rule: a Server Action or a Route Handler, never a Server Component render.

typescripttypescript
// app/actions.ts
'use server'
 
import { cookies } from 'next/headers'
 
export async function signOut() {
  const cookieStore = await cookies()
  cookieStore.delete('session')
}

The response tells the browser to expire the cookie immediately, so the next request arrives without it. Setting the same name again with a maxAge of 0 has the same effect if you need to pass options along with the removal.

Reading cookies from the request object

Inside a Route Handler you also receive the request, and NextRequest carries a parsed cookie store of its own. This avoids a second import when you only need to read.

typescripttypescript
// app/api/preferences/route.ts
import { type NextRequest } from 'next/server'
 
export async function GET(request: NextRequest) {
  const theme = request.cookies.get('theme')?.value ?? 'light'
  return Response.json({ theme })
}

A request without the cookie gets the light fallback, while a request with it gets the stored theme. Cookie presence alone does not prove a user is authenticated, so session cookies must be cryptographically verified before granting access.

The request store reads incoming cookies, while the store from next/headers also exposes outgoing cookie methods. More on that split in NextRequest and NextResponse explained.

Where cookies do not work

Three limits catch people out, and all three come from the same fact that cookies are request and response headers rather than server state.

  • You cannot set a cookie while a Server Component renders, because the response may already be streaming. Next.js throws instead of silently dropping the header.
  • You cannot read a cookie inside a use cache function, since cached output is shared across users. See why cookies and headers cannot be used inside use cache.
  • With Cache Components enabled, a cookie-reading subtree must sit behind Suspense because its value is unavailable during prerendering.

Common mistakes

  • Forgetting the await, which leaves you calling get on a promise instead of the resolved cookie store.
  • Assuming TypeScript argument types validate a Server Action at runtime. Treat every action argument as untrusted input.
  • Leaving httpOnly off a session cookie so client scripts can read it.
  • Deleting a cookie that was set with a different path or domain. The delete only matches when those attributes line up.

The short version

Await the cookies function, read anywhere on the server, and write only from a Server Action or a Route Handler. Treat every cookie you read as request data, and set httpOnly and sameSite on anything that carries identity.

Rune AI

Rune AI

Key Insights

  • The cookies function from next/headers is async and must be awaited.
  • Reading works in Server Components, Server Actions, and Route Handlers.
  • Writing and deleting only work in Server Actions and Route Handlers.
  • Use httpOnly, secure, and sameSite for session cookies.
  • Reading a cookie makes the route depend on request data, so it cannot be prerendered without a Suspense boundary.
RunePowered by Rune AI

Frequently Asked Questions

Why do I have to await cookies()?

The cookies function became asynchronous in Next.js 15, and synchronous access was removed in Next.js 16. In Next.js 14 and earlier it was synchronous, and an official codemod exists for that upgrade.

Can I set a cookie inside a Server Component?

No. A cookie is set through a response header, and HTTP does not allow that once the response has started streaming. Move the write into a Server Action or a Route Handler.

Should I use cookies() or request.cookies in a Route Handler?

Both can read incoming cookies. Use the request object when you already have it and only need to read, and use the next/headers function when you need its outgoing cookie methods.

How do I delete a cookie?

Call delete with the cookie name from a Server Action or Route Handler, or set the same name again with a maxAge of 0. The deletion must match the cookie's domain, path, and protocol constraints.

Conclusion

Reading cookies works in any server-rendered code through the async cookies function, while writing them is limited to Server Actions and Route Handlers because a cookie is delivered as a response header. Set httpOnly and sameSite on anything security related, and remember that reading a cookie makes a route depend on the request.