Error: Cookies can only be modified in a Server Action or Route Handler - Next.js 15 with cookies()
Unanswered
Siberian posted this in #help-forum
SiberianOP
Hello everyone,
I'm working with Next.js 15 and trying to set a session cookie after a user clicks a forgot password link. However, I'm encountering the following error when attempting to set the cookie:
In my code, I'm calling a setSessionCookie function to create a session token and set the cookie for the session. The error occurs when I try to use the cookies() API to set the cookie. From the error message, it seems that I am trying to modify cookies outside of a valid server context (Server Action or Route Handler).
Here is the relavent code:
As well as where the action is called:
I'm working with Next.js 15 and trying to set a session cookie after a user clicks a forgot password link. However, I'm encountering the following error when attempting to set the cookie:
Error: Cookies can only be modified in a Server Action or Route Handler.
In my code, I'm calling a setSessionCookie function to create a session token and set the cookie for the session. The error occurs when I try to use the cookies() API to set the cookie. From the error message, it seems that I am trying to modify cookies outside of a valid server context (Server Action or Route Handler).
Here is the relavent code:
// session.ts
export async function setSessionCookie(userId: string, ipAddress: string): Promise<void> {
const sessionToken = await generateSessionToken();
const session = await createSession(sessionToken, userId, ipAddress);
const cookieStore = await cookies();
cookieStore.set("session", sessionToken, {
httpOnly: true,
path: "/",
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: (session.expiresAt - Date.now()) / 1000,
});
}
// forgotPasswordCodeAction.ts
"use server";
import prisma from "@/lib/db/prisma/prisma";
import { setSessionCookie } from "@/lib/auth/session";
import { redirect } from "next/navigation";
import { getIpAddress } from "../../utils";
export default async function forgotPasswordCodeAction(code: string) {
const user = await prisma.user.findFirst({
where: { forgotPasswordLink: code },
});
if (!user?.id) {
redirect("/");
}
await setSessionCookie(user.id, await getIpAddress());
redirect("/settings/account");
}
As well as where the action is called:
13 Replies
SiberianOP
import forgotPasswordCodeAction from "./actions";
export default async function ForgotPasswordCodePage({
params,
}: {
params: Promise<{ code: string }>;
}) {
const { code } = await params;
console.log(code);
await forgotPasswordCodeAction(code);
return <h1>Processing request</h1>;
}
Thank you.
SiberianOP
This is what I get in the terminal by the way:
24 | const cookieStore = await cookies();
25 |
> 26 | cookieStore.set("session", sessionToken, {
| ^
27 | httpOnly: true,
28 | path: "/",
29 | secure: process.env.NODE_ENV === "production", {
I've had this issue before, and it seemed like the example at the top of the [Next.js cookies documentation](https://nextjs.org/docs/app/api-reference/functions/cookies) was misleading (for me) as it only includes reading the cookies, and I automatically assumed you could write them too.
You can only read cookies from server components, and write them from server actions. So, I found I could set them by calling the action within
Which in the end (when I think about it) makes sense as a server component wouldn't be able to write client data. (?)
You can only read cookies from server components, and write them from server actions. So, I found I could set them by calling the action within
useEffect
in a client component, rather than trying to write them from a server component."use client"
import { useEffect } from "rect";
import { getCookie } from "@/app/_actions/cookies";
export function Component() {
const [cookie, setCookie] = useState(null);
useEffect(() => {
async function fetchCookies() {
const cookieData = await getCookie(); // Server action
setCookie(cookieData);
}
}, [cookie]);
if (cookie) {
// ... logic
}
}
Which in the end (when I think about it) makes sense as a server component wouldn't be able to write client data. (?)
No since the server component can only read cookies not write them
@clarity No since the server component can only read cookies not write them
SiberianOP
Alright, I'll use the client compoenent approach and call the action like:
Thanks.
"use client";
import { useEffect, useState } from "react";
import forgotPasswordCodeAction from "./actions";
import { useRouter } from "next/navigation";
export default function ForgotPasswordCodePage({
params,
}: {
params: { code: string };
}) {
const router = useRouter();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function handleForgotPassword() {
try {
await forgotPasswordCodeAction(params.code);
router.push("/settings/account");
} catch (err) {
console.error("Error processing forgot password:", err);
setError("Failed to process request.");
} finally {
setLoading(false);
}
}
handleForgotPassword();
}, [params.code, router]);
if (loading) return <h1>Processing request...</h1>;
if (error) return <h1>{error}</h1>;
return <h1>Redirecting...</h1>;
}
Thanks.
Asian black bear
The reason why you got the issue in the first place is that cookies (which are basically headers) cannot be written after a server has finished sending headers and is streaming the body/content of a document.
Consequently a page is just the result of a GET request and should not mutate on its own.
Both of these imply that you cannot write cookies within a page/server component and instead it can only happen as part of a route handler or server action that is invoked from the client.
invoked from the client
thats the most important part
calling a server action from a page on load, will not work.
neither will a route handler