Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw error <a> child in <Link> #41756

Merged
merged 9 commits into from Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions errors/manifest.json
Expand Up @@ -694,6 +694,10 @@
"title": "nested-styled-jsx-tags",
"path": "/errors/nested-styled-jsx-tags.md"
},
{
"title": "new-link-a-tag",
"path": "/errors/new-link-a-tag.md"
},
{
"title": "returning-response-body-in-middleware",
"path": "/errors/returning-response-body-in-middleware.md"
Expand Down
21 changes: 21 additions & 0 deletions errors/new-link-a-tag.md
@@ -0,0 +1,21 @@
# Invalid <Link> with <a> child

#### Why This Error Occurred

Starting with Next.js 13, `<Link>` renders as `<a>`, so attempting to use `<a>` as a child is invalid.

#### Possible Ways to Fix It

Run the `new-link` codemod to automatically upgrade previous versions of Next.js to the new `<Link>` usage:

```sh
npx @next/codemod new-link .
```

This will change `<Link><a id="link">Home<a></Link>` to `<Link id="link">Home</Link>`.

Alternatively, you can add the `legacyBehavior` prop `<Link legacyBehavior><a id="link">Home<a></Link>`.

### Useful Links

- [next/link](https://nextjs.org/docs/api-reference/next/link)
8 changes: 8 additions & 0 deletions packages/next/client/link.tsx
Expand Up @@ -408,6 +408,14 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
} else {
child = React.Children.only(children)
}
} else {
if (process.env.NODE_ENV === 'development') {
if ((children as any)?.type === 'a') {
throw new Error(
'Invalid <Link> with <a> child. Please remove <a> or use <Link legacyBehavior>.\nLearn more: https://nextjs.org/docs/messages/new-link-a-tag'
styfle marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
}

const childRef: any = legacyBehavior
Expand Down
42 changes: 42 additions & 0 deletions test/e2e/new-link-behavior/child-a-tag-error.test.ts
@@ -0,0 +1,42 @@
import { createNext, FileRef } from 'e2e-utils'
import { getRedboxSource, hasRedbox } from 'next-test-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import webdriver from 'next-webdriver'
import path from 'path'

const appDir = path.join(__dirname, 'child-a-tag-error')

describe('New Link Behavior with <a> child', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(path.join(appDir, 'pages')),
'next.config.js': new FileRef(path.join(appDir, 'next.config.js')),
},
dependencies: {
next: 'latest',
react: 'latest',
'react-dom': 'latest',
},
})
})
afterAll(() => next.destroy())

it('should throw error with <a> child', async () => {
const browser = await webdriver(next.url, `/`)
const link = await browser.elementsByCss('a[href="/about"]')
const msg =
'Error: Invalid <Link> with <a> child. Please remove <a> or use <Link legacyBehavior>'

if ((global as any).isDev) {
expect(next.cliOutput).toContain(msg)
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toContain(msg)
expect(link).not.toBeDefined()
} else {
expect(link).toBeDefined()
}
})
})
3 changes: 3 additions & 0 deletions test/e2e/new-link-behavior/child-a-tag-error/next.config.js
@@ -0,0 +1,3 @@
module.exports = {
reactStrictMode: true,
}
12 changes: 12 additions & 0 deletions test/e2e/new-link-behavior/child-a-tag-error/pages/about.js
@@ -0,0 +1,12 @@
import Link from 'next/link'

export default function Page() {
return (
<>
<h1>About Page</h1>
<Link href="/">
<a>Home</a>
</Link>
</>
)
}
12 changes: 12 additions & 0 deletions test/e2e/new-link-behavior/child-a-tag-error/pages/index.js
@@ -0,0 +1,12 @@
import Link from 'next/link'

export default function Page() {
return (
<>
<h1>Home Page</h1>
<Link href="/about">
<a>About</a>
</Link>
</>
)
}