Skip to content

Commit df6a828

Browse files
committed
add a serializable contract between the RSC and SSR environment that matches the Request Response keys instead of relying on them directly.
1 parent 303421a commit df6a828

File tree

3 files changed

+50
-18
lines changed

3 files changed

+50
-18
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export type RequestContract =
2+
| {
3+
body?: BodyInit | null;
4+
headers?: HeadersInit;
5+
method?: string;
6+
url: string;
7+
}
8+
| Request;
9+
10+
export type ResponseContract = {
11+
body: ReadableStream<Uint8Array> | null;
12+
headers: [string, string][];
13+
status: number;
14+
statusText: string;
15+
};

packages/react-router/lib/rsc/server.rsc.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ import type {
6060
ErrorBoundaryProps,
6161
HydrateFallbackProps,
6262
} from "../components";
63+
import type { RequestContract, ResponseContract } from "./env-contract";
64+
6365
const Outlet: typeof OutletType = UNTYPED_Outlet;
6466
const WithComponentProps: typeof WithComponentPropsType =
6567
UNSAFE_WithComponentProps;
@@ -374,7 +376,7 @@ export async function matchRSCServerRequest({
374376
requestContext?: RouterContextProvider;
375377
loadServerAction?: LoadServerActionFunction;
376378
onError?: (error: unknown) => void;
377-
request: Request;
379+
request: RequestContract;
378380
routes: RSCRouteConfigEntry[];
379381
generateResponse: (
380382
match: RSCMatch,
@@ -384,7 +386,7 @@ export async function matchRSCServerRequest({
384386
temporaryReferences: unknown;
385387
},
386388
) => Response;
387-
}): Promise<Response> {
389+
}): Promise<ResponseContract> {
388390
let requestUrl = new URL(request.url);
389391

390392
const temporaryReferences = createTemporaryReferenceSet();
@@ -397,20 +399,32 @@ export async function matchRSCServerRequest({
397399
generateResponse,
398400
temporaryReferences,
399401
);
400-
return response;
402+
return {
403+
body: response.body,
404+
headers: Array.from(response.headers),
405+
status: response.status,
406+
statusText: response.statusText,
407+
};
401408
}
402409

403410
let isDataRequest = isReactServerRequest(requestUrl);
404411

405412
const url = new URL(request.url);
406-
let routerRequest = request;
413+
let routerRequest = new Request(request.url, {
414+
body: request.body,
415+
duplex: request.body ? "half" : undefined,
416+
headers: request.headers,
417+
method: request.method,
418+
signal: "signal" in request ? request.signal : null,
419+
} as RequestInit & { duplex?: "half" });
420+
407421
if (isDataRequest) {
408422
url.pathname = url.pathname.replace(/(_root)?\.rsc$/, "");
409423
routerRequest = new Request(url.toString(), {
410424
method: request.method,
411425
headers: request.headers,
412426
body: request.body,
413-
signal: request.signal,
427+
signal: "signal" in request ? request.signal : undefined,
414428
duplex: request.body ? "half" : undefined,
415429
} as RequestInit);
416430
}
@@ -431,14 +445,20 @@ export async function matchRSCServerRequest({
431445
!leafMatch.route.Component &&
432446
!leafMatch.route.ErrorBoundary
433447
) {
434-
return generateResourceResponse(
448+
const response = await generateResourceResponse(
435449
routerRequest,
436450
routes,
437451
basename,
438452
leafMatch.route.id,
439453
requestContext,
440454
onError,
441455
);
456+
return {
457+
body: response.body,
458+
headers: Array.from(response.headers),
459+
status: response.status,
460+
statusText: response.statusText,
461+
};
442462
}
443463

444464
let response = await generateRenderResponse(
@@ -458,13 +478,18 @@ export async function matchRSCServerRequest({
458478
// The front end uses this to know whether a 4xx/5xx status came from app code
459479
// or never reached the origin server
460480
response.headers.set("X-Remix-Response", "yes");
461-
return response;
481+
return {
482+
body: response.body,
483+
headers: Array.from(response.headers),
484+
status: response.status,
485+
statusText: response.statusText,
486+
};
462487
}
463488

464489
async function generateManifestResponse(
465490
routes: RSCRouteConfigEntry[],
466491
basename: string | undefined,
467-
request: Request,
492+
request: RequestContract,
468493
generateResponse: (
469494
match: RSCMatch,
470495
{ temporaryReferences }: { temporaryReferences: unknown },

packages/react-router/lib/rsc/server.ssr.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { shouldHydrateRouteLoader } from "../dom/ssr/routes";
1010
import type { RSCPayload } from "./server.rsc";
1111
import { createRSCRouteModules } from "./route-modules";
1212
import { isRouteErrorResponse } from "../router/utils";
13+
import type { ResponseContract } from "./env-contract";
1314

1415
type DecodedPayload = Promise<RSCPayload> & {
1516
_deepestRenderedBoundaryId?: string | null;
@@ -72,10 +73,6 @@ export type SSRCreateFromReadableStreamFunction = (
7273
* @param opts.createFromReadableStream Your `react-server-dom-xyz/client`'s
7374
* `createFromReadableStream` function, used to decode payloads from the server.
7475
* @param opts.serverResponse A Response or partial response generated by the [RSC](https://react.dev/reference/rsc/server-components) handler containing a serialized {@link unstable_RSCPayload}.
75-
* @param opts.serverResponse.body A BodyInit.
76-
* @param opts.serverResponse.headers A HeadersInit.
77-
* @param opts.serverResponse.status A status code.
78-
* @param opts.serverResponse.statusText A status message.
7976
* @param opts.hydrate Whether to hydrate the server response with the RSC payload.
8077
* Defaults to `true`.
8178
* @param opts.renderHTML A function that renders the {@link unstable_RSCPayload} to
@@ -93,12 +90,7 @@ export async function routeRSCServerRequest({
9390
hydrate = true,
9491
}: {
9592
request: Request;
96-
serverResponse: {
97-
body?: BodyInit | null;
98-
headers?: HeadersInit;
99-
status?: number;
100-
statusText?: string;
101-
};
93+
serverResponse: ResponseContract;
10294
createFromReadableStream: SSRCreateFromReadableStreamFunction;
10395
renderHTML: (
10496
getPayload: () => DecodedPayload,

0 commit comments

Comments
 (0)