diff --git a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.browser.tsx b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.browser.tsx index fcc8b3ccb..56e028872 100644 --- a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.browser.tsx +++ b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.browser.tsx @@ -4,35 +4,42 @@ import { encodeReply, setServerCallback, } from '@vitejs/plugin-rsc/browser' -import * as React from 'react' +import { startTransition, StrictMode } from 'react' import { hydrateRoot } from 'react-dom/client' import { - unstable_RSCHydratedRouter as RSCHydratedRouter, - type unstable_RSCPayload as RSCPayload, unstable_createCallServer as createCallServer, unstable_getRSCStream as getRSCStream, + unstable_RSCHydratedRouter as RSCHydratedRouter, + type unstable_RSCPayload as RSCServerPayload, } from 'react-router' +// Create and set the callServer function to support post-hydration server actions. setServerCallback( createCallServer({ createFromReadableStream, - encodeReply, createTemporaryReferenceSet, + encodeReply, }), ) -createFromReadableStream(getRSCStream()).then( - (payload: RSCPayload) => { - React.startTransition(() => { - hydrateRoot( - document, - - - , - ) - }) - }, -) +// Get and decode the initial server payload +createFromReadableStream(getRSCStream()).then((payload) => { + startTransition(async () => { + const formState = + payload.type === 'render' ? await payload.formState : undefined + + hydrateRoot( + document, + + + , + { + // @ts-expect-error - no types for this yet + formState, + }, + ) + }) +}) diff --git a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.tsx b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.tsx index 8583030ce..0003d1b94 100644 --- a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.tsx +++ b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.tsx @@ -1,6 +1,7 @@ import { createTemporaryReferenceSet, decodeAction, + decodeFormState, decodeReply, loadServerAction, renderToReadableStream, @@ -9,16 +10,21 @@ import { unstable_matchRSCServerRequest as matchRSCServerRequest } from 'react-r import routes from 'virtual:react-router-routes' -export async function fetchServer(request: Request): Promise { - return await matchRSCServerRequest({ +export function fetchServer(request: Request) { + return matchRSCServerRequest({ + // Provide the React Server touchpoints. createTemporaryReferenceSet, - decodeReply, decodeAction, + decodeFormState, + decodeReply, loadServerAction, + // The incoming request. request, + // The app routes. routes, - generateResponse(match, options) { - return new Response(renderToReadableStream(match.payload, options), { + // Encode the match with the React Server implementation. + generateResponse(match) { + return new Response(renderToReadableStream(match.payload), { status: match.statusCode, headers: match.headers, }) @@ -26,6 +32,15 @@ export async function fetchServer(request: Request): Promise { }) } +export default async function handler(request: Request) { + // Import the generateHTML function from the client environment + const ssr = await import.meta.viteRsc.loadModule< + typeof import('./entry.ssr') + >('ssr', 'index') + + return ssr.generateHTML(request, fetchServer) +} + if (import.meta.hot) { import.meta.hot.accept() } diff --git a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.tsx b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.tsx index 4e226dc1e..7f353d070 100644 --- a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.tsx +++ b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.tsx @@ -1,27 +1,45 @@ import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr' -import * as ReactDomServer from 'react-dom/server.edge' +import { renderToReadableStream as renderHTMLToReadableStream } from 'react-dom/server.edge' import { - unstable_RSCStaticRouter as RSCStaticRouter, unstable_routeRSCServerRequest as routeRSCServerRequest, + unstable_RSCStaticRouter as RSCStaticRouter, } from 'react-router' -export default async function handler( +export async function generateHTML( request: Request, fetchServer: (request: Request) => Promise, ): Promise { - const bootstrapScriptContent = - await import.meta.viteRsc.loadBootstrapScriptContent('index') - return routeRSCServerRequest({ + return await routeRSCServerRequest({ + // The incoming request. request, + // How to call the React Server. fetchServer, - createFromReadableStream: (body) => createFromReadableStream(body), - renderHTML(getPayload) { - return ReactDomServer.renderToReadableStream( + // Provide the React Server touchpoints. + createFromReadableStream, + // Render the router to HTML. + async renderHTML(getPayload) { + const payload = await getPayload() + const formState = + payload.type === 'render' ? await payload.formState : undefined + + const bootstrapScriptContent = + await import.meta.viteRsc.loadBootstrapScriptContent('index') + + return await renderHTMLToReadableStream( , { bootstrapScriptContent, + // @ts-expect-error - no types for this yet + formState, }, ) }, }) } + +export default async function handler( + request: Request, + fetchServer: (request: Request) => Promise, +): Promise { + return generateHTML(request, fetchServer) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed03505b5..92a4d90cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5860,8 +5860,8 @@ snapshots: '@babel/generator@7.27.5': dependencies: - '@babel/parser': 7.27.7 - '@babel/types': 7.27.7 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.1 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 @@ -5869,7 +5869,7 @@ snapshots: '@babel/generator@7.28.0': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.0.2 @@ -5894,7 +5894,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -5903,8 +5903,8 @@ snapshots: '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/traverse': 7.27.7 - '@babel/types': 7.28.0 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.1 transitivePeerDependencies: - supports-color @@ -5933,7 +5933,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@babel/helper-plugin-utils@7.27.1': {} @@ -5942,14 +5942,14 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.0 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.27.7 - '@babel/types': 7.28.0 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.1 transitivePeerDependencies: - supports-color @@ -5962,15 +5962,15 @@ snapshots: '@babel/helpers@7.27.6': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@babel/parser@7.27.7': dependencies: - '@babel/types': 7.27.7 + '@babel/types': 7.28.1 '@babel/parser@7.28.0': dependencies: - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@babel/plugin-proposal-pipeline-operator@7.27.1(@babel/core@7.28.0)': dependencies: @@ -6064,7 +6064,7 @@ snapshots: dependencies: '@babel/code-frame': 7.27.1 '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 '@babel/traverse@7.27.7': dependencies: @@ -6085,7 +6085,7 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -6901,13 +6901,13 @@ snapshots: '@react-router/dev@0.0.0-experimental-23decd7bc(@types/node@22.16.5)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tsx@4.20.3)(typescript@5.8.3)(vite@7.0.5(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1))(wrangler@4.25.1)(yaml@2.7.1)': dependencies: '@babel/core': 7.28.0 - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.7 + '@babel/generator': 7.28.0 + '@babel/parser': 7.28.0 '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.0) '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.1 '@npmcli/package-json': 4.0.1 '@react-router/node': 0.0.0-experimental-23decd7bc(react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3) arg: 5.0.2 @@ -7765,9 +7765,9 @@ snapshots: babel-dead-code-elimination@1.0.10: dependencies: '@babel/core': 7.28.0 - '@babel/parser': 7.27.7 - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 + '@babel/parser': 7.28.0 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.1 transitivePeerDependencies: - supports-color