Examples

Static Site (GitHub Pages)

Minimal config for a static site deployed to GitHub Pages:

// vite.config.ts
import { defineConfig } from "vite";
import { vitePluginReactServer } from "vite-plugin-react-server";

export default defineConfig({
  base: "/my-repo/",
  plugins: vitePluginReactServer({
    runner: "isolated",
    moduleBase: "src",
    moduleBaseURL: "/my-repo/",
    Page: (url) => `src/pages${url}page.tsx`,
    props: (url) => `src/pages${url}props.ts`,
    Html: "src/Html.tsx",
    build: { pages: ["/", "/about/"] },
  }),
});

Build and deploy dist/static/.

Live examples:

Dynamic Server (Node / Express)

Don't hand-roll the server: createRequestHandler serves the prerendered output with the MIME types and traversal guard a file server has to get right, dispatches "use server" actions through the sealed baked gate, and renders dynamic routes per request through the edge bundle's render hook — see Build Output:

// server.ts — plain Node, no framework needed
import { createServer } from "node:http";
import {
  createRequestHandler,
  toNodeListener,
} from "vite-plugin-react-server/request-handler";
import { createEdgeRenderHook } from "vite-plugin-react-server/edge";
import * as bundle from "./dist/server-edge/render.js";

const handler = createRequestHandler({
  staticDir: "dist/static",              // prerendered HTML, .rsc, assets
  render: createEdgeRenderHook(bundle),  // per-request dynamic routes
  action: bundle.handleRouteAction,      // sealed action gate, baked at build
});

createServer(toNodeListener(handler)).listen(3000);

Under Express the same handler mounts as middleware — Express only adds whatever else your app needs around it:

import express from "express";

const app = express();
app.use(toNodeListener(handler));
app.listen(3000);

Server Actions

// src/actions/submit.server.ts
"use server";

export async function submitForm(data: FormData) {
  const name = data.get("name") as string;
  // Save to database, send email, etc.
  return { success: true, name };
}
// src/pages/contact/page.tsx
import { submitForm } from "../../actions/submit.server.js";

export const Page = () => (
  <form action={submitForm}>
    <input name="name" required />
    <button type="submit">Submit</button>
  </form>
);

Routing

vprs ships a file-based router — routes: { dir: "routes" }, and the file tree is the URL tree, with dynamic params, per-segment loaders, nested layouts and client-side Link navigation. It has its own page: Routing.

It stays optional. Without routes, map URLs to files yourself with Page and bring your own client router (React Router, TanStack Router, or none):

// Single page
Page: "src/page.tsx",

// Your own mapping
Page: (url) => {
  const routes: Record<string, string> = {
    "/": "src/home.tsx",
    "/about/": "src/about.tsx",
  };
  return routes[url] || "src/404.tsx";
},

React in Config (Server-First)

With react-server condition, you can use JSX directly in your Vite config:

// vite.react.config.tsx
import { defineConfig } from "vite";
import { vitePluginReactServer } from "vite-plugin-react-server";
import { Css } from "vite-plugin-react-server/components";

export default defineConfig({
  plugins: vitePluginReactServer({
    runner: "isolated",
    moduleBase: "src",
    Page: "src/page.tsx",
    components: {
      Html: ({ Root, cssFiles, globalCss, pageProps, Page }) => (
        <html>
          <head><Css cssFiles={globalCss} /></head>
          <body>
            <Root as="div" id="root" cssFiles={cssFiles} Page={Page} pageProps={pageProps} />
          </body>
        </html>
      ),
    },
    build: { pages: ["/"] },
  }),
});

Custom HTML Shell

// src/Html.tsx
import { Css } from "vite-plugin-react-server/components";
import type { HtmlProps } from "vite-plugin-react-server/types";

export const Html = ({ Root, cssFiles, globalCss, pageProps = { title: "My App" }, Page }: HtmlProps) => (
  <html lang="en">
    <head>
      <meta charSet="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <Css cssFiles={globalCss} />
      <title>{pageProps.title}</title>
    </head>
    <body>
      <Root as="main" id="root" cssFiles={cssFiles} Page={Page} pageProps={pageProps} />
    </body>
  </html>
);

Custom Root Component

// src/Root.tsx
import React from "react";
import { Css } from "vite-plugin-react-server/components";

export const Root = ({ Page, pageProps = {}, as: As = React.Fragment, cssFiles, ...props }) => {
  if (As === React.Fragment) {
    return <><Page {...pageProps} /></>;
  }
  return (
    <As {...props} role="main">
      <Page {...pageProps} />
      <Css cssFiles={cssFiles} />
    </As>
  );
};