Skip to content

Commit bc4b9da

Browse files
handle error happen during rsc payload generation
1 parent 11e4a54 commit bc4b9da

File tree

3 files changed

+39
-40
lines changed

3 files changed

+39
-40
lines changed

node_package/src/ReactOnRailsRSC.ts

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BundleManifest } from 'react-on-rails-rsc';
22
import { buildServerRenderer } from 'react-on-rails-rsc/server.node';
3-
import { PassThrough, Readable } from 'stream';
3+
import { Readable } from 'stream';
44

55
import {
66
RSCRenderParams,
@@ -9,9 +9,8 @@ import {
99
StreamableComponentResult,
1010
} from './types/index.ts';
1111
import ReactOnRails from './ReactOnRails.full.ts';
12-
import buildConsoleReplay from './buildConsoleReplay.ts';
1312
import handleError from './handleError.ts';
14-
import { convertToError, createResultObject } from './serverRenderUtils.ts';
13+
import { convertToError } from './serverRenderUtils.ts';
1514
import { notifySSREnd, addPostSSRHook } from './postSSRHooks.ts';
1615

1716
import {
@@ -20,13 +19,6 @@ import {
2019
} from './streamServerRenderedReactComponent.ts';
2120
import loadJsonFile from './loadJsonFile.ts';
2221

23-
const stringToStream = (str: string) => {
24-
const stream = new PassThrough();
25-
stream.push(str);
26-
stream.push(null);
27-
return stream;
28-
};
29-
3022
let serverRenderer: ReturnType<typeof buildServerRenderer> | undefined;
3123

3224
const streamRenderRSCComponent = (
@@ -44,37 +36,41 @@ const streamRenderRSCComponent = (
4436
isShellReady: true,
4537
};
4638

47-
const { pipeToTransform, readableStream, emitError } =
39+
const { pipeToTransform, readableStream, emitError, writeChunk, endStream } =
4840
transformRenderStreamChunksToResultObject(renderState);
49-
Promise.resolve(reactRenderingResult)
50-
.then(async (reactElement) => {
51-
if (!serverRenderer) {
52-
const reactClientManifest = await loadJsonFile<BundleManifest>(reactClientManifestFileName);
53-
serverRenderer = buildServerRenderer(reactClientManifest);
54-
}
5541

56-
const { renderToPipeableStream } = serverRenderer;
57-
const rscStream = renderToPipeableStream(reactElement, {
58-
onError: (err) => {
59-
const error = convertToError(err);
60-
console.error('Error in RSC stream', error);
61-
if (throwJsErrors) {
62-
emitError(error);
63-
}
64-
renderState.hasErrors = true;
65-
renderState.error = error;
66-
},
67-
});
68-
pipeToTransform(rscStream);
69-
})
70-
.catch((e: unknown) => {
71-
const error = convertToError(e);
72-
renderState.hasErrors = true;
73-
renderState.error = error;
74-
const htmlResult = handleError({ e: error, name: options.name, serverSide: true });
75-
const jsonResult = JSON.stringify(createResultObject(htmlResult, buildConsoleReplay(), renderState));
76-
return stringToStream(jsonResult);
42+
const reportError = (error: Error) => {
43+
console.error('Error in RSC stream', error);
44+
if (throwJsErrors) {
45+
emitError(error);
46+
}
47+
renderState.hasErrors = true;
48+
renderState.error = error;
49+
};
50+
51+
const initializeAndRender = async () => {
52+
if (!serverRenderer) {
53+
const reactClientManifest = await loadJsonFile<BundleManifest>(reactClientManifestFileName);
54+
serverRenderer = buildServerRenderer(reactClientManifest);
55+
}
56+
57+
const { renderToPipeableStream } = serverRenderer;
58+
const rscStream = renderToPipeableStream(await reactRenderingResult, {
59+
onError: (err) => {
60+
const error = convertToError(err);
61+
reportError(error);
62+
},
7763
});
64+
pipeToTransform(rscStream);
65+
};
66+
67+
initializeAndRender().catch((e: unknown) => {
68+
const error = convertToError(e);
69+
reportError(error);
70+
const errorHtml = handleError({ e: error, name: options.name, serverSide: true });
71+
writeChunk(errorHtml);
72+
endStream();
73+
});
7874

7975
readableStream.on('end', () => {
8076
notifySSREnd(railsContext);

node_package/src/handleError.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ Message: ${e.message}
5959
6060
${e.stack}`;
6161

62-
const reactElement = React.createElement('pre', null, msg);
62+
// In RSC (React Server Components) bundles, renderToString is not available.
63+
// Therefore, we return the raw error message as a string instead of converting it to HTML.
6364
if (typeof renderToString === 'function') {
65+
const reactElement = React.createElement('pre', null, msg);
6466
return renderToString(reactElement);
6567
}
6668
return msg;

node_package/src/injectRSCPayload.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PassThrough, Transform } from 'stream';
22
import { finished } from 'stream/promises';
3+
import { createRSCPayloadKey } from './utils.ts';
34
import { RailsContextWithServerComponentCapabilities, PipeableOrReadableStream } from './types/index.ts';
45

56
// In JavaScript, when an escape sequence with a backslash (\) is followed by a character
@@ -66,7 +67,7 @@ export default function injectRSCPayload(
6667

6768
ReactOnRails.onRSCPayloadGenerated?.(railsContext, (streamInfo) => {
6869
const { stream, props, componentName } = streamInfo;
69-
const cacheKey = `${componentName}-${JSON.stringify(props)}-${railsContext.componentSpecificMetadata?.renderRequestId}`;
70+
const cacheKey = createRSCPayloadKey(componentName, props, railsContext);
7071

7172
// When a component requests an RSC payload, we initialize a global array to store it.
7273
// This array is injected into the HTML before the component's HTML markup.

0 commit comments

Comments
 (0)