diff --git a/packages/plugin-rsc/examples/react-router/cf/entry.rsc.tsx b/packages/plugin-rsc/examples/react-router/cf/entry.rsc.tsx index b194dfeb0..103b41ed1 100644 --- a/packages/plugin-rsc/examples/react-router/cf/entry.rsc.tsx +++ b/packages/plugin-rsc/examples/react-router/cf/entry.rsc.tsx @@ -7,7 +7,3 @@ export default { return fetchServer(request) }, } - -if (import.meta.hot) { - import.meta.hot.accept() -} diff --git a/packages/plugin-rsc/examples/react-router/cf/entry.ssr.tsx b/packages/plugin-rsc/examples/react-router/cf/entry.ssr.tsx index 9a570e706..689e7ac25 100644 --- a/packages/plugin-rsc/examples/react-router/cf/entry.ssr.tsx +++ b/packages/plugin-rsc/examples/react-router/cf/entry.ssr.tsx @@ -1,13 +1,9 @@ -import handler from '../react-router-vite/entry.ssr' +import { generateHTML } from '../react-router-vite/entry.ssr' console.log('[debug:cf-ssr-entry]') -// TODO: -// shouldn't "entry.rsc.tsx" be the main server entry -// and optionally call "entry.ssr.tsx" only for rendering html? - export default { fetch(request: Request, env: any) { - return handler(request, (request) => env.RSC.fetch(request)) + return generateHTML(request, (request) => env.RSC.fetch(request)) }, } diff --git a/packages/plugin-rsc/examples/react-router/package.json b/packages/plugin-rsc/examples/react-router/package.json index d49b4cab8..818629ba6 100644 --- a/packages/plugin-rsc/examples/react-router/package.json +++ b/packages/plugin-rsc/examples/react-router/package.json @@ -16,11 +16,11 @@ "@vitejs/plugin-rsc": "latest", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-router": "0.0.0-experimental-23decd7bc" + "react-router": "7.7.0" }, "devDependencies": { "@cloudflare/vite-plugin": "^1.10.0", - "@react-router/dev": "0.0.0-experimental-23decd7bc", + "@react-router/dev": "7.7.0", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.11", "@types/react": "^19.1.8", 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.single.tsx b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.single.tsx new file mode 100644 index 000000000..da3895388 --- /dev/null +++ b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.rsc.single.tsx @@ -0,0 +1,10 @@ +import { fetchServer } from './entry.rsc' + +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) +} 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..3e2ce9c92 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,14 +10,19 @@ 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, + // Encode the match with the React Server implementation. generateResponse(match, options) { return new Response(renderToReadableStream(match.payload, options), { status: match.statusCode, diff --git a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.single.tsx b/packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.single.tsx deleted file mode 100644 index 5aff3e4e3..000000000 --- a/packages/plugin-rsc/examples/react-router/react-router-vite/entry.ssr.single.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import baseHandler from './entry.ssr' - -export default async function handler(request: Request) { - const rsc = await import.meta.viteRsc.loadModule< - typeof import('./entry.rsc') - >('rsc', 'index') - return baseHandler(request, rsc.fetchServer) -} 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..c80cde622 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,25 +1,36 @@ 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, }, ) }, diff --git a/packages/plugin-rsc/examples/react-router/vite.config.ts b/packages/plugin-rsc/examples/react-router/vite.config.ts index d02451810..2f1091c65 100644 --- a/packages/plugin-rsc/examples/react-router/vite.config.ts +++ b/packages/plugin-rsc/examples/react-router/vite.config.ts @@ -17,12 +17,8 @@ export default defineConfig({ rsc({ entries: { client: './react-router-vite/entry.browser.tsx', - ssr: './react-router-vite/entry.ssr.single.tsx', - rsc: './react-router-vite/entry.rsc.tsx', - }, - serverHandler: { - environmentName: 'ssr', - entryName: 'index', + ssr: './react-router-vite/entry.ssr.tsx', + rsc: './react-router-vite/entry.rsc.single.tsx', }, }), inspect(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed03505b5..2bd9797c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -610,15 +610,15 @@ importers: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) react-router: - specifier: 0.0.0-experimental-23decd7bc - version: 0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 7.7.0 + version: 7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) devDependencies: '@cloudflare/vite-plugin': specifier: ^1.10.0 version: 1.10.0(rollup@4.44.1)(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))(workerd@1.20250712.0)(wrangler@4.25.1) '@react-router/dev': - specifier: 0.0.0-experimental-23decd7bc - version: 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) + specifier: 7.7.0 + version: 7.7.0(@types/node@22.16.5)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.7.0(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) '@tailwindcss/typography': specifier: ^0.5.16 version: 0.5.16(tailwindcss@4.1.11) @@ -1119,12 +1119,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-decorators@7.27.1': - resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} @@ -2247,13 +2241,13 @@ packages: resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} engines: {node: '>=20.0.0'} - '@react-router/dev@0.0.0-experimental-23decd7bc': - resolution: {integrity: sha512-iY4WgHNv/7mDbExXQA35u7H54ihPJTrm20Z42Ni2G+Hgz3X4A0ZmeT7CtpfuBzC3UIrqdmNZT3nQOSoKNwJlWA==} + '@react-router/dev@7.7.0': + resolution: {integrity: sha512-z6tJ0US20pS/YpaPz59SJgSH+1BJ6xvQmQ/u4Y4HM1uLOa4b3Mleg3KujqAvwGP5wkMkNFz3Ae2g6/kDTFyuCA==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@react-router/serve': ^0.0.0-experimental-23decd7bc - react-router: ^0.0.0-experimental-23decd7bc + '@react-router/serve': ^7.7.0 + react-router: ^7.7.0 typescript: ^5.1.0 vite: ^5.1.0 || ^6.0.0 || ^7.0.0 wrangler: ^3.28.2 || ^4.0.0 @@ -2265,11 +2259,11 @@ packages: wrangler: optional: true - '@react-router/node@0.0.0-experimental-23decd7bc': - resolution: {integrity: sha512-y9tOT+jEzBGXrwBjCq2obqKe+N6znsT+I02R/SDFTqTcXrdTJ7aK0iRnWGHxUw/FrOxoPeGkA0v5dWoJI0jBew==} + '@react-router/node@7.7.0': + resolution: {integrity: sha512-PTl4C+QjWsbTfp+9mybOzIH10ZM/pjZrAlcoxc/KGYxcfWDEe2GDFFBQN6nGZgJe/0SwSjHsVwqo2haMKgTbvQ==} engines: {node: '>=20.0.0'} peerDependencies: - react-router: 0.0.0-experimental-23decd7bc + react-router: 7.7.0 typescript: ^5.1.0 peerDependenciesMeta: typescript: @@ -4076,6 +4070,10 @@ packages: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} + isbot@5.1.28: + resolution: {integrity: sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -4977,8 +4975,8 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react-router@0.0.0-experimental-23decd7bc: - resolution: {integrity: sha512-oTDa74rdP6WACxX8wihI71TiwQa+3aAXNjGGm20OAyA4hGdfe0VBEbJvuIT0vxR+LKsJisI4rpaq0boBGY3m+g==} + react-router@7.7.0: + resolution: {integrity: sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -5894,7 +5892,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 +5901,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 +5931,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 +5940,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 @@ -5978,11 +5976,6 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-pipeline-operator': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -6898,31 +6891,31 @@ snapshots: dependencies: quansync: 0.2.10 - '@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)': + '@react-router/dev@7.7.0(@types/node@22.16.5)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.7.0(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/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.0) + '@babel/generator': 7.28.0 + '@babel/parser': 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) + '@react-router/node': 7.7.0(react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3) arg: 5.0.2 babel-dead-code-elimination: 1.0.10 chokidar: 4.0.3 dedent: 1.6.0(babel-plugin-macros@3.1.0) es-module-lexer: 1.7.0 exit-hook: 2.2.1 + isbot: 5.1.28 jsesc: 3.0.2 lodash: 4.17.21 pathe: 1.1.2 picocolors: 1.1.1 prettier: 2.8.8 react-refresh: 0.14.2 - react-router: 0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: 7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) semver: 7.7.2 set-cookie-parser: 2.7.1 tinyglobby: 0.2.14 @@ -6948,10 +6941,10 @@ snapshots: - tsx - yaml - '@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)': + '@react-router/node@7.7.0(react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - react-router: 0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: 7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) optionalDependencies: typescript: 5.8.3 @@ -7765,9 +7758,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 @@ -8739,6 +8732,8 @@ snapshots: dependencies: is-inside-container: 1.0.0 + isbot@5.1.28: {} + isexe@2.0.0: {} jackspeak@3.4.3: @@ -9761,7 +9756,7 @@ snapshots: react-refresh@0.17.0: {} - react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: cookie: 1.0.2 react: 19.1.0