cookies() Returns a Promise in Next.js 15/16: Fix
cookies() from next/headers returns a Promise since Next.js 15 — change const c = cookies() to const c = await cookies() and make the caller async.
· Updated
The patch is two edits per call site: change const store = cookies() to
const store = await cookies(), and mark the enclosing function async. That
is the entire fix for cookies() should be awaited — the rest of this page is
the fast diagnostic (find every call site), the minimal patch patterns for each
App Router surface, and the verification pass that catches the call site you
missed.
If you upgraded a Next.js app to 15 or 16 and your logs suddenly filled with
Error: Route "/dashboard" used cookies().get(...). cookies() should be awaited before using its value, nothing in your code is broken in the way the message
suggests. Next.js changed the contract of cookies() — and the same change
applies to headers(), draftMode(), and the params/searchParams props
passed to pages and layouts. This page is the express repair; for the full
migration story — why the API went async, the next-async-request-api codemod,
Client Component React.use() unwrapping, and the Supabase createServerClient
factory pattern — see the companion deep dive:
Fix "cookies() should be awaited" Error in Next.js 15.
What actually changed#
In Next.js 14, cookies() from next/headers returned a ReadonlyRequestCookies
object synchronously. You could call .get() on it immediately. In Next.js 15
the function became asynchronous: it now returns a Promise, and you have to
await it before touching .get(), .getAll(), .has(), or .set().
This was a deliberate move. Making these request-scoped APIs async lets the
framework start rendering the static shell of a route before the dynamic,
per-request data (cookies, headers) is resolved. It is the groundwork for
Partial Prerendering. The catch is that the codemod does not always reach every
call site, and a synchronous .get() on the returned promise fails in a way
that looks like an auth bug rather than a syntax change.
Why it looks like a session bug#
In Next.js 15, un-awaited cookies().get(...) mostly still works: the docs
say sync access is kept "to help with backwards compatibility", so a shim
answers the call and logs the warning instead of crashing. That compatibility is
exactly what makes the bug sneaky — the app appears fine while every request
spams the log, and the access patterns the shim does not cover (spreading,
iterating, passing the store around before it resolves, and TypeScript treating
the return value as Promise<ReadonlyRequestCookies>) surface as undefined
session values instead of a clean error. A session lookup that returns null
means middleware treats the user as logged out, and you get redirects to
/login or empty SSR pages even though the cookie is present in the request.
That is why this shows up as "auth broke after the upgrade" rather than a syntax
error — and why the sync grace period is a migration window, not a fix.
The fix#
Await the call, and make the enclosing function async:
// ❌ Next.js 14 pattern — throws in 15/16
import { cookies } from "next/headers";
export function getToken() {
return cookies().get("sb-access-token")?.value;
}// ✅ Next.js 15/16
import { cookies } from "next/headers";
export async function getToken() {
const cookieStore = await cookies();
return cookieStore.get("sb-access-token")?.value ?? null;
}The single most effective change is to centralize cookie access in one helper and await it there. Mixed patterns — one route awaits, another does not — are the hardest version of this bug to track down, because half your app works.
// lib/session.ts
import { cookies } from "next/headers";
export async function readSessionCookie(name: string) {
const store = await cookies();
return store.get(name)?.value ?? null;
}Every server component, route handler, and server action then calls
await readSessionCookie(...) and there is exactly one place that knows the API
is async.
If a call site truly cannot be made async today, Next.js 15 documents a typed escape hatch that keeps the sync behavior while still warning in dev:
import { cookies, type UnsafeUnwrappedCookies } from "next/headers";
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies;
const token = cookieStore.get("sb-access-token")?.value;The name is the documentation: it is unsafe, temporary, and exists only for backwards compatibility. Treat every occurrence as an open migration ticket.
Writing cookies#
.set() follows the same rule, but it can only be called from a Server Action
or a Route Handler — not from a server component during render. If you try to
set a cookie inside a page component you will get a different error about mutating
cookies during rendering. Move the write into an action:
"use server";
import { cookies } from "next/headers";
export async function login(token: string) {
const store = await cookies();
store.set("sb-access-token", token, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
});
}Middleware is the exception#
Inside middleware.js you do not use next/headers. You read cookies from
the request object, and that API stayed synchronous:
export function middleware(request) {
const token = request.cookies.get("sb-access-token")?.value;
// ...
}Mixing the two — awaiting in a server component but expecting the middleware API to behave the same — is a common source of "it works in the route but not the redirect" confusion.
Verifying the fix#
Do not trust a clean dev console alone; the async gap can still silently return
null in a code path the compiler cannot check. Verify behaviorally:
grep -rn "cookies()\." src appand confirm every hit is preceded byawait.- Run a production build (
next build && next start), log in, and hard-refresh a server-rendered protected route. The session must survive the refresh. - Check the server logs during that refresh — a lingering
cookies() should be awaitedline means a call site was missed.
A missed call site in a rarely-hit route is exactly how this reaches production, so the grep-plus-refresh pass matters more than the passing build.
Related Incidents#
- INC-001 cookies() warning
- Next.js Hydration Mismatch: 8 Fixes for App Router (2026)
- Next.js Server Actions Debugging Matrix: Production Fix Matrix
- Fix Next.js revalidatePath Not Working in Server Actions
- Next.js Scroll Resets When searchParams Change
- TypeError: cookies() crash in a Next.js route handler — the same async contract, one surface later: once the warning is fixed in Server Components, the route handlers that still destructure the return value throw outright.
Related fixes & guides
- Next.js "Server Is Running Out of Memory" Fix (2026)
- Next.js useSearchParams Suspense: Static Rendering Fix 2026
- Fix barrel_optimize Build Warnings with MUI in Next.js
- Next.js App Router Guide: From Basics to Advanced Patterns
- Window is not defined in Next.js – 2026 Fix for React Apps
- Next.js Server Actions with Supabase: Complete Guide
- Fix: Next.js proxy.js matcher not working for static assets
- Fix: react-dom/server Import Error in Next.js App Router
- Next.js Redirect from / to another page
- Why useEffect runs twice in Next.js dev