@@ -9,6 +9,12 @@ import { RSCRouterGlobalErrorBoundary } from "./errorBoundaries";
99import { shouldHydrateRouteLoader } from "../dom/ssr/routes" ;
1010import type { RSCPayload } from "./server.rsc" ;
1111import { createRSCRouteModules } from "./route-modules" ;
12+ import { isRouteErrorResponse } from "../router/utils" ;
13+
14+ type DecodedPayload = Promise < RSCPayload > & {
15+ _deepestRenderedBoundaryId ?: string | null ;
16+ formState : Promise < any > ;
17+ } ;
1218
1319// Safe version of React.use() that will not cause compilation errors against
1420// React 18 and will result in a runtime error if used (you can't use RSC against
@@ -46,13 +52,13 @@ export type SSRCreateFromReadableStreamFunction = (
4652 * fetchServer,
4753 * createFromReadableStream,
4854 * async renderHTML(getPayload) {
49- * const payload = await getPayload();
55+ * const payload = getPayload();
5056 *
5157 * return await renderHTMLToReadableStream(
5258 * <RSCStaticRouter getPayload={getPayload} />,
5359 * {
5460 * bootstrapScriptContent,
55- * formState: await getFormState( payload) ,
61+ * formState: await payload.formState ,
5662 * }
5763 * );
5864 * },
@@ -88,7 +94,7 @@ export async function routeRSCServerRequest({
8894 fetchServer : ( request : Request ) => Promise < Response > ;
8995 createFromReadableStream : SSRCreateFromReadableStreamFunction ;
9096 renderHTML : (
91- getPayload : ( ) => Promise < RSCPayload > ,
97+ getPayload : ( ) => DecodedPayload ,
9298 ) => ReadableStream < Uint8Array > | Promise < ReadableStream < Uint8Array > > ;
9399 hydrate ?: boolean ;
94100} ) : Promise < Response > {
@@ -150,8 +156,29 @@ export async function routeRSCServerRequest({
150156 } ) ;
151157 } ;
152158
153- const getPayload = async ( ) => {
154- return createFromReadableStream ( createStream ( ) ) as Promise < RSCPayload > ;
159+ let deepestRenderedBoundaryId : string | null = null ;
160+ const getPayload = ( ) : DecodedPayload => {
161+ const payloadPromise = Promise . resolve (
162+ createFromReadableStream ( createStream ( ) ) ,
163+ ) as Promise < RSCPayload > ;
164+
165+ return Object . defineProperties ( payloadPromise , {
166+ _deepestRenderedBoundaryId : {
167+ get ( ) {
168+ return deepestRenderedBoundaryId ;
169+ } ,
170+ set ( boundaryId : string | null ) {
171+ deepestRenderedBoundaryId = boundaryId ;
172+ } ,
173+ } ,
174+ formState : {
175+ get ( ) {
176+ return payloadPromise . then ( ( payload ) =>
177+ payload . type === "render" ? payload . formState : undefined ,
178+ ) ;
179+ } ,
180+ } ,
181+ } ) as DecodedPayload ;
155182 } ;
156183
157184 try {
@@ -204,11 +231,69 @@ export async function routeRSCServerRequest({
204231 if ( reason instanceof Response ) {
205232 return reason ;
206233 }
234+
235+ try {
236+ const status = isRouteErrorResponse ( reason ) ? reason . status : 500 ;
237+
238+ const html = await renderHTML ( ( ) => {
239+ const decoded = Promise . resolve (
240+ createFromReadableStream ( createStream ( ) ) ,
241+ ) as Promise < RSCPayload > ;
242+
243+ const payloadPromise = decoded . then ( ( payload ) =>
244+ Object . assign ( payload , {
245+ status,
246+ errors : deepestRenderedBoundaryId
247+ ? {
248+ [ deepestRenderedBoundaryId ] : reason ,
249+ }
250+ : { } ,
251+ } ) ,
252+ ) ;
253+
254+ return Object . defineProperties ( payloadPromise , {
255+ _deepestRenderedBoundaryId : {
256+ get ( ) {
257+ return deepestRenderedBoundaryId ;
258+ } ,
259+ set ( boundaryId : string | null ) {
260+ deepestRenderedBoundaryId = boundaryId ;
261+ } ,
262+ } ,
263+ formState : {
264+ get ( ) {
265+ return payloadPromise . then ( ( payload ) =>
266+ payload . type === "render" ? payload . formState : undefined ,
267+ ) ;
268+ } ,
269+ } ,
270+ } ) as unknown as DecodedPayload ;
271+ } ) ;
272+
273+ const headers = new Headers ( serverResponse . headers ) ;
274+ headers . set ( "Content-Type" , "text/html" ) ;
275+
276+ if ( ! hydrate ) {
277+ return new Response ( html , {
278+ status : status ,
279+ headers,
280+ } ) ;
281+ }
282+
283+ if ( ! serverResponseB ?. body ) {
284+ throw new Error ( "Failed to clone server response" ) ;
285+ }
286+
287+ const body = html . pipeThrough ( injectRSCPayload ( serverResponseB . body ) ) ;
288+ return new Response ( body , {
289+ status,
290+ headers,
291+ } ) ;
292+ } catch {
293+ // Throw the original error below
294+ }
295+
207296 throw reason ;
208- // TODO: Track deepest rendered boundary and re-try
209- // Figure out how / if we need to transport the error,
210- // or if we can just re-try on the client to reach
211- // the correct boundary.
212297 }
213298}
214299
@@ -223,7 +308,7 @@ export interface RSCStaticRouterProps {
223308 * A function that starts decoding of the {@link unstable_RSCPayload}. Usually passed
224309 * through from {@link unstable_routeRSCServerRequest}'s `renderHTML`.
225310 */
226- getPayload : ( ) => Promise < RSCPayload > ;
311+ getPayload : ( ) => DecodedPayload ;
227312}
228313
229314/**
@@ -243,13 +328,13 @@ export interface RSCStaticRouterProps {
243328 * fetchServer,
244329 * createFromReadableStream,
245330 * async renderHTML(getPayload) {
246- * const payload = await getPayload();
331+ * const payload = getPayload();
247332 *
248333 * return await renderHTMLToReadableStream(
249334 * <RSCStaticRouter getPayload={getPayload} />,
250335 * {
251336 * bootstrapScriptContent,
252- * formState: await getFormState( payload) ,
337+ * formState: await payload.formState ,
253338 * }
254339 * );
255340 * },
@@ -264,8 +349,9 @@ export interface RSCStaticRouterProps {
264349 * @returns A React component that renders the {@link unstable_RSCPayload} as HTML.
265350 */
266351export function RSCStaticRouter ( { getPayload } : RSCStaticRouterProps ) {
352+ const decoded = getPayload ( ) ;
267353 // Can be replaced with React.use when v18 compatibility is no longer required.
268- const payload = useSafe ( getPayload ( ) ) ;
354+ const payload = useSafe ( decoded ) ;
269355
270356 if ( payload . type === "redirect" ) {
271357 throw new Response ( null , {
@@ -298,6 +384,12 @@ export function RSCStaticRouter({ getPayload }: RSCStaticRouterProps) {
298384 }
299385
300386 const context = {
387+ get _deepestRenderedBoundaryId ( ) {
388+ return decoded . _deepestRenderedBoundaryId ?? null ;
389+ } ,
390+ set _deepestRenderedBoundaryId ( boundaryId : string | null ) {
391+ decoded . _deepestRenderedBoundaryId = boundaryId ;
392+ } ,
301393 actionData : payload . actionData ,
302394 actionHeaders : { } ,
303395 basename : payload . basename ,
0 commit comments