diff --git a/.changeset/proud-dogs-punch.md b/.changeset/proud-dogs-punch.md new file mode 100644 index 000000000..ad9876617 --- /dev/null +++ b/.changeset/proud-dogs-punch.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": patch +--- + +fix: Ensure Location header is properly encoded in redirects happening from next config diff --git a/examples/app-router/app/config-redirect/dest/page.tsx b/examples/app-router/app/config-redirect/dest/page.tsx new file mode 100644 index 000000000..e9ae5d056 --- /dev/null +++ b/examples/app-router/app/config-redirect/dest/page.tsx @@ -0,0 +1,13 @@ +export default async function Page({ + searchParams, +}: { + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +}) { + const q = (await searchParams).q; + + return ( + <> +
q: {q}
+ + ); +} diff --git a/examples/app-router/app/config-redirect/page.tsx b/examples/app-router/app/config-redirect/page.tsx index 6e51d3d56..c66280060 100644 --- a/examples/app-router/app/config-redirect/page.tsx +++ b/examples/app-router/app/config-redirect/page.tsx @@ -3,6 +3,18 @@ export default function RedirectDestination() {

I was redirected from next.config.js

/next-config-redirect => /config-redirect

+ + /next-config-redirect-encoding?q=äöå€ + + + /next-config-redirect-encoding?q=%C3%A4%C3%B6%C3%A5%E2%82%AC +
); } diff --git a/examples/app-router/next.config.ts b/examples/app-router/next.config.ts index d1356ef9f..77bd5abf0 100644 --- a/examples/app-router/next.config.ts +++ b/examples/app-router/next.config.ts @@ -6,13 +6,6 @@ const nextConfig: NextConfig = { transpilePackages: ["@example/shared"], output: "standalone", // outputFileTracingRoot: "../sst", - eslint: { - ignoreDuringBuilds: true, - }, - //TODO: remove this when i'll figure out why it fails locally - typescript: { - ignoreBuildErrors: true, - }, images: { remotePatterns: [ { @@ -60,6 +53,11 @@ const nextConfig: NextConfig = { basePath: false, locale: false, }, + { + source: "/next-config-redirect-encoding", + destination: "/config-redirect/dest", + permanent: false, + }, ]; }, async headers() { diff --git a/packages/open-next/src/core/routingHandler.ts b/packages/open-next/src/core/routingHandler.ts index ed3f926e1..834bf4f58 100644 --- a/packages/open-next/src/core/routingHandler.ts +++ b/packages/open-next/src/core/routingHandler.ts @@ -96,6 +96,11 @@ export default async function routingHandler( const redirect = handleRedirects(internalEvent, RoutesManifest.redirects); if (redirect) { + // We need to encode the value in the Location header to make sure it is valid according to RFC + // https://stackoverflow.com/a/7654605/16587222 + redirect.headers.Location = new URL( + redirect.headers.Location as string, + ).href; debug("redirect", redirect); return redirect; } diff --git a/packages/tests-e2e/tests/appRouter/config.redirect.test.ts b/packages/tests-e2e/tests/appRouter/config.redirect.test.ts index d328d04e2..218e83279 100644 --- a/packages/tests-e2e/tests/appRouter/config.redirect.test.ts +++ b/packages/tests-e2e/tests/appRouter/config.redirect.test.ts @@ -72,4 +72,46 @@ test.describe("Next Config Redirect", () => { }); await expect(el).toBeVisible(); }); + test("Should properly encode the Location header for redirects with query params", async ({ + page, + baseURL, + }) => { + await page.goto("/config-redirect"); + const responsePromise = page.waitForResponse((response) => { + return response.status() === 307; + }); + page.getByTestId("redirect-link").click(); + const res = await responsePromise; + await page.waitForURL("/config-redirect/dest?q=äöå€"); + + const locationHeader = res.headers().location; + expect(locationHeader).toBe( + `${baseURL}/config-redirect/dest?q=%C3%A4%C3%B6%C3%A5%E2%82%AC`, + ); + expect(res.status()).toBe(307); + + const searchParams = page.getByTestId("searchParams"); + await expect(searchParams).toHaveText("q: äöå€"); + }); + test("Should respect already encoded query params", async ({ + page, + baseURL, + }) => { + await page.goto("/config-redirect"); + const responsePromise = page.waitForResponse((response) => { + return response.status() === 307; + }); + page.getByTestId("redirect-link-already-encoded").click(); + const res = await responsePromise; + await page.waitForURL("/config-redirect/dest?q=äöå€"); + + const locationHeader = res.headers().location; + expect(locationHeader).toBe( + `${baseURL}/config-redirect/dest?q=%C3%A4%C3%B6%C3%A5%E2%82%AC`, + ); + expect(res.status()).toBe(307); + + const searchParams = page.getByTestId("searchParams"); + await expect(searchParams).toHaveText("q: äöå€"); + }); });