Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/next/src/build/templates/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,8 @@ export async function handler(
reactLoadableManifest,

assetPrefix: nextConfig.assetPrefix,
strictNextHead: Boolean(
nextConfig.experimental.strictNextHead
),
strictNextHead:
nextConfig.experimental.strictNextHead ?? true,
previewProps: prerenderManifest.preview,
images: nextConfig.images as any,
nextConfigOutput: nextConfig.output,
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,7 @@ export const defaultConfig = Object.freeze({
devtoolSegmentExplorer: false,
browserDebugInfoInTerminal: false,
optimizeRouterScrolling: false,
strictNextHead: true,
},
htmlLimitedBots: undefined,
bundlePagesRouterDependencies: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ module.exports = {
// Make sure entries are not getting disposed.
maxInactiveAge: 1000 * 60 * 60,
},
experimental: {
strictNextHead: process.env.TEST_STRICT_NEXT_HEAD !== 'false',
},
experimental:
process.env.TEST_STRICT_NEXT_HEAD !== undefined
? {
strictNextHead: process.env.TEST_STRICT_NEXT_HEAD === 'true',
}
: {},
// scroll position can be finicky with the
// indicators showing so hide by default
devIndicators: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Head from 'next/head'

export default function Page() {
return (
<div>
<Head>
<title>Title Page</title>
<meta property="og:title" content="Title Content" />
<meta name="description" content="Description Content" />
</Head>
<p>This is a page!</p>
</div>
)
}
132 changes: 83 additions & 49 deletions test/development/pages-dir/client-navigation/rendering-head.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import path from 'path'
const isReact18 = parseInt(process.env.NEXT_TEST_REACT_VERSION) === 18

describe('Client Navigation rendering <Head />', () => {
describe.each([[false], [true]])(
describe.each([[false], [true], [undefined]])(
'with strictNextHead=%s',
(strictNextHead) => {
const { next } = nextTestSetup({
files: path.join(__dirname, 'fixture'),
env: {
TEST_STRICT_NEXT_HEAD: String(strictNextHead),
},
env:
strictNextHead !== undefined
? {
TEST_STRICT_NEXT_HEAD: String(strictNextHead),
}
: {},
})

function render(
Expand All @@ -37,7 +40,7 @@ describe('Client Navigation rendering <Head />', () => {
test('header renders default charset', async () => {
const html = await render('/default-head')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta charSet="utf-8" data-next-head=""/>'
: '<meta charSet="utf-8"/>'
)
Expand All @@ -47,7 +50,7 @@ describe('Client Navigation rendering <Head />', () => {
test('header renders default viewport', async () => {
const html = await render('/default-head')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta name="viewport" content="width=device-width" data-next-head=""/>'
: '<meta name="viewport" content="width=device-width"/>'
)
Expand All @@ -56,17 +59,17 @@ describe('Client Navigation rendering <Head />', () => {
test('header helper renders header information', async () => {
const html = await render('/head')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta charSet="iso-8859-5" data-next-head=""/>'
: '<meta charSet="iso-8859-5"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta content="my meta" data-next-head=""/>'
: '<meta content="my meta"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta name="viewport" content="width=device-width,initial-scale=1" data-next-head=""/>'
: '<meta name="viewport" content="width=device-width,initial-scale=1"/>'
)
Expand All @@ -76,17 +79,17 @@ describe('Client Navigation rendering <Head />', () => {
test('header helper dedupes tags', async () => {
const html = await render('/head')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta charSet="iso-8859-5" data-next-head=""/>'
: '<meta charSet="iso-8859-5"/>'
)
expect(html).not.toContain(
strictNextHead
strictNextHead !== false
? '<meta charSet="utf-8" data-next-head=""/>'
: '<meta charSet="utf-8"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta name="viewport" content="width=device-width,initial-scale=1" data-next-head=""/>'
: '<meta name="viewport" content="width=device-width,initial-scale=1"/>'
)
Expand All @@ -96,24 +99,25 @@ describe('Client Navigation rendering <Head />', () => {
'<meta name="viewport" content="width=device-width"'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta content="my meta" data-next-head=""/>'
: '<meta content="my meta"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<link rel="stylesheet" href="/dup-style.css" data-next-head=""/><link rel="stylesheet" href="/dup-style.css" data-next-head=""/>'
: '<link rel="stylesheet" href="/dup-style.css"/><link rel="stylesheet" href="/dup-style.css"/>'
)
const dedupeLink = strictNextHead
? '<link rel="stylesheet" href="dedupe-style.css" data-next-head=""/>'
: '<link rel="stylesheet" href="dedupe-style.css"/>'
const dedupeLink =
strictNextHead !== false
? '<link rel="stylesheet" href="dedupe-style.css" data-next-head=""/>'
: '<link rel="stylesheet" href="dedupe-style.css"/>'
expect(html).toContain(dedupeLink)
expect(
html.substring(html.indexOf(dedupeLink) + dedupeLink.length)
).not.toContain('<link rel="stylesheet" href="dedupe-style.css"')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<link rel="alternate" hrefLang="en" href="/last/en" data-next-head=""/>'
: '<link rel="alternate" hrefLang="en" href="/last/en"/>'
)
Expand All @@ -129,12 +133,12 @@ describe('Client Navigation rendering <Head />', () => {
// Expect exactly one `viewport`
expect((html.match(/name="viewport"/g) || []).length).toBe(1)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta charSet="iso-8859-1" data-next-head=""/>'
: '<meta charSet="iso-8859-1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta name="viewport" content="width=500" data-next-head=""/>'
: '<meta name="viewport" content="width=500"/>'
)
Expand All @@ -144,98 +148,98 @@ describe('Client Navigation rendering <Head />', () => {
const html = await render('/head')
console.log(html)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="article:tag" content="tag1" data-next-head=""/>'
: '<meta property="article:tag" content="tag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="article:tag" content="tag2" data-next-head=""/>'
: '<meta property="article:tag" content="tag2"/>'
)
expect(html).not.toContain('<meta property="dedupe:tag" content="tag3"')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="dedupe:tag" content="tag4" data-next-head=""/>'
: '<meta property="dedupe:tag" content="tag4"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image" content="ogImageTag1" data-next-head=""/>'
: '<meta property="og:image" content="ogImageTag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image" content="ogImageTag2" data-next-head=""/>'
: '<meta property="og:image" content="ogImageTag2"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:alt" content="ogImageAltTag1" data-next-head=""/>'
: '<meta property="og:image:alt" content="ogImageAltTag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:alt" content="ogImageAltTag2" data-next-head=""/>'
: '<meta property="og:image:alt" content="ogImageAltTag2"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:width" content="ogImageWidthTag1" data-next-head=""/>'
: '<meta property="og:image:width" content="ogImageWidthTag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:width" content="ogImageWidthTag2" data-next-head=""/>'
: '<meta property="og:image:width" content="ogImageWidthTag2"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:height" content="ogImageHeightTag1" data-next-head=""/>'
: '<meta property="og:image:height" content="ogImageHeightTag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:height" content="ogImageHeightTag2" data-next-head=""/>'
: '<meta property="og:image:height" content="ogImageHeightTag2"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:type" content="ogImageTypeTag1" data-next-head=""/>'
: '<meta property="og:image:type" content="ogImageTypeTag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:type" content="ogImageTypeTag2" data-next-head=""/>'
: '<meta property="og:image:type" content="ogImageTypeTag2"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:secure_url" content="ogImageSecureUrlTag1" data-next-head=""/>'
: '<meta property="og:image:secure_url" content="ogImageSecureUrlTag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:secure_url" content="ogImageSecureUrlTag2" data-next-head=""/>'
: '<meta property="og:image:secure_url" content="ogImageSecureUrlTag2"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:url" content="ogImageUrlTag1" data-next-head=""/>'
: '<meta property="og:image:url" content="ogImageUrlTag1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="og:image:url" content="ogImageUrlTag2" data-next-head=""/>'
: '<meta property="og:image:url" content="ogImageUrlTag2"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="fb:pages" content="fbpages1" data-next-head=""/>'
: '<meta property="fb:pages" content="fbpages1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta property="fb:pages" content="fbpages2" data-next-head=""/>'
: '<meta property="fb:pages" content="fbpages2"/>'
)
Expand All @@ -244,12 +248,12 @@ describe('Client Navigation rendering <Head />', () => {
test('header helper avoids dedupe of meta tags with the same name if they use unique keys', async () => {
const html = await render('/head')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta name="citation_author" content="authorName1" data-next-head=""/>'
: '<meta name="citation_author" content="authorName1"/>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta name="citation_author" content="authorName2" data-next-head=""/>'
: '<meta name="citation_author" content="authorName2"/>'
)
Expand All @@ -258,12 +262,12 @@ describe('Client Navigation rendering <Head />', () => {
test('header helper renders Fragment children', async () => {
const html = await render('/head')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<title data-next-head="">Fragment title</title>'
: '<title>Fragment title</title>'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<meta content="meta fragment" data-next-head=""/>'
: '<meta content="meta fragment"/>'
)
Expand All @@ -272,27 +276,28 @@ describe('Client Navigation rendering <Head />', () => {
test('header helper renders boolean attributes correctly children', async () => {
const html = await render('/head')
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<script src="/test-async-false.js" data-next-head="">'
: '<script src="/test-async-false.js">'
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<script src="/test-async-true.js" async="" data-next-head="">'
: ''
)
expect(html).toContain(
strictNextHead
strictNextHead !== false
? '<script src="/test-defer.js" defer="" data-next-head="">'
: ''
)
})

it('should place charset element at the top of <head>', async () => {
const html = await render('/head-priority')
const nextHeadElement = strictNextHead
? '<meta charSet="iso-8859-5" data-next-head=""/><meta name="viewport" content="width=device-width,initial-scale=1" data-next-head=""/><meta name="title" content="head title" data-next-head=""/>'
: '<meta charSet="iso-8859-5"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="title" content="head title"/><meta name="next-head-count" content="3"/>'
const nextHeadElement =
strictNextHead !== false
? '<meta charSet="iso-8859-5" data-next-head=""/><meta name="viewport" content="width=device-width,initial-scale=1" data-next-head=""/><meta name="title" content="head title" data-next-head=""/>'
: '<meta charSet="iso-8859-5"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="title" content="head title"/><meta name="next-head-count" content="3"/>'
const documentHeadElement =
'<meta name="keywords" content="document head test"/>'

Expand All @@ -304,6 +309,35 @@ describe('Client Navigation rendering <Head />', () => {
: `<head>${nextHeadElement}${documentHeadElement}`
)
})

test('custom meta properties are rendered only once', async () => {
const browser = await next.browser('/head-with-custom-metadata')

// Check that title appears only once
const titleElements = await browser.elementsByCss('title')
expect(titleElements).toHaveLength(1)
const titleText = await browser.elementByCss('title').text()
expect(titleText).toBe('Title Page')

// Check that each meta property appears only once
const ogTitleElements = await browser.elementsByCss(
'meta[property="og:title"]'
)
expect(ogTitleElements).toHaveLength(1)
const ogTitleContent = await browser
.elementByCss('meta[property="og:title"]')
.getAttribute('content')
expect(ogTitleContent).toBe('Title Content')

const descriptionElements = await browser.elementsByCss(
'meta[name="description"]'
)
expect(descriptionElements).toHaveLength(1)
const descriptionContent = await browser
.elementByCss('meta[name="description"]')
.getAttribute('content')
expect(descriptionContent).toBe('Description Content')
})
}
)
})
Loading