(
// We're in the app directory if there is no pages router.
const isAppRouter = !pagesRouter
+ if (process.env.NODE_ENV !== 'production') {
+ if (isAppRouter && !asProp) {
+ let href: string | undefined
+ if (typeof hrefProp === 'string') {
+ href = hrefProp
+ } else if (
+ typeof hrefProp === 'object' &&
+ typeof hrefProp.pathname === 'string'
+ ) {
+ href = hrefProp.pathname
+ }
+
+ if (href) {
+ const hasDynamicSegment = href
+ .split('/')
+ .some((segment) => segment.startsWith('[') && segment.endsWith(']'))
+
+ if (hasDynamicSegment) {
+ throw new Error(
+ `Dynamic href \`${href}\` found in while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href`
+ )
+ }
+ }
+ }
+ }
+
const { href, as } = React.useMemo(() => {
if (!pagesRouter) {
const resolvedHref = formatStringOrUrl(hrefProp)
diff --git a/test/e2e/app-dir/dynamic-href.test.ts b/test/e2e/app-dir/dynamic-href.test.ts
new file mode 100644
index 000000000000000..6615538c55e262d
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-href.test.ts
@@ -0,0 +1,75 @@
+import { createNext, FileRef } from 'e2e-utils'
+import { NextInstance } from 'test/lib/next-modes/base'
+import { getRedboxDescription, hasRedbox } from 'next-test-utils'
+import path from 'path'
+import webdriver from 'next-webdriver'
+
+describe('dynamic-href', () => {
+ const isDev = (global as any).isNextDev
+ if ((global as any).isNextDeploy) {
+ it('should skip next deploy for now', () => {})
+ return
+ }
+
+ let next: NextInstance
+
+ beforeAll(async () => {
+ next = await createNext({
+ files: new FileRef(path.join(__dirname, 'dynamic-href')),
+ dependencies: {
+ react: 'experimental',
+ 'react-dom': 'experimental',
+ },
+ })
+ })
+ afterAll(() => next.destroy())
+
+ if (isDev) {
+ it('should error when using dynamic href.pathname in app dir', async () => {
+ const browser = await webdriver(next.url, '/object')
+
+ // Error should show up
+ expect(await hasRedbox(browser, true)).toBeTrue()
+ expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
+ `"Error: Dynamic href \`/object/[slug]\` found in while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
+ )
+
+ // Fix error
+ const pageContent = await next.readFile('app/object/page.js')
+ await next.patchFile(
+ 'app/object/page.js',
+ pageContent.replace(
+ "pathname: '/object/[slug]'",
+ "pathname: '/object/slug'"
+ )
+ )
+ expect(await browser.waitForElementByCss('#link').text()).toBe('to slug')
+
+ // Navigate to new page
+ await browser.elementByCss('#link').click()
+ expect(await browser.waitForElementByCss('#pathname').text()).toBe(
+ '/object/slug'
+ )
+ expect(await browser.elementByCss('#slug').text()).toBe('1')
+ })
+
+ it('should error when using dynamic href in app dir', async () => {
+ const browser = await webdriver(next.url, '/string')
+
+ // Error should show up
+ expect(await hasRedbox(browser, true)).toBeTrue()
+ expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
+ `"Error: Dynamic href \`/object/[slug]\` found in while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
+ )
+ })
+ } else {
+ it('should not error on /object in prod', async () => {
+ const browser = await webdriver(next.url, '/object')
+ expect(await browser.elementByCss('#link').text()).toBe('to slug')
+ })
+ it('should not error on /string in prod', async () => {
+ const browser = await webdriver(next.url, '/string')
+ expect(await browser.elementByCss('#link').text()).toBe('to slug')
+ })
+ }
+})
diff --git a/test/e2e/app-dir/dynamic-href/app/layout.js b/test/e2e/app-dir/dynamic-href/app/layout.js
new file mode 100644
index 000000000000000..c84b681925ebc89
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-href/app/layout.js
@@ -0,0 +1,10 @@
+export default function Root({ children }) {
+ return (
+
+
+ Hello World
+
+ {children}
+
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-href/app/object/[slug]/page.js b/test/e2e/app-dir/dynamic-href/app/object/[slug]/page.js
new file mode 100644
index 000000000000000..911892225aff912
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-href/app/object/[slug]/page.js
@@ -0,0 +1,14 @@
+'use client'
+import { usePathname, useSearchParams } from 'next/navigation'
+
+export default function Page() {
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+
+ return (
+ <>
+ {pathname}
+ {searchParams.get('slug')}
+ >
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-href/app/object/page.js b/test/e2e/app-dir/dynamic-href/app/object/page.js
new file mode 100644
index 000000000000000..bcc1956d7e733c6
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-href/app/object/page.js
@@ -0,0 +1,15 @@
+import Link from 'next/link'
+
+export default function HomePage() {
+ return (
+
+ to slug
+
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-href/app/string/page.js b/test/e2e/app-dir/dynamic-href/app/string/page.js
new file mode 100644
index 000000000000000..a3349d59e24e57c
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-href/app/string/page.js
@@ -0,0 +1,9 @@
+import Link from 'next/link'
+
+export default function HomePage() {
+ return (
+
+ to slug
+
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-href/next.config.js b/test/e2e/app-dir/dynamic-href/next.config.js
new file mode 100644
index 000000000000000..cfa3ac3d7aa94b3
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-href/next.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ experimental: {
+ appDir: true,
+ },
+}