-
Notifications
You must be signed in to change notification settings - Fork 27k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NEXT-1126] Cookies set in middleware missing on Server Component render pass #49442
Comments
Issue also applies to // middleware.ts
// this returns NextResponse.next(), but I've also tried constructing it myself (which produced the same result)
const res = intlMiddleware(req);
res.headers.set('Session-ID', crypto.randomUUID());
// layout.tsx
console.log({
locale: cookies().get("NEXT_LOCALE")?.value, // undefined
header: headers().get("Session-ID"), // null
}); I can't pass these to the edit: I was testing this incorrectly, |
Yeah I'm having the exact same issue. Setting cookies for the first response is pretty critical for most web apps I'd imagine (i.e. session IDs), so hopefully there's a way to make this work. I assume we can't set cookies in the Server Component itself because Next has already begun streaming the response by the time it runs? A hacky workaround for now: use a redirect to trigger a new request with the cookie attached export function middleware(request) {
let sid = request.cookies.get("sid")?.value;
if (!sid) {
let id = crypto.randomUUID();
// @TODO Have to redirect here to ensure cookie is available to root layout
let response = NextResponse.redirect(request.url);
response.cookies.set("sid", id);
return response;
}
} This is obviously not a great solution though since it adds a whole extra request for any visitors without the cookie (and could trigger an infinite loop if their browser blocks cookies). |
@oliverjam oof haha, thanks for the workaround but yeah that's... not great. |
Having the same problem but found a workaround described in a discussion #49444 |
@oliverjam Thank you very much for the workaround. This really helped. |
@timneutkens this is starting to block our work, is there any estimate attached to the Linear issue? The suggested workaround doesn't work for us. If I construct |
Another +1 here, the issue isn't strictly with Server Components, page routing has the same problems.
It's not uncommon to need to ensure the presence of a cookie value such as session ID's |
@timneutkens any news on this? I'm really going to start needing a reliable way of setting a session ID as we're about to start working on authn and payments... |
Is there no remedy for this @timneutkens? I am now actually blocked on this. |
Hi, any update on this? i'm facing the same issue when the application is deployed in a server. |
Please be patient, there's hundreds of open issues and this one is not the top priority over performance/memory issues and such. The issue is in our project for App Router followups. |
@timneutkens I am experiencing the same issue. Even the work around does not work as it ends up in a redirect hell. I have jotted down my concerns here: #53428. I would appreciate it if anyone could help to fix this. It would have been much straight forward if the cookies set are already available in the app router. But during the work around, I faced another glitch where the cookie set is undefined in a subsequent request which I am unsure what it happens so. |
Hello everyone! I wanted to share a similar experience I had. In a previous case, I was having trouble getting the cookie to save correctly in NextJS. At first, everything was working fine, but suddenly it stopped working. After doing some investigation, I realized that the length of the backend response played a role in this issue. It turned out I had a token in my backend that was included in the response. At one point, I increased the amount of data in the response, and this resulted in the response exceeding 4096 characters. Surprisingly, this had an impact on the ability to save the cookie correctly on the client side. Once I adjusted the size of the backend response to be below this limit, the problem was resolved, and the cookie started saving again. I'm not sure if this could be relevant to the issue you're experiencing, but I thought it would be helpful to share my experience just in case. Hope this helps in some way! |
Is there a workaround for this that doesn't return a redirect to the user? |
@Mertiin there isn't unfortunately. we're just gonna have to be patient (this is blocking us from running experiments, very painful) |
This happens because cookies are read from the For cookies set in middleware, you can work around this problem using the mechanism added in #41380 — this allows middleware to “override” what Next will tell Server Components about the value of the The following is an import { NextResponse, type NextRequest } from 'next/server';
import { ResponseCookies, RequestCookies } from 'next/dist/server/web/spec-extension/cookies';
/**
* Copy cookies from the Set-Cookie header of the response to the Cookie header of the request,
* so that it will appear to SSR/RSC as if the user already has the new cookies.
*/
function applySetCookie(req: NextRequest, res: NextResponse): void {
// parse the outgoing Set-Cookie header
const setCookies = new ResponseCookies(res.headers);
// Build a new Cookie header for the request by adding the setCookies
const newReqHeaders = new Headers(req.headers);
const newReqCookies = new RequestCookies(newReqHeaders);
setCookies.getAll().forEach((cookie) => newReqCookies.set(cookie));
// set “request header overrides” on the outgoing response
NextResponse.next({
request: { headers: newReqHeaders },
}).headers.forEach((value, key) => {
if (key === 'x-middleware-override-headers' || key.startsWith('x-middleware-request-')) {
res.headers.set(key, value);
}
});
} Example usagemiddleware.tsexport default function middleware(req: NextRequest) {
// Set cookies on your response
const res = NextResponse.next();
res.cookies.set('foo', 'bar');
// Apply those cookies to the request
applySetCookie(req, res);
return res;
} app/page.tsximport { cookies } from 'next/headers';
export default function MyPage() {
console.log(cookies().get('foo')); // logs “bar”
// ...
} |
Hi, I attempted your fix and after the redirect i'm still not seeing the cookie in the front end. Which redirects me back to the login. import { jwtVerify, importSPKI } from "jose";
import {
ResponseCookies,
RequestCookies,
} from "next/dist/server/web/spec-extension/cookies";
import { NextResponse, NextRequest } from "next/server";
/**
* Copy cookies from the Set-Cookie header of the response to the Cookie header of the request,
* so that it will appear to SSR/RSC as if the user already has the new cookies.
*/
function applySetCookie(req: NextRequest, res: NextResponse): void {
// parse the outgoing Set-Cookie header
const setCookies = new ResponseCookies(res.headers);
// Build a new Cookie header for the request by adding the setCookies
const newReqHeaders = new Headers(req.headers);
const newReqCookies = new RequestCookies(newReqHeaders);
setCookies.getAll().forEach((cookie) => newReqCookies.set(cookie));
// set “request header overrides” on the outgoing response
NextResponse.next({
request: { headers: newReqHeaders },
}).headers.forEach((value, key) => {
if (
key === "x-middleware-override-headers" ||
key.startsWith("x-middleware-request-")
) {
res.headers.set(key, value);
}
});
}
export async function middleware(request: NextRequest) {
const jwt = request.cookies.get("auth_token");
if (jwt) {
try {
const secret = Buffer.from(process.env.SECRET, "base64");
const key = await importSPKI(secret.toString(), "RS256");
await jwtVerify(jwt.value, key);
if (request.url.includes("/login")) {
const response = NextResponse.redirect(new URL("/", request.url));
response.cookies.set("auth_token", jwt.value, {
path: "/",
sameSite: "none",
secure: true,
});
applySetCookie(request, response);
return response;
}
const response = NextResponse.next();
response.cookies.set("auth_token", jwt.value, {
path: "/",
sameSite: "none",
secure: true,
});
applySetCookie(request, response);
return response;
} catch (e) {
console.log(e);
request.cookies.delete("auth_token");
return NextResponse.redirect(new URL("/login", request.url));
}
} else {
if (request.url.includes("/login")) {
return NextResponse.next();
}
return NextResponse.redirect(new URL("/login", request.url));
}
} Does anyone still encounter this issue? |
Yeah when using locally i can see the header, but when i deploy it is not working. @controversial Do you have any idea about it ? |
You could also try this workaround that doesn’t require any changes to middleware. // middleware.ts
import { NextRequest, NextResponse } from "next/server";
export default function middleware() {
const response = NextResponse.next();
if (request.cookies.has("test")) {
console.log("middleware: delete test");
response.cookies.delete("test");
} else {
const value = crypto.randomUUID();
console.log("middleware: create test", value);
response.cookies.set({
name: "test",
value: value,
maxAge: 30,
});
}
return response;
}
// page.tsx or layout.tsx
import { getCookie } from "cookie-util";
export default async function Test() {
const test = getCookie("test");
console.log("page:", test);
return <p>{JSON.stringify(test)}</p>;
}
// cookie-util.ts
import {
RequestCookie,
parseCookie,
} from "next/dist/compiled/@edge-runtime/cookies";
import { cookies, headers } from "next/headers";
export function getCookie(cookieName: string): RequestCookie | undefined {
const allCookiesAsString = headers().get("Set-Cookie");
if (!allCookiesAsString) {
return cookies().get(cookieName);
}
const allCookiesAsObjects = allCookiesAsString
.split(", ")
.map((singleCookieAsString) => parseCookie(singleCookieAsString.trim()));
const targetCookieAsObject = allCookiesAsObjects.find(
(singleCookieAsObject) =>
typeof singleCookieAsObject.get(cookieName) == "string",
);
if (!targetCookieAsObject) {
return cookies().get(cookieName);
}
return {
name: cookieName,
value: targetCookieAsObject.get(cookieName) ?? "",
};
}
// output
middleware: create test f54a058f-fcb8-47ab-b2c2-cf42e42f4c17
page: { name: 'test', value: 'f54a058f-fcb8-47ab-b2c2-cf42e42f4c17' }
middleware: delete test
page: { name: 'test', value: '' } |
This what i have done also but i really would like Next.js team to take Set-Cookie into account too. |
In middleware, set coockie works localy (both in dev and prod) without any fix or workaround.
Any idea when this will be fixed please ? |
We fixed using output: 'standalone' in our next.config.ts. For some unknown reason when we deploy our middleware file is not detected. |
@Tobikblom for some reason in recent versions of next (14.0.0+ ?) the workaround stopped working 😞 The I suspect it's caused by the fact that middleware runs in different process than RSC, and the RSC pass |
I've also encountered a similar issue. When I try to manipulate cookies by requesting api/route.ts on the server-side page, using the Cookies.set operation as described in the Next.js documentation, the client-side cookie doesn't get updated. However, if I request api/route.ts within a client-side component, the client's browser cookie does get updated. Consequently, I've consulted the documentation for next-cookies, where certain scenarios that prevent cookie operations from taking effect are explicitly noted. Could someone explain why this occurs? |
@timneutkens we are trying to be patient, as you asked back on July 13th, 2023. But has the team been able to address this error since then? |
@controversial @andre-muller I think I finally found out the fix which took me too many hours. https://nextjs.org/docs/app/api-reference/functions/next-response#next Cookies are read-only in server components and allow you to read incoming HTTP request cookies. However, when you set-cookie, it modifies the set-cookie header on the HTTP response. For the incoming request to contain your newly set cookie, you can add it to the incoming request.
Even though it took me many hours, I would ask the NextJS team to make this more clear in the documentation. The pattern is quite unintuitive. |
@kylekz @StevenLangbroek Could you also please test the above fix? |
Honestly, I've moved on from the project where I need this, and have stopped using and recommending NextJS or Vercel. If the company acting as its steward can't prioritize a bugfix over [insert swanky AI feature], I'm putting my teams and their commitments at risk. |
Sorry I accidentally deleted a reply of mine, but just to reiterate: your solution was mentioned before, and did / does not work on production. |
@StevenLangbroek Can you link to where it was mentioned? My solution is setting While I agree that the lack of official comment is frustrating, there are still folks trying to resolve this issue so it would be great, if there is any working solution, to document it. |
Here: #49442 (comment) |
I appreciate and value that effort, I'm just no longer interested in contributing, given the lack of interest by the owner of the project and the fact that I'm not currently using it. I'm sure others will gladly help you test your solution. Good luck! |
@StevenLangbroek Fair, again I share your frustration. Several fairly standard and simple use cases in middleware involve setting a cookie that needs to be accessed inside a server component that should be covered by docs:
It would be great to get an official voice on how to do it w/o relying on the community to figure it out. For other folks reading this, please still test out the solution I posted above. I would like to get external validation that it works. |
I'm unsure what cases your workaround is supposed to fix. You're just manually setting the cookie on the instance. It doesn't address the underlying issue, it doesn't help with the other cases described in this thread, it doesn't work across requests boundary nor edge/node boundaries. |
Sorry I'm late to the @ tag but I have to agree with @Thinkscape here. That solution is exactly what I was doing in the first place and it didn't work. |
@controversial solution worked for me. I need to set cookies in a middleware response and read on I don't know exactly what this have to do with the request but without it, the trick does not work NextResponse.next({
request: { headers: newReqHeaders },
}).headers.forEach((value, key) => {
if (key === 'x-middleware-override-headers' || key.startsWith('x-middleware-request-')) {
res.headers.set(key, value);
}
}); Just be careful when deletting cookies, especially if you're validating something based on the cookie's existance, because it'll still exist but with max age 0 |
@Thinkscape @kylekz Can you please provide a reproduction? I'm unable to reproduce, even on Vercel prod. |
we're gonna look at tackling this soon, thank you for your patience |
### What Cookies set/updated/removed in middleware won't be accessible during the render in which they were set ### Why Middleware will properly set a `set-cookie` header to inform the client of the cookie change, but this means the `AsyncLocalStorage` context containing the cookies value wouldn't be updated until the next time the request headers were parsed. In other words, on the first request the cookie would be sent but wouldn't be available in the `cookies()` context. And then the following request would properly have the cookie values. ### How This uses a proxy on the `ResponseCookies` used in middleware to add a middleware override header with the cookie value. When we instantiate the cached cookies, we merge in whatever headers would have been set by middleware, so that they're available in the same render that invoked middleware. ### Test Plan This changeset adds a test to confirm cookies set/deleted in middleware are available in a single pass. Verified with a deployment [here](https://vtest314-e2e-tests-ldx7olfl1-ztanner.vercel.app/rsc-cookies). Fixes #49442 Closes NEXT-1126
This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you. |
### What Cookies set/updated/removed in middleware won't be accessible during the render in which they were set ### Why Middleware will properly set a `set-cookie` header to inform the client of the cookie change, but this means the `AsyncLocalStorage` context containing the cookies value wouldn't be updated until the next time the request headers were parsed. In other words, on the first request the cookie would be sent but wouldn't be available in the `cookies()` context. And then the following request would properly have the cookie values. ### How This uses a proxy on the `ResponseCookies` used in middleware to add a middleware override header with the cookie value. When we instantiate the cached cookies, we merge in whatever headers would have been set by middleware, so that they're available in the same render that invoked middleware. ### Test Plan This changeset adds a test to confirm cookies set/deleted in middleware are available in a single pass. Verified with a deployment [here](https://vtest314-e2e-tests-ldx7olfl1-ztanner.vercel.app/rsc-cookies). Fixes #49442 Closes NEXT-1126
### What Cookies set/updated/removed in middleware won't be accessible during the render in which they were set ### Why Middleware will properly set a `set-cookie` header to inform the client of the cookie change, but this means the `AsyncLocalStorage` context containing the cookies value wouldn't be updated until the next time the request headers were parsed. In other words, on the first request the cookie would be sent but wouldn't be available in the `cookies()` context. And then the following request would properly have the cookie values. ### How This uses a proxy on the `ResponseCookies` used in middleware to add a middleware override header with the cookie value. When we instantiate the cached cookies, we merge in whatever headers would have been set by middleware, so that they're available in the same render that invoked middleware. ### Test Plan This changeset adds a test to confirm cookies set/deleted in middleware are available in a single pass. Verified with a deployment [here](https://vtest314-e2e-tests-ldx7olfl1-ztanner.vercel.app/rsc-cookies). Fixes #49442 Closes NEXT-1126
### What Cookies set/updated/removed in middleware won't be accessible during the render in which they were set ### Why Middleware will properly set a `set-cookie` header to inform the client of the cookie change, but this means the `AsyncLocalStorage` context containing the cookies value wouldn't be updated until the next time the request headers were parsed. In other words, on the first request the cookie would be sent but wouldn't be available in the `cookies()` context. And then the following request would properly have the cookie values. ### How This uses a proxy on the `ResponseCookies` used in middleware to add a middleware override header with the cookie value. When we instantiate the cached cookies, we merge in whatever headers would have been set by middleware, so that they're available in the same render that invoked middleware. ### Test Plan This changeset adds a test to confirm cookies set/deleted in middleware are available in a single pass. Verified with a deployment [here](https://vtest314-e2e-tests-ldx7olfl1-ztanner.vercel.app/rsc-cookies). Fixes #49442 Closes NEXT-1126
### What Cookies set/updated/removed in middleware won't be accessible during the render in which they were set ### Why Middleware will properly set a `set-cookie` header to inform the client of the cookie change, but this means the `AsyncLocalStorage` context containing the cookies value wouldn't be updated until the next time the request headers were parsed. In other words, on the first request the cookie would be sent but wouldn't be available in the `cookies()` context. And then the following request would properly have the cookie values. ### How This uses a proxy on the `ResponseCookies` used in middleware to add a middleware override header with the cookie value. When we instantiate the cached cookies, we merge in whatever headers would have been set by middleware, so that they're available in the same render that invoked middleware. ### Test Plan This changeset adds a test to confirm cookies set/deleted in middleware are available in a single pass. Verified with a deployment [here](https://vtest314-e2e-tests-ldx7olfl1-ztanner.vercel.app/rsc-cookies). Fixes #49442 Closes NEXT-1126
Verify canary release
Provide environment information
Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 22.3.0: Thu Jan 5 20:48:54 PST 2023; root:xnu-8792.81.2~2/RELEASE_ARM64_T6000 Binaries: Node: 18.13.0 npm: 8.19.3 Yarn: N/A pnpm: 7.25.0 Relevant packages: next: 13.4.1 eslint-config-next: 13.1.6 react: 18.2.0 react-dom: 18.2.0
Which area(s) of Next.js are affected? (leave empty if unsure)
App directory (appDir: true), Middleware / Edge (API routes, runtime)
Link to the code that reproduces this issue
https://codesandbox.io/p/sandbox/exciting-tamas-jhekzr
To Reproduce
newSessionId
frommiddleware.ts
, but undefinedsessionId
fromlayout.tsx
.sessionId
is now picked up correctly and logged out fromlayout.tsx
.Describe the Bug
Cookies set in middleware only show up in Server Components from the first request after it was set.
Expected Behavior
I would expect a cookie set in middleware, to be available in Server Components in the render pass in which it was set.
Which browser are you using? (if relevant)
Version 112.0.5615.137 (Official Build) (arm64)
How are you deploying your application? (if relevant)
This happens both locally and on our Vercel preview environments
NEXT-1126
The text was updated successfully, but these errors were encountered: