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