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
{foo}
;
+}
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");
+ });
});