Package Exports
- vite-plugin-react-server
- vite-plugin-react-server/client
- vite-plugin-react-server/components
- vite-plugin-react-server/config
- vite-plugin-react-server/config/createHandlerOptions
- vite-plugin-react-server/css-loader
- vite-plugin-react-server/dev-server
- vite-plugin-react-server/dev-server/cleanupServerAction
- vite-plugin-react-server/dev-server/configureReactServer
- vite-plugin-react-server/dev-server/configureRequestHandler
- vite-plugin-react-server/dev-server/handleServerAction
- vite-plugin-react-server/dev-server/restartWorker
- vite-plugin-react-server/directives
- vite-plugin-react-server/env
- vite-plugin-react-server/env-loader
- vite-plugin-react-server/env/plugin
- vite-plugin-react-server/error
- vite-plugin-react-server/file-preserver
- vite-plugin-react-server/helpers
- vite-plugin-react-server/helpers/handleServerAction
- vite-plugin-react-server/helpers/resolveStreamElements
- vite-plugin-react-server/html-worker
- vite-plugin-react-server/loader
- vite-plugin-react-server/metrics
- vite-plugin-react-server/orchestrator
- vite-plugin-react-server/orchestrator/createPluginOrchestrator
- vite-plugin-react-server/package.json
- vite-plugin-react-server/plugin
- vite-plugin-react-server/react-client
- vite-plugin-react-server/react-client/plugin
- vite-plugin-react-server/react-server
- vite-plugin-react-server/react-server/plugin
- vite-plugin-react-server/react-static
- vite-plugin-react-server/react-static/createBuildLoader
- vite-plugin-react-server/react-static/plugin
- vite-plugin-react-server/react-static/renderPage
- vite-plugin-react-server/react-static/rscToHtmlStream
- vite-plugin-react-server/react-static/temporaryReferences
- vite-plugin-react-server/references
- vite-plugin-react-server/register
- vite-plugin-react-server/rsc-worker
- vite-plugin-react-server/server
- vite-plugin-react-server/static
- vite-plugin-react-server/storybook
- vite-plugin-react-server/stream
- vite-plugin-react-server/stream/client
- vite-plugin-react-server/stream/createFromNodeStream
- vite-plugin-react-server/stream/createHtmlStream
- vite-plugin-react-server/stream/createRenderToPipeableStreamHandler
- vite-plugin-react-server/stream/createRscStream
- vite-plugin-react-server/stream/handleRscStream
- vite-plugin-react-server/stream/server
- vite-plugin-react-server/transformer
- vite-plugin-react-server/transformer/plugin
- vite-plugin-react-server/types
- vite-plugin-react-server/utils
- vite-plugin-react-server/utils/rsc-client
- vite-plugin-react-server/vendor
- vite-plugin-react-server/vendor.client
- vite-plugin-react-server/vendor.server
- vite-plugin-react-server/vendor.static
- vite-plugin-react-server/virtual
- vite-plugin-react-server/worker
Readme
vite-plugin-react-server
React Server Components for plain Vite on stable React — no framework, no
experimental builds. One vite build --app prerenders your pages to static
HTML + RSC payloads and emits your components as portable ESM that runs under
any HTTP server: static hosting, Express/Hono, or anything in between.
It works in BOTH Node module conditions, by design: the dev server and the
build run with or without --conditions react-server, and whichever side your
main thread is on, a worker thread mirrors the other half (server components
need a react-server context, client hydration needs a react-client one — you
always have both). Running the main thread under react-server is an optional
optimization — slightly faster, better stack traces — never a requirement.
Where this sits: vprs is a low-level plugin, not a framework, so it
imposes no router or app structure. Its closest peer is the official
@vitejs/plugin-rsc;
vprs differs by using the ESM transport (react-server-dom-esm) and emitting
portable ESM you host yourself. If you'd rather a framework decide for you, look
at Waku or Vike. Full breakdown, including what vprs does not do:
How vprs compares.
Install
npm install -D vite-plugin-react-server react react-domvprs runs on stable React 19.2+ out of the box. Everything
React-version-locked — the react-server-dom-esm transport (server side AND
the flight client your browser bundle ships), the directive engine, the Node
loader — lives in the
react-server-loader
dependency, installed for you (every package manager). The command above is all
you need.
Want correct CSS preloading? Stable React 19.2.x emits its CSS preload hint
as an invalid as="stylesheet", so browsers ignore the preload (styles still
load, just not preloaded) and log a warning. The fix is on React's experimental
channel today (and reaches stable when React ships it). To opt in, install the
experimental train — all three pinned together:
npm install react@experimental react-dom@experimental react-server-loader@experimentalThat's the whole step: react-server-loader's range admits both trains, so npm
collapses to a single experimental copy — no overrides, no duplicate. See
React Compatibility. Upgrading from 1.x?
See the migration notes.
Minimal Example
// 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: ["/"] },
}),
});// src/page.tsx
export const Page = ({ url }: { url: string }) => <div>Hello from {url}</div>;# Dev server
npx vite
# Build (static site + server/client ESM)
npx vite build --app
# Same build, react-server main thread — optional: a bit faster, better stack traces
NODE_OPTIONS='--conditions react-server' vite build --appBuild Output
dist/
├── static/ # Deployable to any static host
│ ├── index.html # Pre-rendered HTML
│ └── index.rsc # RSC payload for client navigation
├── client/ # Client-side ESM modules (for SSR)
└── server/ # Server-side ESM modules (with server actions)dist/static/ is a complete static site. dist/client/ and dist/server/ are ESM modules you can import in your own Express/Hono/Node server.
Client components
vprs recognises a file as a client module when either of these is true:
- the filename matches
(^|[\/.])client\.[cm]?[jt]sx?$— i.e.Button.client.tsx,bar.client.mjs, or the standalone basenamesrc/client.tsx/client.tsx, or - the file starts with a top-of-file
"use client"directive (leading whitespace, line/block comments, and an optional"use strict"prologue are tolerated above it).
Either is sufficient. Substrings like clientUtils.tsx, clientId.ts, or clients.tsx are not treated as client modules, and a "use client" directive placed after real code does not count.
// src/components/Counter.tsx ← no `.client.` suffix needed
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}See Getting Started.
Third-party client-component packages
Component libraries like Chakra UI, MUI, Mantine, react-aria, and framer-motion are client-only — their components rely on React context/state and must run inside a client boundary, the same constraint they carry under Next.js's App Router. Use them within a "use client" component (commonly a small provider wrapper); they can't be imported directly into a server component. This isn't a vprs limitation — e.g. Chakra's own Next.js App Router guide requires wrapping ChakraProvider in a 'use client' component.
vprs auto-detects these so they're treated correctly at build start: any package with react in its peerDependencies is classified as a client package (using vitefu.crawlFrameworkPkgs). Two escape hatches if needed:
vitePluginReactServer({
// Force a package into the list (e.g. one that doesn't peerDep react)
clientPackages: ["@my/internal-ui"],
// Skip a detected one (e.g. devDeps Storybook bringing along @storybook/react)
excludeClientPackages: ["@storybook/react", "@storybook/react-vite"],
});Storybook
vprs ships a Storybook preset — add one line and your RSC app's components build and render in Storybook:
// .storybook/main.ts
export default {
framework: { name: "@storybook/react-vite", options: {} },
addons: ["vite-plugin-react-server/storybook"],
};It strips the vprs plugin from Storybook's builder, resolves the
react-server-dom-esm transport (from react-server-loader), and silences
"use client"/"use server" directive noise. See
Storybook for details. (Requires vprs ≥ 1.9.0.)
Documentation
| Doc | What it covers |
|---|---|
| How vprs compares | vprs vs @vitejs/plugin-rsc, Waku, Vike — and what vprs does not do |
| Getting Started | Install → first page → dev server → build → deploy |
| Storybook | One-line Storybook support for vprs apps |
| Build Output | What the build produces, how to use the ESM modules |
| Configuration | All plugin options |
| CSS Handling | Inline/linked CSS, CSS modules, the Css component |
| Server Actions | "use server" directives, form actions, hosting |
| Examples | Static site, dynamic server, server actions, custom routing |
| Troubleshooting | Common errors and fixes |
| API Reference | Exported functions, types, and components |
Internals (contributors)
| Doc | What it covers |
|---|---|
| Architecture | Condition system, module structure, plugin composition |
| Transformer | How "use client" / "use server" directives are processed |
| Workers | RSC and HTML worker threads |
Maintenance
| Doc | What it covers |
|---|---|
| Releasing | Version bumps, publishing, demo updates |
| React Compatibility | Vendored ESM transport, type system |
Requirements
- Node.js 22.0.0+ (the build uses
node:fs/promises#glob, which landed in 22) - Stable React 19.2+ (
react/react-domat^19.2.7). As of 2.0 the RSC server APIs vprs relies on (prerenderToNodeStream, thereact-servertransport exports) are part of stable React, so no experimental build is required. The matchingreact-server-dom-esmtransport is provided by thereact-server-loaderpeer; experimental React still works if you want the newest RSC features. See React Compatibility. - Vite 6+
TypeScript
{
"compilerOptions": {
"types": ["vite/client", "vite-plugin-react-server/virtual"]
}
}License
MIT