Skip to content

Commit 953b4c5

Browse files
authored
Fix potential race condition with request ID in dev mode (#84532)
In development mode, the request ID needs to be available before the bootstrap script executes and triggers hydration. Previously, the request ID was set inline with the flight data stream in a script tag that was inserted after the bootstrap script, which could lead to a race condition where `self.__next_r` was accessed before it was defined. With this PR, we're moving the request ID initialization to `bootstrapScriptContent`, which is inserted as a script tag by React immediately before the bootstrap script. This ensures that the request ID is always defined when the bootstrap script runs. I wasn't actually able to reproduce the issue, so I also couldn't add a test for it. But we did receive reports from users that this was happening, which is plausible given the previous script order.
1 parent 665977f commit 953b4c5

File tree

2 files changed

+20
-32
lines changed

2 files changed

+20
-32
lines changed

packages/next/src/server/app-render/app-render.tsx

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,6 +2206,13 @@ async function renderToStream(
22062206
page
22072207
)
22082208

2209+
// In development mode, set the request ID as a global variable, before the
2210+
// bootstrap script is executed, which depends on it during hydration.
2211+
const bootstrapScriptContent =
2212+
process.env.NODE_ENV !== 'production'
2213+
? `self.__next_r=${JSON.stringify(requestId)}`
2214+
: undefined
2215+
22092216
const reactServerErrorsByDigest: Map<string, DigestedError> = new Map()
22102217
const silenceLogger = false
22112218
function onHTMLRenderRSCError(err: DigestedError) {
@@ -2377,8 +2384,7 @@ async function renderToStream(
23772384
const inlinedReactServerDataStream = createInlinedDataReadableStream(
23782385
reactServerResult.tee(),
23792386
nonce,
2380-
formState,
2381-
requestId
2387+
formState
23822388
)
23832389

23842390
return chainStreams(
@@ -2425,8 +2431,7 @@ async function renderToStream(
24252431
inlinedDataStream: createInlinedDataReadableStream(
24262432
reactServerResult.consume(),
24272433
nonce,
2428-
formState,
2429-
requestId
2434+
formState
24302435
),
24312436
getServerInsertedHTML,
24322437
getServerInsertedMetadata,
@@ -2459,6 +2464,7 @@ async function renderToStream(
24592464
})
24602465
},
24612466
maxHeadersLength: reactMaxHeadersLength,
2467+
bootstrapScriptContent,
24622468
bootstrapScripts: [bootstrapScript],
24632469
formState,
24642470
}
@@ -2495,8 +2501,7 @@ async function renderToStream(
24952501
inlinedDataStream: createInlinedDataReadableStream(
24962502
reactServerResult.consume(),
24972503
nonce,
2498-
formState,
2499-
requestId
2504+
formState
25002505
),
25012506
isStaticGeneration: generateStaticHTML,
25022507
isBuildTimePrerendering: ctx.workStore.isBuildTimePrerendering === true,
@@ -2613,6 +2618,7 @@ async function renderToStream(
26132618
),
26142619
streamOptions: {
26152620
nonce,
2621+
bootstrapScriptContent,
26162622
// Include hydration scripts in the HTML
26172623
bootstrapScripts: [errorBootstrapScript],
26182624
formState,
@@ -2645,8 +2651,7 @@ async function renderToStream(
26452651
// render
26462652
reactServerResult.consume(),
26472653
nonce,
2648-
formState,
2649-
requestId
2654+
formState
26502655
),
26512656
isStaticGeneration: generateStaticHTML,
26522657
isBuildTimePrerendering: ctx.workStore.isBuildTimePrerendering === true,
@@ -3362,7 +3367,6 @@ async function prerenderToStream(
33623367
nonce,
33633368
pagePath,
33643369
renderOpts,
3365-
requestId,
33663370
workStore,
33673371
} = ctx
33683372

@@ -4154,8 +4158,7 @@ async function prerenderToStream(
41544158
inlinedDataStream: createInlinedDataReadableStream(
41554159
reactServerResult.consumeAsStream(),
41564160
nonce,
4157-
formState,
4158-
requestId
4161+
formState
41594162
),
41604163
getServerInsertedHTML,
41614164
getServerInsertedMetadata,
@@ -4403,8 +4406,7 @@ async function prerenderToStream(
44034406
inlinedDataStream: createInlinedDataReadableStream(
44044407
reactServerResult.consumeAsStream(),
44054408
nonce,
4406-
formState,
4407-
requestId
4409+
formState
44084410
),
44094411
getServerInsertedHTML,
44104412
getServerInsertedMetadata,
@@ -4501,8 +4503,7 @@ async function prerenderToStream(
45014503
inlinedDataStream: createInlinedDataReadableStream(
45024504
reactServerResult.consumeAsStream(),
45034505
nonce,
4504-
formState,
4505-
requestId
4506+
formState
45064507
),
45074508
isStaticGeneration: true,
45084509
isBuildTimePrerendering:
@@ -4680,8 +4681,7 @@ async function prerenderToStream(
46804681
inlinedDataStream: createInlinedDataReadableStream(
46814682
flightStream,
46824683
nonce,
4683-
formState,
4684-
requestId
4684+
formState
46854685
),
46864686
isStaticGeneration: true,
46874687
isBuildTimePrerendering:

packages/next/src/server/app-render/use-flight-response.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ export function useFlightStream<T>(
103103
export function createInlinedDataReadableStream(
104104
flightStream: ReadableStream<Uint8Array>,
105105
nonce: string | undefined,
106-
formState: unknown | null,
107-
requestId: string
106+
formState: unknown | null
108107
): ReadableStream<Uint8Array> {
109108
const startScriptTag = nonce
110109
? `<script nonce=${JSON.stringify(nonce)}>`
@@ -117,12 +116,7 @@ export function createInlinedDataReadableStream(
117116
type: 'bytes',
118117
start(controller) {
119118
try {
120-
writeInitialInstructions(
121-
controller,
122-
startScriptTag,
123-
formState,
124-
requestId
125-
)
119+
writeInitialInstructions(controller, startScriptTag, formState)
126120
} catch (error) {
127121
// during encoding or enqueueing forward the error downstream
128122
controller.error(error)
@@ -166,18 +160,12 @@ export function createInlinedDataReadableStream(
166160
function writeInitialInstructions(
167161
controller: ReadableStreamDefaultController,
168162
scriptStart: string,
169-
formState: unknown | null,
170-
requestId: string
163+
formState: unknown | null
171164
) {
172165
let scriptContents = `(self.__next_f=self.__next_f||[]).push(${htmlEscapeJsonString(
173166
JSON.stringify([INLINE_FLIGHT_PAYLOAD_BOOTSTRAP])
174167
)})`
175168

176-
if (process.env.NODE_ENV !== 'production') {
177-
// The request ID is only needed in development mode.
178-
scriptContents = `self.__next_r=${JSON.stringify(requestId)};${scriptContents}`
179-
}
180-
181169
if (formState != null) {
182170
scriptContents += `;self.__next_f.push(${htmlEscapeJsonString(
183171
JSON.stringify([INLINE_FLIGHT_PAYLOAD_FORM_STATE, formState])

0 commit comments

Comments
 (0)