diff --git a/.changeset/shiny-plants-remember.md b/.changeset/shiny-plants-remember.md new file mode 100644 index 000000000..b842e9bcc --- /dev/null +++ b/.changeset/shiny-plants-remember.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": minor +--- + +feat: Add option to align with Next.js execution order for headers in middleware and next.config.js diff --git a/examples/app-router/middleware.ts b/examples/app-router/middleware.ts index e5da8f98c..2c3a5fe34 100644 --- a/examples/app-router/middleware.ts +++ b/examples/app-router/middleware.ts @@ -50,6 +50,12 @@ export function middleware(request: NextRequest) { // Response headers should show up in the client's response headers responseHeaders.set("response-header", "response-header"); + // For dangerous.middlewareHeadersOverrideNextConfigHeaders we need to verify that middleware headers override next.config.js headers. + if (path === "/headers/override-from-middleware") { + responseHeaders.set("e2e-headers", "middleware"); + return NextResponse.json({}, { headers: responseHeaders }); + } + // Set the cache control header with custom swr // For: isr.test.ts if (path === "/isr" && !request.headers.get("x-prerender-revalidate")) { diff --git a/examples/app-router/open-next.config.local.ts b/examples/app-router/open-next.config.local.ts index 63d80481e..27e47e95d 100644 --- a/examples/app-router/open-next.config.local.ts +++ b/examples/app-router/open-next.config.local.ts @@ -11,6 +11,10 @@ export default { }, }, + dangerous: { + middlewareHeadersOverrideNextConfigHeaders: true, + }, + imageOptimization: { override: { wrapper: "dummy", diff --git a/examples/app-router/open-next.config.ts b/examples/app-router/open-next.config.ts index 5ff6edf53..bc9430bb8 100644 --- a/examples/app-router/open-next.config.ts +++ b/examples/app-router/open-next.config.ts @@ -8,6 +8,9 @@ const config = { }, }, functions: {}, + dangerous: { + middlewareHeadersOverrideNextConfigHeaders: true, + }, buildCommand: "npx turbo build", }; diff --git a/packages/open-next/src/core/routingHandler.ts b/packages/open-next/src/core/routingHandler.ts index 8a70076ad..951be07c5 100644 --- a/packages/open-next/src/core/routingHandler.ts +++ b/packages/open-next/src/core/routingHandler.ts @@ -129,10 +129,22 @@ export default async function routingHandler( return middlewareEventOrResult; } - headers = { - ...middlewareEventOrResult.responseHeaders, - ...headers, - }; + const middlewareHeadersPrioritized = + globalThis.openNextConfig.dangerous + ?.middlewareHeadersOverrideNextConfigHeaders ?? false; + + if (middlewareHeadersPrioritized) { + headers = { + ...headers, + ...middlewareEventOrResult.responseHeaders, + }; + } else { + headers = { + ...middlewareEventOrResult.responseHeaders, + ...headers, + }; + } + let isExternalRewrite = middlewareEventOrResult.isExternalRewrite ?? false; eventOrResult = middlewareEventOrResult; diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts index 53023d5ae..b68ff3e39 100644 --- a/packages/open-next/src/types/open-next.ts +++ b/packages/open-next/src/types/open-next.ts @@ -80,6 +80,17 @@ export interface DangerousOptions { headersAndCookiesPriority?: ( event: InternalEvent, ) => "middleware" | "handler"; + + /** + * Configuration option to prioritize headers set via middleware over headers set via the option in the Next config. + * + * The default will change to 'true' in v4. + * + * See also {@link https://nextjs.org/docs/app/api-reference/file-conventions/middleware#execution-order} + * + * @default false + */ + middlewareHeadersOverrideNextConfigHeaders?: boolean; } export type BaseOverride = { diff --git a/packages/tests-e2e/tests/appRouter/headers.test.ts b/packages/tests-e2e/tests/appRouter/headers.test.ts index 348811a49..69774b77b 100644 --- a/packages/tests-e2e/tests/appRouter/headers.test.ts +++ b/packages/tests-e2e/tests/appRouter/headers.test.ts @@ -28,3 +28,20 @@ test("Headers", async ({ page }) => { // Request ID header should be set expect(headers["x-opennext-requestid"]).not.toBeFalsy(); }); + +/** + * Tests that the middleware headers are applied after next.config.js headers. Requires 'dangerous.middlewareHeadersOverrideNextConfigHeaders' to be set. + */ +test("Middleware headers override next.config.js headers", async ({ page }) => { + const responsePromise = page.waitForResponse((response) => { + return response.status() === 200; + }); + await page.goto("/headers/override-from-middleware"); + + const response = await responsePromise; + // Response header should be set + const headers = response.headers(); + + // The next.config.js headers is overwritten by the middleware + expect(headers["e2e-headers"]).toEqual("middleware"); +});