Fix: only inject clientTraceMetadata into html page once (#66763)

This commit is contained in:
Jiachi Liu 2024-06-13 21:58:01 +02:00 committed by GitHub
parent 918af1667a
commit 46441387be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 18 deletions

View file

@ -33,7 +33,12 @@ export function makeGetServerInsertedHTML({
basePath: string
}) {
let flushedErrorMetaTagsUntilIndex = 0
let hasUnflushedPolyfills = polyfills.length !== 0
// flag for static content that only needs to be flushed once
let hasFlushedInitially = false
const polyfillTags = polyfills.map((polyfill) => {
return <script key={polyfill.src} {...polyfill} />
})
return async function getServerInsertedHTML() {
// Loop through all the errors that have been captured but not yet
@ -71,11 +76,18 @@ export function makeGetServerInsertedHTML({
}
}
const traceMetaTags = (tracingMetadata || []).map(
({ key, value }, index) => (
<meta key={`next-trace-data-${index}`} name={key} content={value} />
)
)
const serverInsertedHTML = renderServerInsertedHTML()
// Skip React rendering if we know the content is empty.
if (
!hasUnflushedPolyfills &&
polyfillTags.length === 0 &&
traceMetaTags.length === 0 &&
errorMetaTags.length === 0 &&
Array.isArray(serverInsertedHTML) &&
serverInsertedHTML.length === 0
@ -87,23 +99,10 @@ export function makeGetServerInsertedHTML({
<>
{
/* Insert the polyfills if they haven't been flushed yet. */
hasUnflushedPolyfills &&
polyfills.map((polyfill) => {
return <script key={polyfill.src} {...polyfill} />
})
hasFlushedInitially ? null : polyfillTags
}
{serverInsertedHTML}
{tracingMetadata
? tracingMetadata.map(({ key, value }) => {
return (
<meta
key={`next-trace-data-${key}:${value}`}
name={key}
content={value}
/>
)
})
: null}
{hasFlushedInitially ? null : traceMetaTags}
{errorMetaTags}
</>,
{
@ -113,7 +112,7 @@ export function makeGetServerInsertedHTML({
}
)
hasUnflushedPolyfills = false
hasFlushedInitially = true
// There's no need to wait for the stream to be ready
// e.g. calling `await stream.allReady` because `streamToString` will

View file

@ -0,0 +1,12 @@
'use client'
import { useServerInsertedHTML } from 'next/navigation'
export function Client() {
useServerInsertedHTML(() => (
<>
<meta name="client-inserted-key" content="client-inserted-value" />
</>
))
return null
}

View file

@ -0,0 +1,26 @@
import React, { Suspense } from 'react'
import { Client } from './client'
async function Component() {
await new Promise((resolve) => setTimeout(resolve, 200))
return (
<div>
<h1>Component</h1>
<Client />
</div>
)
}
function Suspensey() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Component />
</Suspense>
)
}
export default function Page() {
return <Suspensey />
}
export const dynamic = 'force-dynamic'

View file

@ -37,6 +37,12 @@ describe('clientTraceMetadata', () => {
expect(firstLoadSpanIdContent).not.toBe(secondLoadSpanIdContent)
})
it('should only insert the client trace metadata once', async () => {
const html = await next.render('/suspense')
const matches = html.match(/meta name="my-test-key-1"/g)
expect(matches.length).toBe(1)
})
if (isNextDev) {
describe('next dev only', () => {
it('should inject propagation data for a statically server-side-rendered page', async () => {