@@ -517,11 +526,6 @@ async function hmrWorkflow({
base?: string;
templateName: TemplateName;
}) {
- if (templateName.includes("rsc")) {
- // TODO: Fix CSS HMR support in RSC Framework mode
- return;
- }
-
for (const routeBase of getRouteBasePaths(templateName)) {
let pageErrors: Error[] = [];
page.on("pageerror", (error) => pageErrors.push(error));
@@ -532,8 +536,6 @@ async function hmrWorkflow({
let input = page.locator("input");
await expect(input).toBeVisible();
- await input.type("stateful");
- await expect(input).toHaveValue("stateful");
let edit = createEditor(cwd);
let modifyCss = (contents: string) =>
@@ -544,34 +546,57 @@ async function hmrWorkflow({
"NEW_PADDING_INJECTED_VIA_POSTCSS",
);
- await Promise.all([
- edit(`app/routes/${routeBase}/styles-bundled.css`, modifyCss),
- edit(`app/routes/${routeBase}/styles.module.css`, modifyCss),
- edit(`app/routes/${routeBase}/styles-vanilla-global.css.ts`, modifyCss),
- edit(`app/routes/${routeBase}/styles-vanilla-local.css.ts`, modifyCss),
- edit(`app/routes/${routeBase}/styles-postcss-linked.css`, modifyCss),
- ]);
-
- await Promise.all(
- [
- "#css-bundled",
- "#css-postcss-linked",
- "#css-modules",
- "#css-vanilla-global",
- "#css-vanilla-local",
- ].map(
- async (selector) =>
- await expect(page.locator(selector)).toHaveCSS(
- "padding",
- NEW_PADDING,
- ),
- ),
- );
+ const testCases = [
+ { file: "styles-bundled.css", selector: "#css-bundled" },
+ // TODO: Fix HMR for CSS Modules in server-first routes in RSC Framework mode
+ ...(routeBase === "rsc-server-first-route"
+ ? []
+ : [{ file: "styles.module.css", selector: "#css-modules" }]),
+ // TODO: Fix HMR for `?url` CSS imports in RSC Framework mode: https://github.com/vitejs/vite-plugin-react/issues/772
+ // Once fixed, check if this also fixes HMR for Vanilla Extract
+ ...(templateName.includes("rsc")
+ ? []
+ : [
+ {
+ file: "styles-postcss-linked.css",
+ selector: "#css-postcss-linked",
+ },
+ {
+ file: "styles-vanilla-global.css.ts",
+ selector: "#css-vanilla-global",
+ },
+ {
+ file: "styles-vanilla-local.css.ts",
+ selector: "#css-vanilla-local",
+ },
+ ]),
+ ] as const satisfies Array<{
+ file: string;
+ selector: string;
+ }>;
+
+ for (const { file, selector } of testCases) {
+ const routeFile = `app/routes/${routeBase}/${file}`;
+ await input.fill(routeFile);
+ await edit(routeFile, modifyCss);
+ await expect(
+ page.locator(selector),
+ `CSS update for ${routeFile}`,
+ ).toHaveCSS("padding", NEW_PADDING);
+
+ // TODO: Fix state preservation when changing CSS Modules in RSC Framework mode
+ if (templateName.includes("rsc") && file === "styles.module.css") {
+ continue;
+ }
- // Ensure CSS updates were handled by HMR
- await expect(input).toHaveValue("stateful");
+ // Ensure CSS updates were handled by HMR
+ await expect(input, `State preservation for ${routeFile}`).toHaveValue(
+ routeFile,
+ );
+ }
- if (routeBase === "css") {
+ // RSC Framework mode doesn't support custom entries yet
+ if (!templateName.includes("rsc")) {
// The following change triggers a full page reload, so we check it after all the checks for HMR state preservation
await edit("app/entry.client.css", modifyCss);
await expect(page.locator("#entry-client")).toHaveCSS(
diff --git a/packages/react-router-dev/package.json b/packages/react-router-dev/package.json
index 3b78681fa5..039d77a6c2 100644
--- a/packages/react-router-dev/package.json
+++ b/packages/react-router-dev/package.json
@@ -78,7 +78,7 @@
"@babel/types": "^7.27.7",
"@npmcli/package-json": "^4.0.1",
"@react-router/node": "workspace:*",
- "@vitejs/plugin-rsc": "0.4.11",
+ "@vitejs/plugin-rsc": "0.4.21",
"arg": "^5.0.1",
"babel-dead-code-elimination": "^1.0.6",
"chokidar": "^4.0.0",
diff --git a/packages/react-router/lib/rsc/server.ssr.tsx b/packages/react-router/lib/rsc/server.ssr.tsx
index f7a20738e4..47bb43df09 100644
--- a/packages/react-router/lib/rsc/server.ssr.tsx
+++ b/packages/react-router/lib/rsc/server.ssr.tsx
@@ -112,6 +112,8 @@ export async function routeRSCServerRequest({
throw new Error("Missing body in server response");
}
+ const detectRedirectResponse = serverResponse.clone();
+
let serverResponseB: Response | null = null;
if (hydrate) {
serverResponseB = serverResponse.clone();
@@ -126,7 +128,12 @@ export async function routeRSCServerRequest({
};
try {
- const payload = await getPayload();
+ if (!detectRedirectResponse.body) {
+ throw new Error("Failed to clone server response");
+ }
+ const payload = (await createFromReadableStream(
+ detectRedirectResponse.body,
+ )) as RSCPayload;
if (
serverResponse.status === SINGLE_FETCH_REDIRECT_STATUS &&
payload.type === "redirect"
diff --git a/playground/rsc-vite-framework/package.json b/playground/rsc-vite-framework/package.json
index ba68f82041..adfced593d 100644
--- a/playground/rsc-vite-framework/package.json
+++ b/playground/rsc-vite-framework/package.json
@@ -18,7 +18,7 @@
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.5.2",
- "@vitejs/plugin-rsc": "0.4.11",
+ "@vitejs/plugin-rsc": "0.4.21",
"cross-env": "^7.0.3",
"remark-frontmatter": "^5.0.0",
"remark-mdx-frontmatter": "^5.2.0",
diff --git a/playground/rsc-vite/package.json b/playground/rsc-vite/package.json
index 736990179e..2029e23792 100644
--- a/playground/rsc-vite/package.json
+++ b/playground/rsc-vite/package.json
@@ -15,7 +15,7 @@
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.5.2",
- "@vitejs/plugin-rsc": "0.4.11",
+ "@vitejs/plugin-rsc": "0.4.21",
"cross-env": "^7.0.3",
"typescript": "^5.1.6",
"vite": "^6.2.0"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3815f2f0b3..14060069c4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -512,8 +512,8 @@ importers:
specifier: ^4.5.2
version: 4.5.2(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
'@vitejs/plugin-rsc':
- specifier: 0.4.11
- version: 0.4.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
+ specifier: 0.4.21
+ version: 0.4.21(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
typescript:
specifier: ^5.1.6
version: 5.4.5
@@ -573,8 +573,8 @@ importers:
specifier: ^4.5.2
version: 4.5.2(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
'@vitejs/plugin-rsc':
- specifier: 0.4.11
- version: 0.4.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
+ specifier: 0.4.21
+ version: 0.4.21(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
cross-env:
specifier: ^7.0.3
version: 7.0.3
@@ -1127,8 +1127,8 @@ importers:
specifier: workspace:*
version: link:../react-router-node
'@vitejs/plugin-rsc':
- specifier: 0.4.11
- version: 0.4.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
+ specifier: 0.4.21
+ version: 0.4.21(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
arg:
specifier: ^5.0.1
version: 5.0.2
@@ -1911,8 +1911,8 @@ importers:
specifier: ^4.5.2
version: 4.5.2(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
'@vitejs/plugin-rsc':
- specifier: 0.4.11
- version: 0.4.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
+ specifier: 0.4.21
+ version: 0.4.21(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
cross-env:
specifier: ^7.0.3
version: 7.0.3
@@ -1972,8 +1972,8 @@ importers:
specifier: ^4.5.2
version: 4.5.2(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
'@vitejs/plugin-rsc':
- specifier: 0.4.11
- version: 0.4.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
+ specifier: 0.4.21
+ version: 0.4.21(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
cross-env:
specifier: ^7.0.3
version: 7.0.3
@@ -3817,9 +3817,6 @@ packages:
'@mjackson/node-fetch-server@0.6.1':
resolution: {integrity: sha512-9ZJnk/DJjt805uv5PPv11haJIW+HHf3YEEyVXv+8iLQxLD/iXA68FH220XoiTPBC4gCg5q+IMadDw8qPqlA5wg==}
- '@mjackson/node-fetch-server@0.7.0':
- resolution: {integrity: sha512-un8diyEBKU3BTVj3GzlTPA1kIjCkGdD+AMYQy31Gf9JCkfoZzwgJ79GUtHrF2BN3XPNMLpubbzPcxys+a3uZEw==}
-
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
cpu: [arm64]
@@ -4413,6 +4410,9 @@ packages:
'@remix-run/changelog-github@0.0.5':
resolution: {integrity: sha512-43tqwUqWqirbv6D9uzo55ASPsCJ61Ein1k/M8qn+Qpros0MmbmuzjLVPmtaxfxfe2ANX0LefLvCD0pAgr1tp4g==}
+ '@remix-run/node-fetch-server@0.8.0':
+ resolution: {integrity: sha512-8/sKegb4HrM6IdcQeU0KPhj9VOHm5SUqswJDHuMCS3mwbr/NRx078QDbySmn0xslahvvZoOENd7EnK40kWKxkg==}
+
'@remix-run/web-blob@3.1.0':
resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==}
@@ -5232,8 +5232,8 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
- '@vitejs/plugin-rsc@0.4.11':
- resolution: {integrity: sha512-+4H4wLi+Y9yF58znBfKgGfX8zcqUGt8ngnmNgzrdGdF1SVz7EO0sg7WnhK5fFVHt6fUxsVEjmEabsCWHKPL1Tw==}
+ '@vitejs/plugin-rsc@0.4.21':
+ resolution: {integrity: sha512-bczK6FFl5R0Drob0VpfeQt5avMfdAchp6EMVBswBUdvxerEeYOEamAWB0zeDEf+5zYZvVISbE8M/dERllx2V9A==}
peerDependencies:
react: '*'
react-dom: '*'
@@ -11984,8 +11984,6 @@ snapshots:
'@mjackson/node-fetch-server@0.6.1': {}
- '@mjackson/node-fetch-server@0.7.0': {}
-
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
optional: true
@@ -12828,6 +12826,8 @@ snapshots:
transitivePeerDependencies:
- encoding
+ '@remix-run/node-fetch-server@0.8.0': {}
+
'@remix-run/web-blob@3.1.0':
dependencies:
'@remix-run/web-stream': 1.1.0
@@ -13782,9 +13782,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vitejs/plugin-rsc@0.4.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))':
+ '@vitejs/plugin-rsc@0.4.21(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))':
dependencies:
- '@mjackson/node-fetch-server': 0.7.0
+ '@remix-run/node-fetch-server': 0.8.0
es-module-lexer: 1.7.0
estree-walker: 3.0.3
magic-string: 0.30.17
@@ -13795,9 +13795,9 @@ snapshots:
vite: 6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
vitefu: 1.1.1(vite@6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
- '@vitejs/plugin-rsc@0.4.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))':
+ '@vitejs/plugin-rsc@0.4.21(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))':
dependencies:
- '@mjackson/node-fetch-server': 0.7.0
+ '@remix-run/node-fetch-server': 0.8.0
es-module-lexer: 1.7.0
estree-walker: 3.0.3
magic-string: 0.30.17