Vite plugin for React Server Components (RSC)
npm install vite-plugin-react-serverA Vite plugin that enables React Server Components (RSC) streaming and static HTML page generation. This plugin uses React conditions to automatically provide the optimal implementation for each execution environment.
React Components as part of your build tooling - not just as a dependency.
Vite's philosophy is built around Native ESM and making frameworks first-class citizens. This plugin extends that philosophy to React Server Components:
- Native ESM for React: Your React components are true ESM modules that work anywhere
- React as Configuration: Serialize React Server Components for static hosting
- On-Demand Loading: Only streams the pages you're actually developing
- React condition: Automatically adapts to client/server environments
``sh`
npm install -D vite-plugin-react-server patch-package react@experimental react-dom@experimental react-server-dom-esm
npm run patch
Minimal Config:
`ts
// vite.config.ts
import { defineConfig } from "vite";
import { vitePluginReactServer } from "vite-plugin-react-server";
export default defineConfig({
plugins: vitePluginReactServer({
moduleBase: "src",
Page: src/page.tsx,`
build: { pages: ["/"] }
}),
});
Create a Page:
`tsx`
// src/page.tsx
export const Page = ({ url }: { url: string }) => {
return Hello from {url};
};
Run the Development Server:
`bash`
NODE_OPTIONS="--conditions react-server" npx viteor
npx vite
Both these commands show the same application with roughly the same developer experience.
React Condition System
The react-server condition is needed for the ESM system to consume the React dependencies. What it boils down to is the following semantics:
- When we say we want to "render HTML", it means we need the client environment
- When we say we want to "render RSC", it means we need the server environment
Just like HTML, RSC is simply a string. It's the serialized product of your React Components. Since it's serialized, we are free to send it back and forth from one thread to another using worker threads. This plugin enables exactly this worker thread approach by managing the React condition for you. This explains why both:
`bash`
NODE_OPTIONS="--conditions react-server" npx viteor
npx vite
Return the same application, but we now understand that the former will need an html-worker and the latter will need the rsc-worker to both serialize RSC and HTML.
Visit your app: Open http://localhost:5173 in your browser. You should see "Hello from /" displayed.
What's Next?
- Add more pages to build.pages array"use server"
- Create server actions with "use client"
- Add client components with
- Customize your HTML template
The plugin supports both traditional multi-step builds and modern Environment API builds:
`bash`Build all environments separately
npm run build:static # vite build
npm run build:client # vite build --ssr
npm run build:server # NODE_OPTIONS="--conditions react-server" vite build
Note: The traditional build approach is supported but may need configuration adjustments for proper server environment handling.
The plugin now supports Vite's Environment API with two modes:
#### Server-First Mode (Faster)
`bash`
NODE_OPTIONS='--conditions react-server' vite build --app
- Static generation on main thread
- HTML rendering via html-worker
- Benefits: Faster execution, easier debugging, direct component access
#### Client-First Mode (Isolated)
`bash`
vite build --app
- Static generation on RSC worker thread
- HTML rendering on main thread
- Benefits: Server thread isolation, custom RSC worker support
The plugin uses Node.js conditions to automatically load the correct implementation:
`typescript
// The plugin automatically detects and loads the right implementation
import { getCondition } from './config/getCondition.js';
const condition = getCondition(); // Returns 'client' or 'server'
const { vitePluginReactServer } = await import(./plugin.${condition}.js);`
#### Traditional approach:
This has the benefit of controlling and debugging each build separately
`json`
{
"type": "module",
"scripts": {
"dev:rsc": "NODE_OPTIONS='--conditions react-server' vite",
"dev:ssr": "vite",
"build": "NODE_OPTIONS='--conditions react-server' vite build --app",
"preview": "vite preview"
}
}
The plugin supports two development paradigms. Both produce identical output but differ in architecture:
| Mode | Condition | Command | Architecture | Benefits |
|------|-----------|---------|--------------|----------|
| RSC | react-server | npm run dev:rsc | RSC on main thread | Easier debugging, React in config |null
| SSR | (default) | npm run dev:ssr | RSC in worker | Better isolation, traditional SSR split |
- npm run dev:rsc (RSC Mode):
- Main thread has react-server conditionvite.react.config.tsx
- RSC processing runs directly (no worker)
- Easier debugging - breakpoints work in server components
- Supports React in config files (e.g., )
- npm run dev:ssr (SSR Mode):
- Main thread is client-focused
- RSC processing runs in isolated worker thread
- Closer to traditional SSR/client architecture
- npm run build (Build Environment):
1. Static Build: vite build → dist/static/vite build --ssr
2. Client Build: → dist/client/NODE_OPTIONS="--conditions react-server" vite build --ssr
3. Server Build: → dist/server/ + final dist/static/use client
- /use server boundary transformationsindex.html
- and index.rsc to dist/static/${route} for each build.pages
- npm run dev-build:
- Debug the build process
- Avoids the "this error message is hidden in production" and shows the full error
- Development build is not intended for production
The plugin automatically adapts to different execution environments using Node.js conditions:
`typescript
import { getCondition } from "vite-plugin-react-server/config";
if (getCondition() !== "react-server") {
throw new Error("-10 poison damage");
}
`
- RSC Mode (npm run dev:rsc):react-server
- Condition: on main thread
- RSC processing runs directly on the main Vite thread
- Easier debugging - breakpoints work in server components
- Supports React in config files
- SSR Mode (npm run dev:ssr):
- Condition: default (client-focused main thread)
- RSC processing runs in isolated worker thread
- Traditional SSR/client architecture
- Build Mode (npm run build):NODE_OPTIONS='--conditions react-server' vite build --app
- Command:
- Builds all Vite environments (client, ssr, server)
- Generates static HTML files
The plugin uses condition-based module loading:
``
plugin/
├── index.ts # Main entry point with condition detection
├── plugin.client.ts # Client environment implementation
├── plugin.server.ts # Server environment implementation
├── dev-server/
│ ├── index.ts # Condition-based loader
│ ├── index.client.ts # Client implementation
│ ├── index.server.ts # Server implementation
│ └── ... # Other modules follow same pattern
└── ...
This ensures:
- Client environments get lightweight, browser-compatible implementations
- Server environments get full-featured RSC processing capabilities
- No runtime overhead from unused server code in client environments
`tsx`
// Simple string-based routing
export default defineConfig({
plugins: vitePluginReactServer({
Page: "src/page/page.tsx",
props: "src/page/props.ts",
build: { pages: ["/", "/about"] }
}),
});
`tsx
// actions.server.ts
"use server";
export async function addTodo(title: string) {
return { success: true };
}
// Use in components
import { addTodo } from "./actions.server.js";
export function TodoForm() {
return
$3
`tsx
// src/components/Link.client.tsx
"use client";
import React from 'react';export function Link({ to, children }: { to: string, children: React.ReactNode }) {
return {children};
}
``tsx
// Counter.client.tsx
"use client";
import { useState } from "react";export function Counter() {
const [count, setCount] = useState(0);
return ;
}
`Key Points:
- Use
"use client" directive at the top of client component files
- Use .client.` suffix in filenames for auto-discovery- Node.js: 23.7.0 or higher
- React: Experimental version (handled by patch system)
- Vite: Compatible with latest Vite versions
This project uses experimental React features and includes a patch system for compatibility. See React Type Compatibility for maintenance details.
| Topic |
|-------|
| Getting Started |
| Core Concepts |
| Configuration Guide |
| CSS & Styling |
| Server Actions |
| Build & Deployment |
| Advanced Development |
| Plugin Internals |
| Worker System |
| API Reference |
| React Compatibility |
| Troubleshooting |
| Package Exports |
| Transformations |
MIT License - see LICENSE file for details.