Skip to content

Commit 2f0e502

Browse files
committed
fix: custom head properties are duplicated in pages router
1 parent 40d2fa9 commit 2f0e502

File tree

4 files changed

+105
-55
lines changed

4 files changed

+105
-55
lines changed

packages/next/src/build/templates/pages.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,8 @@ export async function handler(
289289
reactLoadableManifest,
290290

291291
assetPrefix: nextConfig.assetPrefix,
292-
strictNextHead: Boolean(
293-
nextConfig.experimental.strictNextHead
294-
),
292+
strictNextHead:
293+
nextConfig.experimental.strictNextHead ?? true,
295294
previewProps: prerenderManifest.preview,
296295
images: nextConfig.images as any,
297296
nextConfigOutput: nextConfig.output,

test/development/pages-dir/client-navigation/fixture/next.config.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ module.exports = {
44
// Make sure entries are not getting disposed.
55
maxInactiveAge: 1000 * 60 * 60,
66
},
7-
experimental: {
8-
strictNextHead: process.env.TEST_STRICT_NEXT_HEAD !== 'false',
9-
},
7+
experimental:
8+
process.env.TEST_STRICT_NEXT_HEAD !== undefined
9+
? {
10+
strictNextHead: process.env.TEST_STRICT_NEXT_HEAD === 'true',
11+
}
12+
: {},
1013
// scroll position can be finicky with the
1114
// indicators showing so hide by default
1215
devIndicators: false,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Head from 'next/head'
2+
3+
export default function Page() {
4+
return (
5+
<div>
6+
<Head>
7+
<title>Title Page</title>
8+
<meta property="og:title" content="Title Content" />
9+
<meta name="description" content="Description Content" />
10+
</Head>
11+
<p>This is a page!</p>
12+
</div>
13+
)
14+
}

test/development/pages-dir/client-navigation/rendering-head.test.ts

Lines changed: 83 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import path from 'path'
88
const isReact18 = parseInt(process.env.NEXT_TEST_REACT_VERSION) === 18
99

1010
describe('Client Navigation rendering <Head />', () => {
11-
describe.each([[false], [true]])(
11+
describe.each([[false], [true], [undefined]])(
1212
'with strictNextHead=%s',
1313
(strictNextHead) => {
1414
const { next } = nextTestSetup({
1515
files: path.join(__dirname, 'fixture'),
16-
env: {
17-
TEST_STRICT_NEXT_HEAD: String(strictNextHead),
18-
},
16+
env:
17+
strictNextHead !== undefined
18+
? {
19+
TEST_STRICT_NEXT_HEAD: String(strictNextHead),
20+
}
21+
: {},
1922
})
2023

2124
function render(
@@ -37,7 +40,7 @@ describe('Client Navigation rendering <Head />', () => {
3740
test('header renders default charset', async () => {
3841
const html = await render('/default-head')
3942
expect(html).toContain(
40-
strictNextHead
43+
strictNextHead !== false
4144
? '<meta charSet="utf-8" data-next-head=""/>'
4245
: '<meta charSet="utf-8"/>'
4346
)
@@ -47,7 +50,7 @@ describe('Client Navigation rendering <Head />', () => {
4750
test('header renders default viewport', async () => {
4851
const html = await render('/default-head')
4952
expect(html).toContain(
50-
strictNextHead
53+
strictNextHead !== false
5154
? '<meta name="viewport" content="width=device-width" data-next-head=""/>'
5255
: '<meta name="viewport" content="width=device-width"/>'
5356
)
@@ -56,17 +59,17 @@ describe('Client Navigation rendering <Head />', () => {
5659
test('header helper renders header information', async () => {
5760
const html = await render('/head')
5861
expect(html).toContain(
59-
strictNextHead
62+
strictNextHead !== false
6063
? '<meta charSet="iso-8859-5" data-next-head=""/>'
6164
: '<meta charSet="iso-8859-5"/>'
6265
)
6366
expect(html).toContain(
64-
strictNextHead
67+
strictNextHead !== false
6568
? '<meta content="my meta" data-next-head=""/>'
6669
: '<meta content="my meta"/>'
6770
)
6871
expect(html).toContain(
69-
strictNextHead
72+
strictNextHead !== false
7073
? '<meta name="viewport" content="width=device-width,initial-scale=1" data-next-head=""/>'
7174
: '<meta name="viewport" content="width=device-width,initial-scale=1"/>'
7275
)
@@ -76,17 +79,17 @@ describe('Client Navigation rendering <Head />', () => {
7679
test('header helper dedupes tags', async () => {
7780
const html = await render('/head')
7881
expect(html).toContain(
79-
strictNextHead
82+
strictNextHead !== false
8083
? '<meta charSet="iso-8859-5" data-next-head=""/>'
8184
: '<meta charSet="iso-8859-5"/>'
8285
)
8386
expect(html).not.toContain(
84-
strictNextHead
87+
strictNextHead !== false
8588
? '<meta charSet="utf-8" data-next-head=""/>'
8689
: '<meta charSet="utf-8"/>'
8790
)
8891
expect(html).toContain(
89-
strictNextHead
92+
strictNextHead !== false
9093
? '<meta name="viewport" content="width=device-width,initial-scale=1" data-next-head=""/>'
9194
: '<meta name="viewport" content="width=device-width,initial-scale=1"/>'
9295
)
@@ -96,24 +99,25 @@ describe('Client Navigation rendering <Head />', () => {
9699
'<meta name="viewport" content="width=device-width"'
97100
)
98101
expect(html).toContain(
99-
strictNextHead
102+
strictNextHead !== false
100103
? '<meta content="my meta" data-next-head=""/>'
101104
: '<meta content="my meta"/>'
102105
)
103106
expect(html).toContain(
104-
strictNextHead
107+
strictNextHead !== false
105108
? '<link rel="stylesheet" href="/dup-style.css" data-next-head=""/><link rel="stylesheet" href="/dup-style.css" data-next-head=""/>'
106109
: '<link rel="stylesheet" href="/dup-style.css"/><link rel="stylesheet" href="/dup-style.css"/>'
107110
)
108-
const dedupeLink = strictNextHead
109-
? '<link rel="stylesheet" href="dedupe-style.css" data-next-head=""/>'
110-
: '<link rel="stylesheet" href="dedupe-style.css"/>'
111+
const dedupeLink =
112+
strictNextHead !== false
113+
? '<link rel="stylesheet" href="dedupe-style.css" data-next-head=""/>'
114+
: '<link rel="stylesheet" href="dedupe-style.css"/>'
111115
expect(html).toContain(dedupeLink)
112116
expect(
113117
html.substring(html.indexOf(dedupeLink) + dedupeLink.length)
114118
).not.toContain('<link rel="stylesheet" href="dedupe-style.css"')
115119
expect(html).toContain(
116-
strictNextHead
120+
strictNextHead !== false
117121
? '<link rel="alternate" hrefLang="en" href="/last/en" data-next-head=""/>'
118122
: '<link rel="alternate" hrefLang="en" href="/last/en"/>'
119123
)
@@ -129,12 +133,12 @@ describe('Client Navigation rendering <Head />', () => {
129133
// Expect exactly one `viewport`
130134
expect((html.match(/name="viewport"/g) || []).length).toBe(1)
131135
expect(html).toContain(
132-
strictNextHead
136+
strictNextHead !== false
133137
? '<meta charSet="iso-8859-1" data-next-head=""/>'
134138
: '<meta charSet="iso-8859-1"/>'
135139
)
136140
expect(html).toContain(
137-
strictNextHead
141+
strictNextHead !== false
138142
? '<meta name="viewport" content="width=500" data-next-head=""/>'
139143
: '<meta name="viewport" content="width=500"/>'
140144
)
@@ -144,98 +148,98 @@ describe('Client Navigation rendering <Head />', () => {
144148
const html = await render('/head')
145149
console.log(html)
146150
expect(html).toContain(
147-
strictNextHead
151+
strictNextHead !== false
148152
? '<meta property="article:tag" content="tag1" data-next-head=""/>'
149153
: '<meta property="article:tag" content="tag1"/>'
150154
)
151155
expect(html).toContain(
152-
strictNextHead
156+
strictNextHead !== false
153157
? '<meta property="article:tag" content="tag2" data-next-head=""/>'
154158
: '<meta property="article:tag" content="tag2"/>'
155159
)
156160
expect(html).not.toContain('<meta property="dedupe:tag" content="tag3"')
157161
expect(html).toContain(
158-
strictNextHead
162+
strictNextHead !== false
159163
? '<meta property="dedupe:tag" content="tag4" data-next-head=""/>'
160164
: '<meta property="dedupe:tag" content="tag4"/>'
161165
)
162166
expect(html).toContain(
163-
strictNextHead
167+
strictNextHead !== false
164168
? '<meta property="og:image" content="ogImageTag1" data-next-head=""/>'
165169
: '<meta property="og:image" content="ogImageTag1"/>'
166170
)
167171
expect(html).toContain(
168-
strictNextHead
172+
strictNextHead !== false
169173
? '<meta property="og:image" content="ogImageTag2" data-next-head=""/>'
170174
: '<meta property="og:image" content="ogImageTag2"/>'
171175
)
172176
expect(html).toContain(
173-
strictNextHead
177+
strictNextHead !== false
174178
? '<meta property="og:image:alt" content="ogImageAltTag1" data-next-head=""/>'
175179
: '<meta property="og:image:alt" content="ogImageAltTag1"/>'
176180
)
177181
expect(html).toContain(
178-
strictNextHead
182+
strictNextHead !== false
179183
? '<meta property="og:image:alt" content="ogImageAltTag2" data-next-head=""/>'
180184
: '<meta property="og:image:alt" content="ogImageAltTag2"/>'
181185
)
182186
expect(html).toContain(
183-
strictNextHead
187+
strictNextHead !== false
184188
? '<meta property="og:image:width" content="ogImageWidthTag1" data-next-head=""/>'
185189
: '<meta property="og:image:width" content="ogImageWidthTag1"/>'
186190
)
187191
expect(html).toContain(
188-
strictNextHead
192+
strictNextHead !== false
189193
? '<meta property="og:image:width" content="ogImageWidthTag2" data-next-head=""/>'
190194
: '<meta property="og:image:width" content="ogImageWidthTag2"/>'
191195
)
192196
expect(html).toContain(
193-
strictNextHead
197+
strictNextHead !== false
194198
? '<meta property="og:image:height" content="ogImageHeightTag1" data-next-head=""/>'
195199
: '<meta property="og:image:height" content="ogImageHeightTag1"/>'
196200
)
197201
expect(html).toContain(
198-
strictNextHead
202+
strictNextHead !== false
199203
? '<meta property="og:image:height" content="ogImageHeightTag2" data-next-head=""/>'
200204
: '<meta property="og:image:height" content="ogImageHeightTag2"/>'
201205
)
202206
expect(html).toContain(
203-
strictNextHead
207+
strictNextHead !== false
204208
? '<meta property="og:image:type" content="ogImageTypeTag1" data-next-head=""/>'
205209
: '<meta property="og:image:type" content="ogImageTypeTag1"/>'
206210
)
207211
expect(html).toContain(
208-
strictNextHead
212+
strictNextHead !== false
209213
? '<meta property="og:image:type" content="ogImageTypeTag2" data-next-head=""/>'
210214
: '<meta property="og:image:type" content="ogImageTypeTag2"/>'
211215
)
212216
expect(html).toContain(
213-
strictNextHead
217+
strictNextHead !== false
214218
? '<meta property="og:image:secure_url" content="ogImageSecureUrlTag1" data-next-head=""/>'
215219
: '<meta property="og:image:secure_url" content="ogImageSecureUrlTag1"/>'
216220
)
217221
expect(html).toContain(
218-
strictNextHead
222+
strictNextHead !== false
219223
? '<meta property="og:image:secure_url" content="ogImageSecureUrlTag2" data-next-head=""/>'
220224
: '<meta property="og:image:secure_url" content="ogImageSecureUrlTag2"/>'
221225
)
222226
expect(html).toContain(
223-
strictNextHead
227+
strictNextHead !== false
224228
? '<meta property="og:image:url" content="ogImageUrlTag1" data-next-head=""/>'
225229
: '<meta property="og:image:url" content="ogImageUrlTag1"/>'
226230
)
227231
expect(html).toContain(
228-
strictNextHead
232+
strictNextHead !== false
229233
? '<meta property="og:image:url" content="ogImageUrlTag2" data-next-head=""/>'
230234
: '<meta property="og:image:url" content="ogImageUrlTag2"/>'
231235
)
232236
expect(html).toContain(
233-
strictNextHead
237+
strictNextHead !== false
234238
? '<meta property="fb:pages" content="fbpages1" data-next-head=""/>'
235239
: '<meta property="fb:pages" content="fbpages1"/>'
236240
)
237241
expect(html).toContain(
238-
strictNextHead
242+
strictNextHead !== false
239243
? '<meta property="fb:pages" content="fbpages2" data-next-head=""/>'
240244
: '<meta property="fb:pages" content="fbpages2"/>'
241245
)
@@ -244,12 +248,12 @@ describe('Client Navigation rendering <Head />', () => {
244248
test('header helper avoids dedupe of meta tags with the same name if they use unique keys', async () => {
245249
const html = await render('/head')
246250
expect(html).toContain(
247-
strictNextHead
251+
strictNextHead !== false
248252
? '<meta name="citation_author" content="authorName1" data-next-head=""/>'
249253
: '<meta name="citation_author" content="authorName1"/>'
250254
)
251255
expect(html).toContain(
252-
strictNextHead
256+
strictNextHead !== false
253257
? '<meta name="citation_author" content="authorName2" data-next-head=""/>'
254258
: '<meta name="citation_author" content="authorName2"/>'
255259
)
@@ -258,12 +262,12 @@ describe('Client Navigation rendering <Head />', () => {
258262
test('header helper renders Fragment children', async () => {
259263
const html = await render('/head')
260264
expect(html).toContain(
261-
strictNextHead
265+
strictNextHead !== false
262266
? '<title data-next-head="">Fragment title</title>'
263267
: '<title>Fragment title</title>'
264268
)
265269
expect(html).toContain(
266-
strictNextHead
270+
strictNextHead !== false
267271
? '<meta content="meta fragment" data-next-head=""/>'
268272
: '<meta content="meta fragment"/>'
269273
)
@@ -272,27 +276,28 @@ describe('Client Navigation rendering <Head />', () => {
272276
test('header helper renders boolean attributes correctly children', async () => {
273277
const html = await render('/head')
274278
expect(html).toContain(
275-
strictNextHead
279+
strictNextHead !== false
276280
? '<script src="/test-async-false.js" data-next-head="">'
277281
: '<script src="/test-async-false.js">'
278282
)
279283
expect(html).toContain(
280-
strictNextHead
284+
strictNextHead !== false
281285
? '<script src="/test-async-true.js" async="" data-next-head="">'
282286
: ''
283287
)
284288
expect(html).toContain(
285-
strictNextHead
289+
strictNextHead !== false
286290
? '<script src="/test-defer.js" defer="" data-next-head="">'
287291
: ''
288292
)
289293
})
290294

291295
it('should place charset element at the top of <head>', async () => {
292296
const html = await render('/head-priority')
293-
const nextHeadElement = strictNextHead
294-
? '<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=""/>'
295-
: '<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"/>'
297+
const nextHeadElement =
298+
strictNextHead !== false
299+
? '<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=""/>'
300+
: '<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"/>'
296301
const documentHeadElement =
297302
'<meta name="keywords" content="document head test"/>'
298303

@@ -304,6 +309,35 @@ describe('Client Navigation rendering <Head />', () => {
304309
: `<head>${nextHeadElement}${documentHeadElement}`
305310
)
306311
})
312+
313+
test('custom meta properties are rendered only once', async () => {
314+
const browser = await next.browser('/head-with-ssr-props')
315+
316+
// Check that title appears only once
317+
const titleElements = await browser.elementsByCss('title')
318+
expect(titleElements).toHaveLength(1)
319+
const titleText = await browser.elementByCss('title').text()
320+
expect(titleText).toBe('Title Page')
321+
322+
// Check that each meta property appears only once
323+
const ogTitleElements = await browser.elementsByCss(
324+
'meta[property="og:title"]'
325+
)
326+
expect(ogTitleElements).toHaveLength(1)
327+
const ogTitleContent = await browser
328+
.elementByCss('meta[property="og:title"]')
329+
.getAttribute('content')
330+
expect(ogTitleContent).toBe('Title Content')
331+
332+
const descriptionElements = await browser.elementsByCss(
333+
'meta[name="description"]'
334+
)
335+
expect(descriptionElements).toHaveLength(1)
336+
const descriptionContent = await browser
337+
.elementByCss('meta[name="description"]')
338+
.getAttribute('content')
339+
expect(descriptionContent).toBe('Description Content')
340+
})
307341
}
308342
)
309343
})

0 commit comments

Comments
 (0)