diff --git a/examples/e2e/app-router/app/config-redirect/dest/page.tsx b/examples/e2e/app-router/app/config-redirect/dest/page.tsx
new file mode 100644
index 00000000..e2051a79
--- /dev/null
+++ b/examples/e2e/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/e2e/app-router/app/config-redirect/page.tsx b/examples/e2e/app-router/app/config-redirect/page.tsx
index b7766ca6..387637c8 100644
--- a/examples/e2e/app-router/app/config-redirect/page.tsx
+++ b/examples/e2e/app-router/app/config-redirect/page.tsx
@@ -3,6 +3,15 @@ export default function RedirectDestination() {
);
}
diff --git a/examples/e2e/app-router/e2e/config.redirect.test.ts b/examples/e2e/app-router/e2e/config.redirect.test.ts
index cb6c970e..fb4df8ca 100644
--- a/examples/e2e/app-router/e2e/config.redirect.test.ts
+++ b/examples/e2e/app-router/e2e/config.redirect.test.ts
@@ -72,4 +72,36 @@ test.describe("Next Config Redirect", () => {
});
await expect(el).toBeVisible();
});
+ test("Should properly encode the Location header for redirects with query params", async ({ page }) => {
+ 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("/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 }) => {
+ 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("/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: äöå€");
+ });
});
diff --git a/examples/e2e/app-router/e2e/middleware.redirect.test.ts b/examples/e2e/app-router/e2e/middleware.redirect.test.ts
index 45159b8a..44f2dc11 100644
--- a/examples/e2e/app-router/e2e/middleware.redirect.test.ts
+++ b/examples/e2e/app-router/e2e/middleware.redirect.test.ts
@@ -1,11 +1,4 @@
import { expect, test } from "@playwright/test";
-import { validateMd5 } from "../../utils";
-
-/*
- * `curl -s https://opennext.js.org/share.png | md5sum`
- * This is the MD5 hash of the image. It is used to validate the image content.
- */
-const OPENNEXT_PNG_MD5 = "405f45cc3397b09717a13ebd6f1e027b";
test("Middleware Redirect", async ({ page, context }) => {
await page.goto("/");
@@ -25,14 +18,3 @@ test("Middleware Redirect", async ({ page, context }) => {
el = page.getByText("Redirect Destination", { exact: true });
await expect(el).toBeVisible();
});
-
-test("Middleware Rewrite External Image", async ({ page }) => {
- await page.goto("/rewrite-external");
- page.on("response", async (response) => {
- expect(response.status()).toBe(200);
- expect(response.headers()["content-type"]).toBe("image/png");
- expect(response.headers()["cache-control"]).toBe("max-age=600");
- const bodyBuffer = await response.body();
- expect(validateMd5(bodyBuffer, OPENNEXT_PNG_MD5)).toBe(true);
- });
-});
diff --git a/examples/e2e/app-router/e2e/middleware.rewrite.test.ts b/examples/e2e/app-router/e2e/middleware.rewrite.test.ts
index fb89df14..24c027b8 100644
--- a/examples/e2e/app-router/e2e/middleware.rewrite.test.ts
+++ b/examples/e2e/app-router/e2e/middleware.rewrite.test.ts
@@ -1,4 +1,11 @@
import { expect, test } from "@playwright/test";
+import { validateMd5 } from "../../utils";
+
+/*
+ * `curl -s https://opennext.js.org/share.png | md5sum`
+ * This is the MD5 hash of the image. It is used to validate the image content.
+ */
+const OPENNEXT_PNG_MD5 = "405f45cc3397b09717a13ebd6f1e027b";
test("Middleware Rewrite", async ({ page }) => {
await page.goto("/");
@@ -14,3 +21,23 @@ test("Middleware Rewrite", async ({ page }) => {
el = page.getByText("Rewritten Destination", { exact: true });
await expect(el).toBeVisible();
});
+
+test("Middleware Rewrite External Image", async ({ page }) => {
+ await page.goto("/rewrite-external");
+ page.on("response", async (response) => {
+ expect(response.status()).toBe(200);
+ expect(response.headers()["content-type"]).toBe("image/png");
+ expect(response.headers()["cache-control"]).toBe("max-age=600");
+ const bodyBuffer = await response.body();
+ expect(validateMd5(bodyBuffer, OPENNEXT_PNG_MD5)).toBe(true);
+ });
+});
+
+test("Middleware Rewrite Status Code", async ({ page }) => {
+ await page.goto("/rewrite-status-code");
+ const el = page.getByText("Rewritten Destination", { exact: true });
+ await expect(el).toBeVisible();
+ page.on("response", async (response) => {
+ expect(response.status()).toBe(403);
+ });
+});
diff --git a/examples/e2e/app-router/middleware.ts b/examples/e2e/app-router/middleware.ts
index 71081bc4..ace1530d 100644
--- a/examples/e2e/app-router/middleware.ts
+++ b/examples/e2e/app-router/middleware.ts
@@ -28,6 +28,12 @@ export function middleware(request: NextRequest) {
const u = new URL("https://opennext.js.org/share.png");
return NextResponse.rewrite(u);
}
+ if (path === "/rewrite-status-code") {
+ const u = new URL("/rewrite-destination", `${protocol}://${host}`);
+ return NextResponse.rewrite(u, {
+ status: 403,
+ });
+ }
if (path === "/cookies") {
const res = NextResponse.next();
res.cookies.set("foo", "bar");
diff --git a/examples/e2e/app-router/next.config.ts b/examples/e2e/app-router/next.config.ts
index e629b612..fff69ce3 100644
--- a/examples/e2e/app-router/next.config.ts
+++ b/examples/e2e/app-router/next.config.ts
@@ -59,6 +59,11 @@ const nextConfig: NextConfig = {
basePath: false,
locale: false,
},
+ {
+ source: "/next-config-redirect-encoding",
+ destination: "/config-redirect/dest",
+ permanent: false,
+ },
];
},
async headers() {