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

Support de-deduping head tags by setting key #3170

Merged
merged 6 commits into from Oct 31, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions lib/head.js
Expand Up @@ -48,11 +48,16 @@ const METATYPES = ['name', 'httpEquiv', 'charSet', 'itemProp', 'property']
// which shouldn't be duplicated, like <title/>.

function unique () {
const keys = new Set()
const tags = new Set()
const metaTypes = new Set()
const metaCategories = {}

return (h) => {
if (h.key) {
if (keys.has(h.key)) return false
keys.add(h.key)
}
switch (h.type) {
case 'title':
case 'base':
Expand Down
20 changes: 20 additions & 0 deletions readme.md
Expand Up @@ -197,6 +197,26 @@ export default () =>
</div>
```

To avoid duplicate tags in your `<head>` you can use the `key` property, which will make sure the tag is only rendered once:

```jsx
import Head from 'next/head'
export default () => (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" key="viewport" />
</Head>
<Head>
<meta name="viewport" content="initial-scale=1.2, width=device-width" key="viewport" />
</Head>
<p>Hello world!</p>
</div>
)
```

In this case only the second `<meta name="viewport" />` is rendered.

_Note: The contents of `<head>` get cleared upon unmounting the component, so make sure each page completely defines what it needs in `<head>`, without making assumptions about what other pages added_

### Fetching data and component lifecycle
Expand Down
2 changes: 1 addition & 1 deletion server/document.js
Expand Up @@ -88,7 +88,7 @@ export class Head extends Component {
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error/index.js`} as='script' />
{this.getPreloadDynamicChunks()}
{this.getPreloadMainLinks()}
{(head || []).map((h, i) => React.cloneElement(h, { key: i }))}
{(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))}
{styles || null}
{this.props.children}
</head>
Expand Down
12 changes: 12 additions & 0 deletions test/integration/basic/pages/head.js
Expand Up @@ -3,8 +3,20 @@ import Head from 'next/head'

export default () => <div>
<Head>
{/* this will not render */}
<meta charSet='utf-8' />
{/* this will get rendered */}
<meta charSet='iso-8859-5' />

<meta content='my meta' />

{/* the following 2 links tag will be rendered both */}
<link rel='stylesheet' href='/dup-style.css' />
<link rel='stylesheet' href='/dup-style.css' />

{/* only one tag will be rendered as they have the same key */}
<link rel='stylesheet' href='dedupe-style.css' key='my-style' />
<link rel='stylesheet' href='dedupe-style.css' key='my-style' />
</Head>
<h1>I can haz meta tags</h1>
</div>
10 changes: 10 additions & 0 deletions test/integration/basic/test/rendering.js
Expand Up @@ -28,6 +28,16 @@ export default function ({ app }, suiteName, render) {
expect(html.includes('I can haz meta tags')).toBeTruthy()
})

test('header helper dedupes tags', async () => {
const html = await (render('/head'))
expect(html).toContain('<meta charSet="iso-8859-5" class="next-head"/>')
expect(html).not.toContain('<meta charSet="utf-8" class="next-head"/>')
expect(html).toContain('<meta content="my meta" class="next-head"/>')
expect(html).toContain('<link rel="stylesheet" href="/dup-style.css" class="next-head"/><link rel="stylesheet" href="/dup-style.css" class="next-head"/>')
expect(html).toContain('<link rel="stylesheet" href="dedupe-style.css" class="next-head"/>')
expect(html).not.toContain('<link rel="stylesheet" href="dedupe-style.css" class="next-head"/><link rel="stylesheet" href="dedupe-style.css" class="next-head"/>')
})

test('renders styled jsx', async () => {
const $ = await get$('/styled-jsx')
const styleId = $('#blue-box').attr('class')
Expand Down