Skip to content
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

Closed
1 task done
StevenLangbroek opened this issue May 8, 2023 · 55 comments · Fixed by #65008
Closed
1 task done
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked Runtime Related to Node.js or Edge Runtime with Next.js.

Comments

@StevenLangbroek
Copy link

StevenLangbroek commented May 8, 2023

Verify canary release

  • I verified that the issue exists in the latest Next.js 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

  • Load the URL in your browser
  • Check logs. It should log a UUID newSessionId from middleware.ts, but undefined sessionId from layout.tsx.
  • If you refresh the page, sessionId is now picked up correctly and logged out from layout.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

@StevenLangbroek StevenLangbroek added the bug Issue was opened via the bug report template. label May 8, 2023
@github-actions github-actions bot added area: app App directory (appDir: true) Runtime Related to Node.js or Edge Runtime with Next.js. labels May 8, 2023
@StevenLangbroek
Copy link
Author

StevenLangbroek commented May 8, 2023

Issue also applies to headers set on NextResponse.next() after construction:

// 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 NextReponse.next static method, as next-intl is in charge of that.

edit: I was testing this incorrectly, headers work actually, apologies for any confusion. the issue with cookies remains.

@oliverjam
Copy link

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).

@timneutkens timneutkens added the linear: next Confirmed issue that is tracked by the Next.js team. label May 9, 2023
@timneutkens timneutkens changed the title Cookies set in middleware missing on Server Component render pass [NEXT-1126] Cookies set in middleware missing on Server Component render pass May 9, 2023
@StevenLangbroek
Copy link
Author

@oliverjam oof haha, thanks for the workaround but yeah that's... not great.

@tobidemski
Copy link

tobidemski commented May 11, 2023

Having the same problem but found a workaround described in a discussion #49444

@rahulbaruah
Copy link

@oliverjam Thank you very much for the workaround. This really helped.

@StevenLangbroek
Copy link
Author

StevenLangbroek commented Jun 9, 2023

@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 ResponseCookies myself with the return value of headers(), it's entirely empty.

@nicholas-c
Copy link

nicholas-c commented Jun 14, 2023

Another +1 here, the issue isn't strictly with Server Components, page routing has the same problems.

  1. Set cookies in middleware (as per docs)
  2. Return response in middleware (as per docs)
  3. Try to access that cookie in getServerSideProps <- cookie isn't available here

It's not uncommon to need to ensure the presence of a cookie value such as session ID's

@StevenLangbroek
Copy link
Author

@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...

@StevenLangbroek
Copy link
Author

Is there no remedy for this @timneutkens? I am now actually blocked on this.

@Ramiro-luengo
Copy link

Hi, any update on this? i'm facing the same issue when the application is deployed in a server.

@timneutkens
Copy link
Member

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.

@varghesethomase
Copy link

@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.

@JuanTG08
Copy link

JuanTG08 commented Aug 5, 2023

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!

@Mertiin
Copy link

Mertiin commented Aug 15, 2023

Is there a workaround for this that doesn't return a redirect to the user?

@StevenLangbroek
Copy link
Author

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)

@controversial
Copy link
Contributor

controversial commented Aug 16, 2023

This happens because cookies are read from the Cookies header on the request, whereas you set cookies using the Set-Cookie header on the response. For a single given page load, Middleware and Server Components executed as part of a single request/response cycle. When it receives a Set-Cookie header on a response, the browser can’t then go back in time to put a different Cookie header on the request it already sent (the reason the redirect workaround works is that it creates a second request, which will carry the updated Cookie header).


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 Cookie header on the request. Overriding the Cookie header will make Server Component functions like cookies() return different values, even for the same request.

The following is an applySetCookie(req, res) function that you can drop in to your existing middlewares, which will make the cookies() function in RSC reflect the newly-set cookies as expected

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 usage

middleware.ts
export 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.tsx
import { cookies } from 'next/headers';
export default function MyPage() {
  console.log(cookies().get('foo')); // logs “bar”
  // ...
}

@Ramiro-luengo
Copy link

Ramiro-luengo commented Sep 5, 2023

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.
Only when the application is deployed though, it doesn't happen when using it locally.

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?

@frkozbk
Copy link

frkozbk commented Sep 18, 2023

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. Only when the application is deployed though, it doesn't happen when using it locally.

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 ?

@Tobikblom
Copy link

Tobikblom commented Sep 23, 2023

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: '' }

@quiloos39
Copy link

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.

@ElfieBest
Copy link

In middleware, set coockie works localy (both in dev and prod) without any fix or workaround.
But when in Vercel deployement it still doesn't work even with the fix :

     // Apply those cookies to the request (Set-Cookie header of the response to the Cookie header of the request)
    applySetCookie(request, res); 

Any idea when this will be fixed please ?

@frkozbk
Copy link

frkozbk commented Oct 17, 2023

In middleware, set coockie works localy (both in dev and prod) without any fix or workaround. But when in Vercel deployement it still doesn't work even with the fix :

     // Apply those cookies to the request (Set-Cookie header of the response to the Cookie header of the request)
    applySetCookie(request, res); 

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.

@Thinkscape
Copy link

Thinkscape commented Nov 15, 2023

You could also try this workaround that doesn’t require any changes to middleware.

@Tobikblom for some reason in recent versions of next (14.0.0+ ?) the workaround stopped working 😞

The headers().get("Set-Cookie") doesn't seem to contain the cookies set by middleware.

I suspect it's caused by the fact that middleware runs in different process than RSC, and the RSC pass headers() merely returns the original request headers.

@loakliu
Copy link

loakliu commented Mar 27, 2024

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?

@andre-muller
Copy link

@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?

@polarizing96
Copy link

@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.

const setCookie = (request: NextRequest, response: NextResponse, cookie: ResponseCookie) => {
  request.cookies.set(cookie)
  response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  })
  response.cookies.set(cookie)
  return response;
}

export async function middleware(request: NextRequest, response: NextResponse) {
  response = setCookie(request, response, { name: 'cookieName', value: 'cookieValue' })
  return response;
}

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.

@polarizing96
Copy link

@kylekz @StevenLangbroek Could you also please test the above fix?

@StevenLangbroek
Copy link
Author

@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.

@StevenLangbroek
Copy link
Author

Kyle Steven Langbroek Could you also please test the above fix?

Sorry I accidentally deleted a reply of mine, but just to reiterate: your solution was mentioned before, and did / does not work on production.

@polarizing96
Copy link

polarizing96 commented Apr 7, 2024

@StevenLangbroek Can you link to where it was mentioned? My solution is setting request.cookies.set, I do not see it mentioned anywhere in this thread.

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.

@StevenLangbroek
Copy link
Author

@StevenLangbroek Can you link to where it was mentioned? My solution is setting request.cookies.set, I do not see it mentioned anywhere in this thread.

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)

@StevenLangbroek
Copy link
Author

@StevenLangbroek Can you link to where it was mentioned? My solution is setting request.cookies.set, I do not see it mentioned anywhere in this thread.

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.

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!

@polarizing96
Copy link

polarizing96 commented Apr 7, 2024

@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:

  • Setting a unique session ID
  • Refreshing an authorization token

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.

@Thinkscape
Copy link

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.

@kylekz
Copy link

kylekz commented Apr 18, 2024

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.

@vitormarkis
Copy link

@controversial solution worked for me. I need to set cookies in a middleware response and read on getServerSideProps

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

@polarizing96
Copy link

polarizing96 commented Apr 18, 2024

@Thinkscape @kylekz Can you please provide a reproduction?

I'm unable to reproduce, even on Vercel prod.

@feedthejim
Copy link
Contributor

we're gonna look at tackling this soon, thank you for your patience

ztanner added a commit that referenced this issue Apr 25, 2024
### 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
Copy link
Contributor

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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 10, 2024
lubieowoce pushed a commit that referenced this issue Aug 23, 2024
### 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
lubieowoce pushed a commit that referenced this issue Aug 23, 2024
### 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
lubieowoce pushed a commit that referenced this issue Aug 28, 2024
### 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
lubieowoce pushed a commit that referenced this issue Sep 2, 2024
### 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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked Runtime Related to Node.js or Edge Runtime with Next.js.
Projects
None yet
Development

Successfully merging a pull request may close this issue.