diff --git a/test/e2e/app-dir/rewrites-redirects/app/[...params]/page.tsx b/test/e2e/app-dir/rewrites-redirects/app/[...params]/page.tsx
new file mode 100644
index 000000000000..013b668d1e8e
--- /dev/null
+++ b/test/e2e/app-dir/rewrites-redirects/app/[...params]/page.tsx
@@ -0,0 +1,7 @@
+export default function Page({ params: { params } }) {
+ return (
+
+ {params.join('/')}
+
+ )
+}
diff --git a/test/e2e/app-dir/rewrites-redirects/app/layout.tsx b/test/e2e/app-dir/rewrites-redirects/app/layout.tsx
new file mode 100644
index 000000000000..762515029332
--- /dev/null
+++ b/test/e2e/app-dir/rewrites-redirects/app/layout.tsx
@@ -0,0 +1,8 @@
+export default function Layout({ children }) {
+ return (
+
+
+ {children}
+
+ )
+}
diff --git a/test/e2e/app-dir/rewrites-redirects/app/page.tsx b/test/e2e/app-dir/rewrites-redirects/app/page.tsx
new file mode 100644
index 000000000000..b18a6b1b73ce
--- /dev/null
+++ b/test/e2e/app-dir/rewrites-redirects/app/page.tsx
@@ -0,0 +1,35 @@
+'use client'
+
+import Link from 'next/link'
+import { useRouter } from 'next/navigation'
+
+const Test = ({ page, href }: { page: string; href?: string }) => {
+ const router = useRouter()
+ href ??= `/${page}-before`
+
+ return (
+ <>
+
+ Link to /{page}-before
+
+
+ >
+ )
+}
+
+export default function Page() {
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
diff --git a/test/e2e/app-dir/rewrites-redirects/middleware.ts b/test/e2e/app-dir/rewrites-redirects/middleware.ts
new file mode 100644
index 000000000000..d3b147ecd233
--- /dev/null
+++ b/test/e2e/app-dir/rewrites-redirects/middleware.ts
@@ -0,0 +1,16 @@
+import { NextResponse } from 'next/server'
+import type { NextRequest } from 'next/server'
+
+export function middleware(request: NextRequest) {
+ if (request.nextUrl.pathname.startsWith('/middleware-rewrite-before')) {
+ return NextResponse.rewrite(
+ new URL('/middleware-rewrite-after', request.url)
+ )
+ }
+
+ if (request.nextUrl.pathname.startsWith('/middleware-redirect-before')) {
+ return NextResponse.redirect(
+ new URL('/middleware-redirect-after', request.url)
+ )
+ }
+}
diff --git a/test/e2e/app-dir/rewrites-redirects/next.config.js b/test/e2e/app-dir/rewrites-redirects/next.config.js
new file mode 100644
index 000000000000..bc7f7ab9233f
--- /dev/null
+++ b/test/e2e/app-dir/rewrites-redirects/next.config.js
@@ -0,0 +1,28 @@
+module.exports = {
+ reactStrictMode: true,
+ experimental: {
+ appDir: true,
+ },
+ async rewrites() {
+ return [
+ {
+ source: '/config-rewrite-before',
+ destination: '/config-rewrite-after',
+ },
+ ]
+ },
+ async redirects() {
+ return [
+ {
+ source: '/config-redirect-before',
+ destination: '/config-redirect-after',
+ permanent: true,
+ },
+ {
+ source: '/config-redirect-catchall-before/:path*',
+ destination: '/config-redirect-catchall-after/:path*',
+ permanent: true,
+ },
+ ]
+ },
+}
diff --git a/test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts b/test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts
new file mode 100644
index 000000000000..ae79abb789b9
--- /dev/null
+++ b/test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts
@@ -0,0 +1,71 @@
+import { createNextDescribe } from 'e2e-utils'
+
+createNextDescribe(
+ 'redirects and rewrites',
+ {
+ files: __dirname,
+ dependencies: {
+ react: 'latest',
+ 'react-dom': 'latest',
+ typescript: 'latest',
+ '@types/react': 'latest',
+ '@types/node': 'latest',
+ },
+ },
+ ({ next }) => {
+ /**
+ * All test will use a link/button to navigate to '/*-before' which should be redirected by correct redirect/rewrite to '/*-after'
+ */
+ describe.each(['link', 'button'])('navigation using %s', (testType) => {
+ it('should rewrite from middleware correctly', async () => {
+ const browser = await next.browser('/')
+ await browser
+ .elementById(`${testType}-middleware-rewrite`)
+ .click()
+ .waitForElementByCss('.page_middleware-rewrite-after')
+ const url = new URL(await browser.url())
+ expect(url.pathname).toEndWith('-before')
+ })
+
+ it('should redirect from middleware correctly', async () => {
+ const browser = await next.browser('/')
+ await browser
+ .elementById(`${testType}-middleware-redirect`)
+ .click()
+ .waitForElementByCss('.page_middleware-redirect-after')
+ const url = new URL(await browser.url())
+ expect(url.pathname).toEndWith('-after')
+ })
+
+ it('should rewrite from next.config.js correctly', async () => {
+ const browser = await next.browser('/')
+ await browser
+ .elementById(`${testType}-config-rewrite`)
+ .click()
+ .waitForElementByCss('.page_config-rewrite-after')
+ const url = new URL(await browser.url())
+ expect(url.pathname).toEndWith('-before')
+ })
+
+ it('should redirect from next.config.js correctly', async () => {
+ const browser = await next.browser('/')
+ await browser
+ .elementById(`${testType}-config-redirect`)
+ .click()
+ .waitForElementByCss('.page_config-redirect-after')
+ const url = new URL(await browser.url())
+ expect(url.pathname).toEndWith('-after')
+ })
+
+ it('should redirect using catchall from next.config.js correctly', async () => {
+ const browser = await next.browser('/')
+ await browser
+ .elementById(`${testType}-config-redirect-catchall`)
+ .click()
+ .waitForElementByCss('.page_config-redirect-catchall-after_thing')
+ const url = new URL(await browser.url())
+ expect(url.pathname).toEndWith('-after/thing')
+ })
+ })
+ }
+)