Skip to content

Commit

Permalink
Add a prefix to errors serialized from server rendering
Browse files Browse the repository at this point in the history
It can be a bit confusing to see where this error came from otherwise
since it didn't come from elsewhere on the client.
  • Loading branch information
sebmarkbage committed Mar 29, 2024
1 parent 73a50cb commit 4255c4e
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 59 deletions.
62 changes: 44 additions & 18 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,8 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
theError.message,
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
componentStack(['Lazy', 'Suspense', 'div', 'App']),
],
Expand Down Expand Up @@ -909,7 +910,8 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
theError.message,
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
componentStack(['Lazy', 'Suspense', 'div', 'App']),
],
Expand Down Expand Up @@ -992,7 +994,8 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
theError.message,
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
componentStack([
'Erroring',
Expand Down Expand Up @@ -1078,7 +1081,8 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
theError.message,
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
componentStack(['Lazy', 'Suspense', 'div', 'App']),
],
Expand Down Expand Up @@ -1404,13 +1408,15 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
'The server did not finish this Suspense boundary: The render was aborted by the server without a reason.',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'The render was aborted by the server without a reason.',
expectedDigest,
// We get the stack of the task when it was aborted which is why we see `h1`
componentStack(['h1', 'Suspense', 'div', 'App']),
],
[
'The server did not finish this Suspense boundary: The render was aborted by the server without a reason.',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'The render was aborted by the server without a reason.',
expectedDigest,
componentStack(['Suspense', 'main', 'div', 'App']),
],
Expand Down Expand Up @@ -2145,7 +2151,8 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
theError.message,
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
componentStack([
'AsyncText',
Expand Down Expand Up @@ -3431,12 +3438,14 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
'The server did not finish this Suspense boundary: foobar',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'foobar',
'a digest',
componentStack(['Suspense', 'p', 'div', 'App']),
],
[
'The server did not finish this Suspense boundary: foobar',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'foobar',
'a digest',
componentStack(['Suspense', 'span', 'div', 'App']),
],
Expand Down Expand Up @@ -3512,12 +3521,14 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
'The server did not finish this Suspense boundary: uh oh',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'uh oh',
'a digest',
componentStack(['Suspense', 'p', 'div', 'App']),
],
[
'The server did not finish this Suspense boundary: uh oh',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'uh oh',
'a digest',
componentStack(['Suspense', 'span', 'div', 'App']),
],
Expand Down Expand Up @@ -3991,7 +4002,8 @@ describe('ReactDOMFizzServer', () => {
errors,
[
[
theError.message,
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
componentStack(['Erroring', 'Suspense', 'div', 'App']),
],
Expand Down Expand Up @@ -6772,7 +6784,14 @@ describe('ReactDOMFizzServer', () => {

expect(recoverableErrors).toEqual(
__DEV__
? ['server error', 'replay error', 'server error']
? [
'Switched to client rendering because the server rendering errored:\n\n' +
'server error',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'replay error',
'Switched to client rendering because the server rendering errored:\n\n' +
'server error',
]
: [
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
Expand Down Expand Up @@ -6931,8 +6950,10 @@ describe('ReactDOMFizzServer', () => {
expect(recoverableErrors).toEqual(
__DEV__
? [
'The server did not finish this Suspense boundary: aborted',
'The server did not finish this Suspense boundary: aborted',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'aborted',
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'aborted',
]
: [
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
Expand Down Expand Up @@ -7103,8 +7124,10 @@ describe('ReactDOMFizzServer', () => {
// It surfaced in two different suspense boundaries.
__DEV__
? [
'The server did not finish this Suspense boundary: replay error',
'The server did not finish this Suspense boundary: replay error',
'Switched to client rendering because the server rendering errored:\n\n' +
'replay error',
'Switched to client rendering because the server rendering errored:\n\n' +
'replay error',
]
: [
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
Expand Down Expand Up @@ -7230,7 +7253,10 @@ describe('ReactDOMFizzServer', () => {

expect(recoverableErrors).toEqual(
__DEV__
? ['server error']
? [
'Switched to client rendering because the server rendering errored:\n\n' +
'server error',
]
: [
'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
],
Expand Down
20 changes: 12 additions & 8 deletions packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1292,10 +1292,12 @@ describe('ReactDOMServerHydration', () => {
}

expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Caught [The server did not finish this Suspense boundary: The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]",
]
`);
[
"Caught [Switched to client rendering because the server rendering aborted due to:
The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]",
]
`);
});

// @gate __DEV__
Expand All @@ -1318,10 +1320,12 @@ describe('ReactDOMServerHydration', () => {
}

expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Caught [The server did not finish this Suspense boundary: The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]",
]
`);
[
"Caught [Switched to client rendering because the server rendering aborted due to:
The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]",
]
`);
});
});

Expand Down
8 changes: 4 additions & 4 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -2659,18 +2659,18 @@ function updateDehydratedSuspenseComponent(
// TODO: Figure out a better signal than encoding a magic digest value.
if (!enablePostpone || digest !== 'POSTPONE') {
let error;
if (message) {
if (__DEV__ && message) {
// eslint-disable-next-line react-internal/prod-error-codes
error = new Error(message);
} else {
error = new Error(
'The server could not finish this Suspense boundary, likely ' +
'due to an error during server rendering. Switched to ' +
'client rendering.',
'due to an error during server rendering. ' +
'Switched to client rendering.',
);
}
// Replace the stack with the server stack
error.stack = stack || '';
error.stack = (__DEV__ && stack) || '';
(error: any).digest = digest;
capturedValue = createCapturedValueFromError(
error,
Expand Down
58 changes: 29 additions & 29 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,7 @@ function encodeErrorForBoundary(
digest: ?string,
error: mixed,
thrownInfo: ThrownInfo,
wasAborted: boolean,
) {
boundary.errorDigest = digest;
if (__DEV__) {
Expand All @@ -838,8 +839,10 @@ function encodeErrorForBoundary(
message = String(error);
stack = null;
}

boundary.errorMessage = message;
const prefix = wasAborted
? 'Switched to client rendering because the server rendering aborted due to:\n\n'
: 'Switched to client rendering because the server rendering errored:\n\n';
boundary.errorMessage = prefix + message;
boundary.errorStack = stack;
boundary.errorComponentStack = thrownInfo.componentStack;
}
Expand Down Expand Up @@ -1021,7 +1024,7 @@ function renderSuspenseBoundary(
} else {
errorDigest = logRecoverableError(request, error, thrownInfo);
}
encodeErrorForBoundary(newBoundary, errorDigest, error, thrownInfo);
encodeErrorForBoundary(newBoundary, errorDigest, error, thrownInfo, false);

untrackBoundary(request, newBoundary);

Expand Down Expand Up @@ -1165,7 +1168,13 @@ function replaySuspenseBoundary(
} else {
errorDigest = logRecoverableError(request, error, thrownInfo);
}
encodeErrorForBoundary(resumedBoundary, errorDigest, error, thrownInfo);
encodeErrorForBoundary(
resumedBoundary,
errorDigest,
error,
thrownInfo,
false,
);

task.replay.pendingTasks--;

Expand Down Expand Up @@ -2962,6 +2971,7 @@ function erroredReplay(
error,
errorDigest,
errorInfo,
false,
);
}

Expand Down Expand Up @@ -2992,7 +3002,7 @@ function erroredTask(
boundary.pendingTasks--;
if (boundary.status !== CLIENT_RENDERED) {
boundary.status = CLIENT_RENDERED;
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo);
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, false);
untrackBoundary(request, boundary);

// Regardless of what happens next, this boundary won't be displayed,
Expand Down Expand Up @@ -3032,24 +3042,21 @@ function abortRemainingSuspenseBoundary(
error: mixed,
errorDigest: ?string,
errorInfo: ThrownInfo,
wasAborted: boolean,
): void {
const resumedBoundary = createSuspenseBoundary(request, new Set());
resumedBoundary.parentFlushed = true;
// We restore the same id of this boundary as was used during prerender.
resumedBoundary.rootSegmentID = rootSegmentID;

resumedBoundary.status = CLIENT_RENDERED;
let errorMessage = error;
if (__DEV__) {
const errorPrefix = 'The server did not finish this Suspense boundary: ';
if (error && typeof error.message === 'string') {
errorMessage = errorPrefix + error.message;
} else {
// eslint-disable-next-line react-internal/safe-string-coercion
errorMessage = errorPrefix + String(error);
}
}
encodeErrorForBoundary(resumedBoundary, errorDigest, errorMessage, errorInfo);
encodeErrorForBoundary(
resumedBoundary,
errorDigest,
error,
errorInfo,
wasAborted,
);

if (resumedBoundary.parentFlushed) {
request.clientRenderedBoundaries.push(resumedBoundary);
Expand All @@ -3064,6 +3071,7 @@ function abortRemainingReplayNodes(
error: mixed,
errorDigest: ?string,
errorInfo: ThrownInfo,
aborted: boolean,
): void {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
Expand All @@ -3076,6 +3084,7 @@ function abortRemainingReplayNodes(
error,
errorDigest,
errorInfo,
aborted,
);
} else {
const boundaryNode: ReplaySuspenseBoundary = node;
Expand All @@ -3086,6 +3095,7 @@ function abortRemainingReplayNodes(
error,
errorDigest,
errorInfo,
aborted,
);
}
}
Expand All @@ -3102,7 +3112,7 @@ function abortRemainingReplayNodes(
);
} else if (boundary.status !== CLIENT_RENDERED) {
boundary.status = CLIENT_RENDERED;
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo);
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, aborted);
if (boundary.parentFlushed) {
request.clientRenderedBoundaries.push(boundary);
}
Expand Down Expand Up @@ -3178,6 +3188,7 @@ function abortTask(task: Task, request: Request, error: mixed): void {
error,
errorDigest,
errorInfo,
true,
);
}
request.pendingRootTasks--;
Expand Down Expand Up @@ -3207,18 +3218,7 @@ function abortTask(task: Task, request: Request, error: mixed): void {
} else {
errorDigest = logRecoverableError(request, error, errorInfo);
}
let errorMessage = error;
if (__DEV__) {
const errorPrefix =
'The server did not finish this Suspense boundary: ';
if (error && typeof error.message === 'string') {
errorMessage = errorPrefix + error.message;
} else {
// eslint-disable-next-line react-internal/safe-string-coercion
errorMessage = errorPrefix + String(error);
}
}
encodeErrorForBoundary(boundary, errorDigest, errorMessage, errorInfo);
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, true);

untrackBoundary(request, boundary);

Expand Down

0 comments on commit 4255c4e

Please sign in to comment.