Skip to content

Commit b9149cc

Browse files
authored
Include regular stack trace in serialized errors from Fizz (#28684)
We previously only included the component stack. Cleaned up the fields in Fizz server that wasn't using consistent hidden classes in dev vs prod. Added a prefix to errors serialized from server rendering. It can be a bit confusing to see where this error came from otherwise since it didn't come from elsewhere on the client. It's really kind of confusing with other recoverable errors that happen on the client too.
1 parent 5d4b758 commit b9149cc

13 files changed

+229
-104
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,22 +1226,29 @@ export function isSuspenseInstanceFallback(
12261226

12271227
export function getSuspenseInstanceFallbackErrorDetails(
12281228
instance: SuspenseInstance,
1229-
): {digest: ?string, message?: string, stack?: string} {
1229+
): {
1230+
digest: ?string,
1231+
message?: string,
1232+
stack?: string,
1233+
componentStack?: string,
1234+
} {
12301235
const dataset =
12311236
instance.nextSibling && ((instance.nextSibling: any): HTMLElement).dataset;
1232-
let digest, message, stack;
1237+
let digest, message, stack, componentStack;
12331238
if (dataset) {
12341239
digest = dataset.dgst;
12351240
if (__DEV__) {
12361241
message = dataset.msg;
12371242
stack = dataset.stck;
1243+
componentStack = dataset.cstck;
12381244
}
12391245
}
12401246
if (__DEV__) {
12411247
return {
12421248
message,
12431249
digest,
12441250
stack,
1251+
componentStack,
12451252
};
12461253
} else {
12471254
// Object gets DCE'd if constructed in tail position and matches callsite destructuring

packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ function handleNode(node_: Node) {
9494
dataset['dgst'],
9595
dataset['msg'],
9696
dataset['stck'],
97+
dataset['cstck'],
9798
);
9899
node.remove();
99100
} else if (dataset['rri'] != null) {

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3801,6 +3801,8 @@ const clientRenderedSuspenseBoundaryError1B =
38013801
stringToPrecomputedChunk(' data-msg="');
38023802
const clientRenderedSuspenseBoundaryError1C =
38033803
stringToPrecomputedChunk(' data-stck="');
3804+
const clientRenderedSuspenseBoundaryError1D =
3805+
stringToPrecomputedChunk(' data-cstck="');
38043806
const clientRenderedSuspenseBoundaryError2 =
38053807
stringToPrecomputedChunk('></template>');
38063808

@@ -3843,7 +3845,8 @@ export function writeStartClientRenderedSuspenseBoundary(
38433845
destination: Destination,
38443846
renderState: RenderState,
38453847
errorDigest: ?string,
3846-
errorMesssage: ?string,
3848+
errorMessage: ?string,
3849+
errorStack: ?string,
38473850
errorComponentStack: ?string,
38483851
): boolean {
38493852
let result;
@@ -3861,19 +3864,27 @@ export function writeStartClientRenderedSuspenseBoundary(
38613864
);
38623865
}
38633866
if (__DEV__) {
3864-
if (errorMesssage) {
3867+
if (errorMessage) {
38653868
writeChunk(destination, clientRenderedSuspenseBoundaryError1B);
38663869
writeChunk(
38673870
destination,
3868-
stringToChunk(escapeTextForBrowser(errorMesssage)),
3871+
stringToChunk(escapeTextForBrowser(errorMessage)),
38693872
);
38703873
writeChunk(
38713874
destination,
38723875
clientRenderedSuspenseBoundaryErrorAttrInterstitial,
38733876
);
38743877
}
3875-
if (errorComponentStack) {
3878+
if (errorStack) {
38763879
writeChunk(destination, clientRenderedSuspenseBoundaryError1C);
3880+
writeChunk(destination, stringToChunk(escapeTextForBrowser(errorStack)));
3881+
writeChunk(
3882+
destination,
3883+
clientRenderedSuspenseBoundaryErrorAttrInterstitial,
3884+
);
3885+
}
3886+
if (errorComponentStack) {
3887+
writeChunk(destination, clientRenderedSuspenseBoundaryError1D);
38773888
writeChunk(
38783889
destination,
38793890
stringToChunk(escapeTextForBrowser(errorComponentStack)),
@@ -4236,6 +4247,7 @@ const clientRenderData1 = stringToPrecomputedChunk(
42364247
const clientRenderData2 = stringToPrecomputedChunk('" data-dgst="');
42374248
const clientRenderData3 = stringToPrecomputedChunk('" data-msg="');
42384249
const clientRenderData4 = stringToPrecomputedChunk('" data-stck="');
4250+
const clientRenderData5 = stringToPrecomputedChunk('" data-cstck="');
42394251
const clientRenderDataEnd = dataElementQuotedEnd;
42404252

42414253
export function writeClientRenderBoundaryInstruction(
@@ -4244,8 +4256,9 @@ export function writeClientRenderBoundaryInstruction(
42444256
renderState: RenderState,
42454257
id: number,
42464258
errorDigest: ?string,
4247-
errorMessage?: string,
4248-
errorComponentStack?: string,
4259+
errorMessage: ?string,
4260+
errorStack: ?string,
4261+
errorComponentStack: ?string,
42494262
): boolean {
42504263
const scriptFormat =
42514264
!enableFizzExternalRuntime ||
@@ -4276,7 +4289,7 @@ export function writeClientRenderBoundaryInstruction(
42764289
writeChunk(destination, clientRenderScript1A);
42774290
}
42784291

4279-
if (errorDigest || errorMessage || errorComponentStack) {
4292+
if (errorDigest || errorMessage || errorStack || errorComponentStack) {
42804293
if (scriptFormat) {
42814294
// ,"JSONString"
42824295
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
@@ -4293,7 +4306,7 @@ export function writeClientRenderBoundaryInstruction(
42934306
);
42944307
}
42954308
}
4296-
if (errorMessage || errorComponentStack) {
4309+
if (errorMessage || errorStack || errorComponentStack) {
42974310
if (scriptFormat) {
42984311
// ,"JSONString"
42994312
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
@@ -4310,6 +4323,23 @@ export function writeClientRenderBoundaryInstruction(
43104323
);
43114324
}
43124325
}
4326+
if (errorStack || errorComponentStack) {
4327+
// ,"JSONString"
4328+
if (scriptFormat) {
4329+
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
4330+
writeChunk(
4331+
destination,
4332+
stringToChunk(escapeJSStringsForInstructionScripts(errorStack || '')),
4333+
);
4334+
} else {
4335+
// " data-stck="HTMLString
4336+
writeChunk(destination, clientRenderData4);
4337+
writeChunk(
4338+
destination,
4339+
stringToChunk(escapeTextForBrowser(errorStack || '')),
4340+
);
4341+
}
4342+
}
43134343
if (errorComponentStack) {
43144344
// ,"JSONString"
43154345
if (scriptFormat) {
@@ -4321,8 +4351,8 @@ export function writeClientRenderBoundaryInstruction(
43214351
),
43224352
);
43234353
} else {
4324-
// " data-stck="HTMLString
4325-
writeChunk(destination, clientRenderData4);
4354+
// " data-cstck="HTMLString
4355+
writeChunk(destination, clientRenderData5);
43264356
writeChunk(
43274357
destination,
43284358
stringToChunk(escapeTextForBrowser(errorComponentStack)),

packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export function writeStartClientRenderedSuspenseBoundary(
219219
// flushing these error arguments are not currently supported in this legacy streaming format.
220220
errorDigest: ?string,
221221
errorMessage: ?string,
222+
errorStack: ?string,
222223
errorComponentStack: ?string,
223224
): boolean {
224225
if (renderState.generateStaticMarkup) {
@@ -231,6 +232,7 @@ export function writeStartClientRenderedSuspenseBoundary(
231232
renderState,
232233
errorDigest,
233234
errorMessage,
235+
errorStack,
234236
errorComponentStack,
235237
);
236238
}

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function clientRenderBoundary(
1919
suspenseBoundaryID,
2020
errorDigest,
2121
errorMsg,
22+
errorStack,
2223
errorComponentStack,
2324
) {
2425
// Find the fallback's first element.
@@ -36,7 +37,8 @@ export function clientRenderBoundary(
3637
const dataset = suspenseIdNode.dataset;
3738
if (errorDigest) dataset['dgst'] = errorDigest;
3839
if (errorMsg) dataset['msg'] = errorMsg;
39-
if (errorComponentStack) dataset['stck'] = errorComponentStack;
40+
if (errorStack) dataset['stck'] = errorStack;
41+
if (errorComponentStack) dataset['cstck'] = errorComponentStack;
4042
// Tell React to retry it if the parent already hydrated.
4143
if (suspenseNode['_reactRetry']) {
4244
suspenseNode['_reactRetry']();

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,8 @@ describe('ReactDOMFizzServer', () => {
796796
errors,
797797
[
798798
[
799-
theError.message,
799+
'Switched to client rendering because the server rendering errored:\n\n' +
800+
theError.message,
800801
expectedDigest,
801802
componentStack(['Lazy', 'Suspense', 'div', 'App']),
802803
],
@@ -919,7 +920,8 @@ describe('ReactDOMFizzServer', () => {
919920
errors,
920921
[
921922
[
922-
theError.message,
923+
'Switched to client rendering because the server rendering errored:\n\n' +
924+
theError.message,
923925
expectedDigest,
924926
componentStack(['Lazy', 'Suspense', 'div', 'App']),
925927
],
@@ -1002,7 +1004,8 @@ describe('ReactDOMFizzServer', () => {
10021004
errors,
10031005
[
10041006
[
1005-
theError.message,
1007+
'Switched to client rendering because the server rendering errored:\n\n' +
1008+
theError.message,
10061009
expectedDigest,
10071010
componentStack([
10081011
'Erroring',
@@ -1088,7 +1091,8 @@ describe('ReactDOMFizzServer', () => {
10881091
errors,
10891092
[
10901093
[
1091-
theError.message,
1094+
'Switched to client rendering because the server rendering errored:\n\n' +
1095+
theError.message,
10921096
expectedDigest,
10931097
componentStack(['Lazy', 'Suspense', 'div', 'App']),
10941098
],
@@ -1414,13 +1418,15 @@ describe('ReactDOMFizzServer', () => {
14141418
errors,
14151419
[
14161420
[
1417-
'The server did not finish this Suspense boundary: The render was aborted by the server without a reason.',
1421+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
1422+
'The render was aborted by the server without a reason.',
14181423
expectedDigest,
14191424
// We get the stack of the task when it was aborted which is why we see `h1`
14201425
componentStack(['h1', 'Suspense', 'div', 'App']),
14211426
],
14221427
[
1423-
'The server did not finish this Suspense boundary: The render was aborted by the server without a reason.',
1428+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
1429+
'The render was aborted by the server without a reason.',
14241430
expectedDigest,
14251431
componentStack(['Suspense', 'main', 'div', 'App']),
14261432
],
@@ -2155,7 +2161,8 @@ describe('ReactDOMFizzServer', () => {
21552161
errors,
21562162
[
21572163
[
2158-
theError.message,
2164+
'Switched to client rendering because the server rendering errored:\n\n' +
2165+
theError.message,
21592166
expectedDigest,
21602167
componentStack([
21612168
'AsyncText',
@@ -3441,12 +3448,14 @@ describe('ReactDOMFizzServer', () => {
34413448
errors,
34423449
[
34433450
[
3444-
'The server did not finish this Suspense boundary: foobar',
3451+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
3452+
'foobar',
34453453
'a digest',
34463454
componentStack(['Suspense', 'p', 'div', 'App']),
34473455
],
34483456
[
3449-
'The server did not finish this Suspense boundary: foobar',
3457+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
3458+
'foobar',
34503459
'a digest',
34513460
componentStack(['Suspense', 'span', 'div', 'App']),
34523461
],
@@ -3522,12 +3531,14 @@ describe('ReactDOMFizzServer', () => {
35223531
errors,
35233532
[
35243533
[
3525-
'The server did not finish this Suspense boundary: uh oh',
3534+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
3535+
'uh oh',
35263536
'a digest',
35273537
componentStack(['Suspense', 'p', 'div', 'App']),
35283538
],
35293539
[
3530-
'The server did not finish this Suspense boundary: uh oh',
3540+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
3541+
'uh oh',
35313542
'a digest',
35323543
componentStack(['Suspense', 'span', 'div', 'App']),
35333544
],
@@ -4001,7 +4012,8 @@ describe('ReactDOMFizzServer', () => {
40014012
errors,
40024013
[
40034014
[
4004-
theError.message,
4015+
'Switched to client rendering because the server rendering errored:\n\n' +
4016+
theError.message,
40054017
expectedDigest,
40064018
componentStack(['Erroring', 'Suspense', 'div', 'App']),
40074019
],
@@ -6782,7 +6794,14 @@ describe('ReactDOMFizzServer', () => {
67826794

67836795
expect(recoverableErrors).toEqual(
67846796
__DEV__
6785-
? ['server error', 'replay error', 'server error']
6797+
? [
6798+
'Switched to client rendering because the server rendering errored:\n\n' +
6799+
'server error',
6800+
'Switched to client rendering because the server rendering errored:\n\n' +
6801+
'replay error',
6802+
'Switched to client rendering because the server rendering errored:\n\n' +
6803+
'server error',
6804+
]
67866805
: [
67876806
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
67886807
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
@@ -6941,8 +6960,10 @@ describe('ReactDOMFizzServer', () => {
69416960
expect(recoverableErrors).toEqual(
69426961
__DEV__
69436962
? [
6944-
'The server did not finish this Suspense boundary: aborted',
6945-
'The server did not finish this Suspense boundary: aborted',
6963+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
6964+
'aborted',
6965+
'Switched to client rendering because the server rendering aborted due to:\n\n' +
6966+
'aborted',
69466967
]
69476968
: [
69486969
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
@@ -7113,8 +7134,10 @@ describe('ReactDOMFizzServer', () => {
71137134
// It surfaced in two different suspense boundaries.
71147135
__DEV__
71157136
? [
7116-
'The server did not finish this Suspense boundary: replay error',
7117-
'The server did not finish this Suspense boundary: replay error',
7137+
'Switched to client rendering because the server rendering errored:\n\n' +
7138+
'replay error',
7139+
'Switched to client rendering because the server rendering errored:\n\n' +
7140+
'replay error',
71187141
]
71197142
: [
71207143
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
@@ -7240,7 +7263,10 @@ describe('ReactDOMFizzServer', () => {
72407263

72417264
expect(recoverableErrors).toEqual(
72427265
__DEV__
7243-
? ['server error']
7266+
? [
7267+
'Switched to client rendering because the server rendering errored:\n\n' +
7268+
'server error',
7269+
]
72447270
: [
72457271
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
72467272
],

0 commit comments

Comments
 (0)