mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Support de-deduping head tags by setting key (#3170)
* Support de-deduping head tags by setting key * move dedupe logic to `unique` function * fix head tag deduping logic * remove console.log * use `toContain` assertions * update de-duping head tags section in README
This commit is contained in:
parent
c8059b9f12
commit
190853b4ff
|
@ -48,11 +48,16 @@ const METATYPES = ['name', 'httpEquiv', 'charSet', 'itemProp', 'property']
|
||||||
// which shouldn't be duplicated, like <title/>.
|
// which shouldn't be duplicated, like <title/>.
|
||||||
|
|
||||||
function unique () {
|
function unique () {
|
||||||
|
const keys = new Set()
|
||||||
const tags = new Set()
|
const tags = new Set()
|
||||||
const metaTypes = new Set()
|
const metaTypes = new Set()
|
||||||
const metaCategories = {}
|
const metaCategories = {}
|
||||||
|
|
||||||
return (h) => {
|
return (h) => {
|
||||||
|
if (h.key) {
|
||||||
|
if (keys.has(h.key)) return false
|
||||||
|
keys.add(h.key)
|
||||||
|
}
|
||||||
switch (h.type) {
|
switch (h.type) {
|
||||||
case 'title':
|
case 'title':
|
||||||
case 'base':
|
case 'base':
|
||||||
|
|
20
readme.md
20
readme.md
|
@ -197,6 +197,26 @@ export default () =>
|
||||||
</div>
|
</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_
|
_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
|
### Fetching data and component lifecycle
|
||||||
|
|
|
@ -88,7 +88,7 @@ export class Head extends Component {
|
||||||
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error/index.js`} as='script' />
|
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error/index.js`} as='script' />
|
||||||
{this.getPreloadDynamicChunks()}
|
{this.getPreloadDynamicChunks()}
|
||||||
{this.getPreloadMainLinks()}
|
{this.getPreloadMainLinks()}
|
||||||
{(head || []).map((h, i) => React.cloneElement(h, { key: i }))}
|
{(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))}
|
||||||
{styles || null}
|
{styles || null}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -3,8 +3,20 @@ import Head from 'next/head'
|
||||||
|
|
||||||
export default () => <div>
|
export default () => <div>
|
||||||
<Head>
|
<Head>
|
||||||
|
{/* this will not render */}
|
||||||
|
<meta charSet='utf-8' />
|
||||||
|
{/* this will get rendered */}
|
||||||
<meta charSet='iso-8859-5' />
|
<meta charSet='iso-8859-5' />
|
||||||
|
|
||||||
<meta content='my meta' />
|
<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>
|
</Head>
|
||||||
<h1>I can haz meta tags</h1>
|
<h1>I can haz meta tags</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,6 +28,16 @@ export default function ({ app }, suiteName, render) {
|
||||||
expect(html.includes('I can haz meta tags')).toBeTruthy()
|
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 () => {
|
test('renders styled jsx', async () => {
|
||||||
const $ = await get$('/styled-jsx')
|
const $ = await get$('/styled-jsx')
|
||||||
const styleId = $('#blue-box').attr('class')
|
const styleId = $('#blue-box').attr('class')
|
||||||
|
|
Loading…
Reference in a new issue