How to Fix: Regression in v15.1.0: `cookies()` throws in server action
Next.js 15.1.0 introduced a regression where calling cookies() inside a server action can throw unexpectedly, even though the same pattern worked previously. If your app started failing after upgrading, the problem is not your form, not your action signature, and not your cookie name. The failure is caused by a broken request async storage context during server action execution.
Table of Contents
What this bug looks like
In the reported reproduction, a server action is invoked from src/app/page.tsx, and inside that action the code accesses cookies(). On Next.js v15.1.0, this can throw because the action no longer has access to the expected request-bound context.
Typical symptoms include:
- An exception when cookies() is called inside the action.
- The same logic working in an earlier version.
- Confusion because cookies() still works in other server contexts such as route handlers or page rendering.
If you need the original reproduction, see the linked repository.
Understanding the Root Cause
Understanding the Root Cause
This bug happens because cookies() depends on an internal request context managed by Next.js. Under the hood, APIs like cookies() and headers() read from request-scoped async storage. In a healthy execution path, that storage is attached to the current server request and remains available while the server action runs.
In v15.1.0, a regression caused some server actions to execute without that storage being available at the moment cookies() is evaluated. As a result, Next.js throws because it cannot resolve the active request state.
Technically, this means:
- cookies() is not a plain utility function; it is context-sensitive.
- Server actions require the correct request async storage to be bound.
- The regression breaks that binding in scenarios that previously worked.
This is why the code can fail even though it is semantically correct.
Step-by-Step Solution
Step-by-Step Solution
Because this is a framework regression, the most reliable fix is to avoid v15.1.0 for this pattern and move to a version where the bug is fixed, or temporarily use a safe workaround.
1. Upgrade Next.js to a patched version
First, update Next.js to the latest stable release in the same major line if a fix is available.
npm install next@latest react@latest react-dom@latest
Or with pnpm:
pnpm add next@latest react@latest react-dom@latest
Then verify the installed version:
npm ls next
If your project must stay on the affected line temporarily, use one of the workarounds below.
2. Keep cookie access inside a route handler as a workaround
If cookies() throws in a server action, move the cookie read/write logic into a Route Handler, where request context is reliably available.
app/api/session/route.ts
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function POST() {
const cookieStore = await cookies()
const current = cookieStore.get('session')?.value
return NextResponse.json({ session: current ?? null })
}
Then call that route from your client component or from a safe server boundary.
app/page.tsx
export default function Page() {
async function handleSubmit(formData: FormData) {
'use server'
const res = await fetch('http://localhost:3000/api/session', {
method: 'POST',
cache: 'no-store'
})
const data = await res.json()
console.log(data)
}
return (
<form action={handleSubmit}>
<button type="submit">Submit</button>
</form>
)
}
This is not as elegant as reading cookies directly in the action, but it avoids the broken execution path.
3. If you only need to set state, pass explicit values into the action
If your action only needs a value that originally came from a cookie, you may be able to read it earlier and pass it explicitly rather than calling cookies() inside the action.
import { cookies } from 'next/headers'
export default async function Page() {
const cookieStore = await cookies()
const session = cookieStore.get('session')?.value ?? null
async function saveAction(formData: FormData) {
'use server'
console.log('Known session:', session)
}
return (
<form action={saveAction}>
<button type="submit">Save</button>
</form>
)
}
This works only when the value does not need to be refreshed from the current request at action time.
4. Avoid wrapping or delaying the call in ways that obscure context
Even outside this regression, request-bound APIs are safest when called directly in the server-controlled flow. Avoid patterns like unnecessary indirection, custom deferred wrappers, or background execution that may outlive the request scope.
async function action() {
'use server'
// Prefer direct access in the action body
const store = await cookies()
return store.get('theme')?.value
}
While this alone may not fix the regression in v15.1.0, it prevents additional context-loss bugs.
5. Confirm the issue is really this regression
Before changing architecture, verify these points:
- The function contains ‘use server’.
- The error started after upgrading to 15.1.0.
- cookies() works in a route handler or page render but fails in the action.
- You are not accidentally calling the action from unsupported code.
If all four are true, you are very likely hitting the reported regression.
Common Edge Cases
Common Edge Cases
1. Confusing cookies() read timing with action timing
If you read a cookie during page render and reuse that value later in the action, it may be stale. That workaround is acceptable only when stale data is harmless.
2. Using helper functions that call cookies()
If cookies() is buried inside a utility, the stack trace may make the bug look unrelated. Check all helpers used by the server action.
3. Mixing route handlers and server actions incorrectly
If you move logic to an API route, remember that absolute URLs may fail in some deployments. Prefer environment-aware base URLs or invoke internal logic directly where possible.
4. Assuming all server-only APIs behave the same
cookies(), headers(), and other dynamic APIs all depend on request context. If one breaks because of context loss, others may too.
5. Development and production differences
Some context-related bugs appear inconsistent between local development and production builds. Always test your workaround in both environments.
FAQ
FAQ
Why does cookies() work in my page but fail in my server action?
Page rendering and route handlers often run in a request context that is properly initialized. In the affected v15.1.0 path, the server action can lose access to that context, so cookies() throws.
Is this caused by using await cookies() versus cookies()?
No. The core problem is not the syntax style. The issue is that the underlying request storage is unavailable when the action executes. Use the API style required by your installed Next.js version, but understand that the regression is contextual.
What is the best long-term fix?
The best fix is to upgrade to a Next.js release that patches the regression. Workarounds like moving logic into a route handler are useful for production stability, but they should not be necessary once the framework bug is resolved.
If your application depends heavily on authentication, session cookies, or CSRF flows inside server actions, treating this as a framework-version issue first will save time. Your code may already be correct; the regression is in how Next.js 15.1.0 binds request context for server actions.