Repository: opennextjs/opennextjs-aws
Pull request #851: fix: Ensure cookies set in middleware are available on initial render
Author: login: sommeeeer
Created: 2025-05-03T22:57:11Z | Merged: 2025-05-06T13:58:16Z

For #850

We should remove `x-middleware-set-cookie` from `filteredHeaders`. Instead we remove it on the `overwrittenResponseHeaders` which will be passed to the response. 

`reqProps` in `requestHandler.ts` will use the original `headers` object, and pass it into `processRequest`. This makes sure we will see the header on initial render when using `cookies().get()` from Next.


Diff:
diff --git a/.changeset/breezy-adults-behave.md b/.changeset/breezy-adults-behave.md
new file mode 100644
index 000000000..00c02ebeb
--- /dev/null
+++ b/.changeset/breezy-adults-behave.md
@@ -0,0 +1,5 @@
+---
+"@opennextjs/aws": patch
+---
+
+fix: Ensure cookies set in middleware are available on initial render when using `cookies().get()` from Next.js
diff --git a/examples/app-router/app/cookies/page.tsx b/examples/app-router/app/cookies/page.tsx
new file mode 100644
index 000000000..60ef69a70
--- /dev/null
+++ b/examples/app-router/app/cookies/page.tsx
@@ -0,0 +1,7 @@
+import { cookies } from "next/headers";
+
+export default async function Page() {
+  const foo = (await cookies()).get("foo")?.value;
+
+  return <div data-testid="foo">{foo}</div>;
+}
diff --git a/examples/app-router/middleware.ts b/examples/app-router/middleware.ts
index 88a6c140a..a634eb4a2 100644
--- a/examples/app-router/middleware.ts
+++ b/examples/app-router/middleware.ts
@@ -28,6 +28,11 @@ export function middleware(request: NextRequest) {
     const u = new URL("https://opennext.js.org/share.png");
     return NextResponse.rewrite(u);
   }
+  if (path === "/cookies") {
+    const res = NextResponse.next();
+    res.cookies.set("foo", "bar");
+    return res;
+  }
   const requestHeaders = new Headers(request.headers);
   // Setting the Request Headers, this should be available in RSC
   requestHeaders.set("request-header", "request-header");
diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts
index 26f8df333..1a0e09177 100644
--- a/packages/open-next/src/core/requestHandler.ts
+++ b/packages/open-next/src/core/requestHandler.ts
@@ -87,7 +87,11 @@ export async function openNextHandler(
           continue;
         }
         const key = rawKey.slice(MIDDLEWARE_HEADER_PREFIX_LEN);
-        overwrittenResponseHeaders[key] = value;
+        // We skip this header here since it is used by Next internally and we don't want it on the response headers.
+        // This header needs to be present in the request headers for processRequest, so cookies().get() from Next will work on initial render.
+        if (key !== "x-middleware-set-cookie") {
+          overwrittenResponseHeaders[key] = value;
+        }
         headers[key] = value;
         delete headers[rawKey];
       }
diff --git a/packages/open-next/src/core/routing/middleware.ts b/packages/open-next/src/core/routing/middleware.ts
index cd4c47628..2d52e6e89 100644
--- a/packages/open-next/src/core/routing/middleware.ts
+++ b/packages/open-next/src/core/routing/middleware.ts
@@ -115,7 +115,6 @@ export async function handleMiddleware(
   // These are internal headers used by Next.js, we don't want to expose them to the client
   const filteredHeaders = [
     "x-middleware-override-headers",
-    "x-middleware-set-cookie",
     "x-middleware-next",
     "x-middleware-rewrite",
     // We need to drop `content-encoding` because it will be decoded
diff --git a/packages/tests-e2e/tests/appRouter/middleware.cookies.test.ts b/packages/tests-e2e/tests/appRouter/middleware.cookies.test.ts
index 9c92fe604..e35324a1d 100644
--- a/packages/tests-e2e/tests/appRouter/middleware.cookies.test.ts
+++ b/packages/tests-e2e/tests/appRouter/middleware.cookies.test.ts
@@ -1,12 +1,44 @@
 import { expect, test } from "@playwright/test";
 
-test("Cookies", async ({ page, context }) => {
-  await page.goto("/");
+test.describe("Middleware Cookies", () => {
+  test("should be able to set cookies on response in middleware", async ({
+    page,
+    context,
+  }) => {
+    await page.goto("/");
 
-  const cookies = await context.cookies();
-  const from = cookies.find(({ name }) => name === "from");
-  expect(from?.value).toEqual("middleware");
+    const cookies = await context.cookies();
+    const from = cookies.find(({ name }) => name === "from");
+    expect(from?.value).toEqual("middleware");
 
-  const love = cookies.find(({ name }) => name === "with");
-  expect(love?.value).toEqual("love");
+    const love = cookies.find(({ name }) => name === "with");
+    expect(love?.value).toEqual("love");
+  });
+  test("should be able to get cookies set in the middleware with Next's cookies().get()", async ({
+    page,
+  }) => {
+    await page.goto("/cookies");
+
+    expect(await page.getByTestId("foo").textContent()).toBe("bar");
+  });
+  test("should not expose internal Next headers in response", async ({
+    page,
+    context,
+  }) => {
+    const responsePromise = page.waitForResponse((response) =>
+      response.url().includes("/cookies"),
+    );
+
+    await page.goto("/cookies");
+
+    const response = await responsePromise;
+    const headers = response.headers();
+
+    const cookies = await context.cookies();
+    const fooCookie = cookies.find(({ name }) => name === "foo");
+    expect(fooCookie?.value).toBe("bar");
+
+    expect(headers).not.toHaveProperty("x-middleware-set-cookie");
+    expect(headers).not.toHaveProperty("x-middleware-next");
+  });
 });


Reviews:
user: login: khuezy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T00:30:56Z
user: login: sommeeeer
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-04T07:36:59Z
user: login: khuezy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T14:34:16Z
user: login: khuezy
body: LGTM thanks!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T16:40:50Z
user: login: conico974
body: LGTM Thanks
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:58:05Z

Review comments:
diff_hunk: @@ -92,6 +92,8 @@ export async function openNextHandler(
         delete headers[rawKey];
       }
 
+      delete overwrittenResponseHeaders["x-middleware-set-cookie"];
path: packages/open-next/src/core/requestHandler.ts
user: login: khuezy
body: Should we add a comment here to note the reason why this header needs to be deleted here?
created_at: 2025-05-04T00:30:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -92,6 +92,8 @@ export async function openNextHandler(
         delete headers[rawKey];
       }
 
+      delete overwrittenResponseHeaders["x-middleware-set-cookie"];
path: packages/open-next/src/core/requestHandler.ts
user: login: sommeeeer
body: Yeah will do that. 

I might refactor this to be a part of the `for` loop above, and just skip adding the header to `overwrittenResponseHeaders`. Like this:
```ts
if (key !== "x-middleware-set-cookie") {
  overwrittenResponseHeaders[key] = value;
}
```

What should I go for?
created_at: 2025-05-04T07:36:58Z
author_association: COLLABORATOR
diff_hunk: @@ -92,6 +92,8 @@ export async function openNextHandler(
         delete headers[rawKey];
       }
 
+      delete overwrittenResponseHeaders["x-middleware-set-cookie"];
path: packages/open-next/src/core/requestHandler.ts
user: login: khuezy
body: Either would be fine. `delete` opts out of the v8 optimization, so maybe setting it to `undefined` would be better?
created_at: 2025-05-04T14:34:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-03T22:57:15Z
body: ###  🦋  Changeset detected

Latest commit: a3b374d7dec5315e8cb0a6c948e844a314d255ee

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 3 packages</summary>

  | Name             | Type  |
| ---------------- | ----- |
| @opennextjs/aws  | Patch |
| app-pages-router | Patch |
| app-router       | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/sommeeeer/opennextjs-aws/new/fix-middleware-setcookie?filename=.changeset/poor-cooks-retire.md&value=---%0A%22app-router%22%3A%20patch%0A%22%40opennextjs%2Faws%22%3A%20patch%0A%22tests-e2e%22%3A%20patch%0A---%0A%0Afix%3A%20Ensure%20cookies%20set%20in%20middleware%20are%20available%20on%20initial%20render%0A)


author_association: NONE
user: login: pkg-pr-new[bot]
created_at: 2025-05-03T23:00:26Z
body: 
[Open in StackBlitz](https://pkg.pr.new/template/ccbac02d-bf15-41b3-8241-4dc411be2dda)


```
pnpm add https://pkg.pr.new/@opennextjs/aws@851
```


_commit: <a href="https://github.com/opennextjs/opennextjs-aws/runs/41612092245"><code>a1a3504</code></a>_

author_association: NONE