admin管理员组文章数量:1313149
I'm currently developing a Next.js 14 application and have encountered an issue with modifying cookies. Despite following the Next.js documentation on server actions and cookie manipulation, I'm receiving an error when trying to set cookies during the signout process. The error message states: "Cookies can only be modified in a Server Action or Route Handler.
Here's the relevant code snippet from my project:
// pwa/src/utils/globalActions.ts
'use server';
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
export const signout = async () => {
cookies.set('token', '', { maxAge: -1 });
cookies.set('refreshToken', '', { maxAge: -1 });
cookies.set('user', '', { maxAge: -1 });
return redirect('/auth/signin');
};
// pwa/src/utils/apiAxios.ts (This file is used by both client and server ponents)
import axios, {
type AxiosResponse,
type AxiosError,
type AxiosInstance,
} from 'axios';
import { signout } from './globalActions';
const Axios = axios.create({
baseURL: ENTRYPOINT,
headers: {
Accept: 'application/ld+json',
'Content-Type': 'application/ld+json',
},
});
Axios.interceptors.response.use(
response => response,
async (error: AxiosError<ApiResponseError>) => {
if (error.status === 401 || error.response?.status === 401) {
await signout();
}
return Promise.reject(error);
},
);
I've checked my usage against the Next.js documentation and made sure to use the server execution context ('use server';). Yet, the issue persists. I suspect I might be missing a detail in how Next.js 14 handles cookies in server actions or there might be a specific configuration step I've overlooked.
Has anyone encountered this issue before, or does anyone have insights on how to properly modify cookies within server ponents in Next.js 14? Any help or pointers would be greatly appreciated.
I'm currently developing a Next.js 14 application and have encountered an issue with modifying cookies. Despite following the Next.js documentation on server actions and cookie manipulation, I'm receiving an error when trying to set cookies during the signout process. The error message states: "Cookies can only be modified in a Server Action or Route Handler.
Here's the relevant code snippet from my project:
// pwa/src/utils/globalActions.ts
'use server';
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
export const signout = async () => {
cookies.set('token', '', { maxAge: -1 });
cookies.set('refreshToken', '', { maxAge: -1 });
cookies.set('user', '', { maxAge: -1 });
return redirect('/auth/signin');
};
// pwa/src/utils/apiAxios.ts (This file is used by both client and server ponents)
import axios, {
type AxiosResponse,
type AxiosError,
type AxiosInstance,
} from 'axios';
import { signout } from './globalActions';
const Axios = axios.create({
baseURL: ENTRYPOINT,
headers: {
Accept: 'application/ld+json',
'Content-Type': 'application/ld+json',
},
});
Axios.interceptors.response.use(
response => response,
async (error: AxiosError<ApiResponseError>) => {
if (error.status === 401 || error.response?.status === 401) {
await signout();
}
return Promise.reject(error);
},
);
I've checked my usage against the Next.js documentation and made sure to use the server execution context ('use server';). Yet, the issue persists. I suspect I might be missing a detail in how Next.js 14 handles cookies in server actions or there might be a specific configuration step I've overlooked.
Has anyone encountered this issue before, or does anyone have insights on how to properly modify cookies within server ponents in Next.js 14? Any help or pointers would be greatly appreciated.
Share Improve this question asked Jan 31, 2024 at 0:13 Léo LhuileLéo Lhuile 1032 silver badges4 bronze badges4 Answers
Reset to default 1Cookies can only be modified in a Server Action or Route Handler.
this message indicates that you should be on the server side but as far as I know axios.interceptors.response
gets called immediately after axios receives a response from the server and before the promise returned by the Axios request is resolved.
so you are calling cookies.set
after request is exectuted on the server
According to documentation you have to set cookies inside middleware if you are using NextJS 13 or newer version.
Good to know: `cookies()` is a Dynamic Function whose returned values cannot
be known ahead of time. Using it in a layout or page will opt a route into
dynamic rendering at request time.
After adding middleware you can read cookies anywhere you want but you have to mark the function as "use server"
.
Here is a YouTube video that explains better.
Here is the explanation that how i am doing this
For signout
feature i am redirecting the page to /log-out
and inside middleware
i am checking the current url. If current url contains /log-out
then i am clearing every cookies and then redirect to the main page.
import { NextResponse } from 'next/server';
import { NextRequest as NextRequestType } from 'next/server';
const ERROR_FALLBACK = "/log-out"
export async function middleware(req: NextRequestType) {
let token = req.cookies.get("ACCESS_TOKEN");
if (req.nextUrl.pathname.includes(ERROR_FALLBACK)) {
token = undefined;
}
if (!token) {
try {
const initBody = {
device: { osType: 'web' }
};
const { data: result } = await axios.post(`${process.env.NEXT_PUBLIC_BASE_URL}${Endpoints.INIT}`, initBody);
const { token } = result.data;
let response: NextResponse
if (req.nextUrl.pathname.includes(ERROR_FALLBACK)) {
response = NextResponse.redirect(new URL('/', req.url));
} else {
response = NextResponse.next();
}
response.cookies.set('ACCESS_TOKEN', encrypt(token, HashAlgorithm.AES), {
maxAge: 25 * 365 * 24 * 60 * 60 * 1000
});
return response;
} catch (error) {
return NextResponse.redirect(new URL(ERROR_FALLBACK, req.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: "/((?!api|static|.*\\..*|_next).*)",
};
This middleware
function is called everytime before page is loading if current url matches with matcher.
For more information about matcher you can check the documentation.
Explanation of the above code
In my backend app every user has access token. By calling this endpoint it returns the token. Inside middleware i am sending a request to my backend if there is no token inside cookies.
If current endpoint contains my FALLBACK url then i am resetting by updating it.
But in your case you want to clear cookies. So you can do this like below.
if (req.nextUrl.pathname.includes(ERROR_FALLBACK)) {
let response: NextResponse = NextResponse.redirect(new URL('/auth/signin', req.url));
response.cookies.delete('token');
response.cookies.delete('refreshToken');
response.cookies.delete('user');
return response;
}
How to redirect inside axios?
You need to send a message to ui and listen to it inside client ponent. Here is a explanation of how you can do this.
First write a helper function that emits a message and listener for this.
import { EventEmitter } from 'events';
const emitter = new EventEmitter();
export const navigateTo = (path: string) => {
emitter.emit('navigate', path);
};
export const onNavigate = (listener) => {
emitter.on('navigate', listener);
};
And now write a helper ponent that listens this event in client.
"use client"
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { onNavigate } from '@/lib/Events';
export default function EventListener({ children }) {
const router = useRouter();
useEffect(() => {
const handleNavigation = (path: string) => {
router.replace(path);
};
onNavigate(handleNavigation);
}, [router]);
return children;
}
After creating above ponent add it to your top ponent. In my case i added this ponent inside layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<EventListener>
{children}
</EventListener>
<Toaster />
</body>
</html>
);
}
Now you can update your signout()
function like below
export const signout = () => {
navigateTo(WebLink.ERROR_FALLBACK);
};
After this you can call signout
function wherever you want.
How to set token to axios header?
You can read cookies
wherever you want if you mark the ponent or function as "use server"
"use server"
export async function getToken() {
const a = await getCookieValue(COOKIE_KEY.TOKEN);
return a;
}
async function getCookieValue(key: string) {
return cookies().get(key)?.value;
}
And axios requester object
export const Requester = axios.create({
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
timeout: 20000,
});
Requester.interceptors.request.use(
async (config) => {
const token = await getToken();
if (token) {
config.headers['token'] = token;
}
return config;
},
(error) => {
console.warn(error);
return Promise.reject(error);
},
);
Requester.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response?.data?.meta?.statusCode === 101) {
endSession();
return {};
}
if (error.response?.data?.meta?.statusCode === 104) {
endSession();
return {};
}
return Promise.reject(error);
},
);
Importing cookies inplace Dynamically worked for me:
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response.status === 400 && !originalRequest._retry) {
//......
const response = await axios.post(....)
const { cookies } = await import('next/headers'); //<-----HERE
const cookieInstance = await cookies();
const cookieString = response.headers['set-cookie'] as any;
const access = parseCookieHelpers(cookieString);
if (access) cookieInstance.set(access.name, access.value, access.options as any);
return await api.request(originalRequest);
} catch (refreshError) {
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
The Next.js Cookie Documentation is a bit misleading with how you can actually access cookies.
You can apparently read cookies in a server ponent, but you cannot set them. I've found that trying to do anything with cookies within a server ponent, I was receiving the error that it must be in a Server Action or Route Handler.
I utilized useEffect()
within a client ponent to be able to access & set cookies from my server action:
"use client"
import { useEffect } from "rect";
import { getCookie } from "@/app/_actions/cookies";
export default function Home() {
const [cookie, setCookie] = useState(null);
useEffect(() => {
async function fetchCookies() {
const cookieData = await getCookie(); // Server action
setCookie(cookieData);
}
}, [cookie]);
if (cookie) {
// ... logic
}
}
app/_actions/cookie.ts
"use server"
import { cookies } from "next/headers";
export async function getCookie() {
const cookie = (await cookies()).get("cookie_name");
return cookie?.value;
}
本文标签: javascriptNextjs 14 Server Actions Issue Modifying CookiesStack Overflow
版权声明:本文标题:javascript - Next.js 14 Server Actions: Issue Modifying Cookies - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741937056a2405919.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论