+
+
Vite + RSC + Nitro
+
+
+
+
+
+
+
Request URL: {props.url?.href}
+
+ -
+ Edit
src/client.tsx to test client HMR.
+
+ -
+ Edit
src/root.tsx to test server HMR.
+
+ {/* -
+ Visit{" "}
+
+
?__rsc
+ {" "}
+ to view RSC stream payload.
+ */}
+ -
+ Visit{" "}
+
+
?__nojs
+ {" "}
+ to test server action without js enabled.
+
+
+
+ );
+}
diff --git a/examples/vite-rsc/package.json b/examples/vite-rsc/package.json
new file mode 100644
index 0000000000..5cfb84fb05
--- /dev/null
+++ b/examples/vite-rsc/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@vitejs/plugin-rsc-examples-starter",
+ "version": "0.0.0",
+ "private": true,
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "latest",
+ "@vitejs/plugin-rsc": "https://pkg.pr.new/@vitejs/plugin-rsc@687458d",
+ "nitro": "latest",
+ "rsc-html-stream": "^0.0.7",
+ "vite": "beta"
+ }
+}
diff --git a/examples/vite-rsc/tsconfig.json b/examples/vite-rsc/tsconfig.json
new file mode 100644
index 0000000000..a7b38dc3ca
--- /dev/null
+++ b/examples/vite-rsc/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "nitro/tsconfig",
+ "compilerOptions": {
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "types": ["vite/client", "@vitejs/plugin-rsc/types"],
+ "jsx": "react-jsx"
+ }
+}
diff --git a/examples/vite-rsc/vite.config.ts b/examples/vite-rsc/vite.config.ts
new file mode 100644
index 0000000000..85ad2922b4
--- /dev/null
+++ b/examples/vite-rsc/vite.config.ts
@@ -0,0 +1,32 @@
+import { defineConfig } from "vite";
+import { nitro } from "nitro/vite";
+
+import rsc from "@vitejs/plugin-rsc";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [
+ nitro({
+ experimental: {
+ vite: {
+ services: {
+ ssr: { entry: "./app/framework/entry.ssr.tsx" },
+ rsc: { entry: "./app/framework/entry.rsc.tsx" },
+ },
+ },
+ },
+ }),
+ rsc({ serverHandler: false }),
+ react(),
+ ],
+
+ environments: {
+ client: {
+ build: {
+ rollupOptions: {
+ input: { index: "./app/framework/entry.browser.tsx" },
+ },
+ },
+ },
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3f08cdb76d..50794d04d8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -476,6 +476,37 @@ importers:
specifier: beta
version: 8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)
+ examples/vite-rsc:
+ dependencies:
+ react:
+ specifier: ^19.2.3
+ version: 19.2.3
+ react-dom:
+ specifier: ^19.2.3
+ version: 19.2.3(react@19.2.3)
+ devDependencies:
+ '@types/react':
+ specifier: ^19.2.7
+ version: 19.2.7
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.7)
+ '@vitejs/plugin-react':
+ specifier: latest
+ version: 5.1.2(vite@8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))
+ '@vitejs/plugin-rsc':
+ specifier: https://pkg.pr.new/@vitejs/plugin-rsc@687458d
+ version: https://pkg.pr.new/@vitejs/plugin-rsc@687458d(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))
+ nitro:
+ specifier: link:../..
+ version: link:../..
+ rsc-html-stream:
+ specifier: ^0.0.7
+ version: 0.0.7
+ vite:
+ specifier: beta
+ version: 8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)
+
examples/vite-ssr-html:
devDependencies:
'@tailwindcss/vite':
@@ -3132,6 +3163,18 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+ '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@687458d':
+ resolution: {integrity: sha512-0s8D3QrdMOF2cbrtDVq3bbjKUFAW6SL6StTqPVIMlcQQUAg2bCv5prg1B/drYcENrfRPOJVYhPVcDlGCJU27gQ==, tarball: https://pkg.pr.new/@vitejs/plugin-rsc@687458d}
+ version: 0.5.10
+ peerDependencies:
+ react: '*'
+ react-dom: '*'
+ react-server-dom-webpack: '*'
+ vite: '*'
+ peerDependenciesMeta:
+ react-server-dom-webpack:
+ optional: true
+
'@vitejs/plugin-vue@6.0.3':
resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -4035,6 +4078,9 @@ packages:
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+ es-module-lexer@2.0.0:
+ resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
+
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -4745,6 +4791,9 @@ packages:
is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
+ is-reference@3.0.3:
+ resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
+
is-regexp@3.1.0:
resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
engines: {node: '>=12'}
@@ -5590,6 +5639,9 @@ packages:
perfect-debounce@2.0.0:
resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==}
+ periscopic@4.0.2:
+ resolution: {integrity: sha512-sqpQDUy8vgB7ycLkendSKS6HnVz1Rneoc3Rc+ZBUCe2pbqlVuCC5vF52l0NJ1aiMg/r1qfYF9/myz8CZeI2rjA==}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -5922,6 +5974,9 @@ packages:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
+ rsc-html-stream@0.0.7:
+ resolution: {integrity: sha512-v9+fuY7usTgvXdNl8JmfXCvSsQbq2YMd60kOeeMIqCJFZ69fViuIxztHei7v5mlMMa2h3SqS+v44Gu9i9xANZA==}
+
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
@@ -6349,6 +6404,9 @@ packages:
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
+ turbo-stream@3.1.0:
+ resolution: {integrity: sha512-tVI25WEXl4fckNEmrq70xU1XumxUwEx/FZD5AgEcV8ri7Wvrg2o7GEq8U7htrNx3CajciGm+kDyhRf5JB6t7/A==}
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -6932,6 +6990,9 @@ packages:
zhead@2.2.4:
resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==}
+ zimmerframe@1.1.4:
+ resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==}
+
zod@3.22.3:
resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
@@ -9473,6 +9534,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@687458d(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))':
+ dependencies:
+ es-module-lexer: 2.0.0
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ periscopic: 4.0.2
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ srvx: 0.10.0
+ strip-literal: 3.1.0
+ turbo-stream: 3.1.0
+ vite: 8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)
+ vitefu: 1.1.1(vite@8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))
+
'@vitejs/plugin-vue@6.0.3(vite@8.0.0-beta.5(@types/node@25.0.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.53
@@ -10376,6 +10451,8 @@ snapshots:
es-module-lexer@1.7.0: {}
+ es-module-lexer@2.0.0: {}
+
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -11248,6 +11325,10 @@ snapshots:
dependencies:
'@types/estree': 1.0.8
+ is-reference@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
is-regexp@3.1.0: {}
is-stream@2.0.1: {}
@@ -12311,6 +12392,12 @@ snapshots:
perfect-debounce@2.0.0: {}
+ periscopic@4.0.2:
+ dependencies:
+ '@types/estree': 1.0.8
+ is-reference: 3.0.3
+ zimmerframe: 1.1.4
+
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -12743,6 +12830,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ rsc-html-stream@0.0.7: {}
+
run-applescript@7.1.0: {}
rxjs@7.8.2:
@@ -13178,6 +13267,8 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
+ turbo-stream@3.1.0: {}
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@@ -13701,6 +13792,8 @@ snapshots:
zhead@2.2.4: {}
+ zimmerframe@1.1.4: {}
+
zod@3.22.3: {}
zod@3.25.76: {}
diff --git a/src/build/vite/env.ts b/src/build/vite/env.ts
index 15c0a5c28e..19979a52de 100644
--- a/src/build/vite/env.ts
+++ b/src/build/vite/env.ts
@@ -68,7 +68,7 @@ export function createServiceEnvironment(
return {
consumer: "server",
build: {
- rollupOptions: { input: serviceConfig.entry },
+ rollupOptions: { input: { index: serviceConfig.entry } },
minify: ctx.nitro!.options.minify,
sourcemap: ctx.nitro!.options.sourcemap,
outDir: join(ctx.nitro!.options.buildDir, "vite/services", name),
diff --git a/src/build/vite/plugin.ts b/src/build/vite/plugin.ts
index e5f46e2dc4..ed66378280 100644
--- a/src/build/vite/plugin.ts
+++ b/src/build/vite/plugin.ts
@@ -285,7 +285,7 @@ function nitroService(ctx: NitroPluginContext): VitePlugin {
function createContext(pluginConfig: NitroPluginConfig): NitroPluginContext {
return {
pluginConfig,
- services: {},
+ services: { ...pluginConfig.experimental?.vite?.services },
_entryPoints: {},
};
}
diff --git a/src/build/vite/types.ts b/src/build/vite/types.ts
index 9f2d6146a0..ed64f604ab 100644
--- a/src/build/vite/types.ts
+++ b/src/build/vite/types.ts
@@ -34,7 +34,12 @@ export interface NitroPluginConfig extends NitroConfig {
*
* @default true
*/
- serverReload: boolean;
+ serverReload?: boolean;
+
+ /**
+ * Additional Vite environment services to register.
+ */
+ services?: Record