JSPM

  • Created
  • Published
  • Downloads 1072
  • Score
    100M100P100Q106649F
  • License MIT

Vite plugin for React Server Components (RSC)

Package Exports

  • vite-plugin-react-server
  • vite-plugin-react-server/check-react-version
  • vite-plugin-react-server/client
  • vite-plugin-react-server/components
  • vite-plugin-react-server/package.json
  • vite-plugin-react-server/server
  • vite-plugin-react-server/types

Readme

Vite React Server Plugin

A Vite plugin that enables React Server Components (RSC) streaming and static HTML page generation. It leverages experimental dependencies from React, specifically react-server-dom-esm.

Example Projects

Installation

npm install -D vite-plugin-react-server

Open Source and Work in Progress

This project uses the latest OSS-experimental React version from the official React GitHub repository. The plugin includes a patch system to facilitate setup. First, install dependencies and patches:

npm install -D patch-package react@experimental react-dom@experimental react-server-dom-esm

Add the following command to your package.json scripts:

"patch": "patch"

Run the patch command:

npm run patch

It will instruct you to add:

"postinstall": "patch-package"

This ensures the patch is applied after every npm install. If errors arise related to react-server-dom-esm, verify that the postinstall step ran.


Plugin Structure and Purpose

Environment-Based Execution

This plugin uses environment detection to determine the execution context. It achieves this by checking the NODE_OPTIONS environment variable:

import { getCondition } from "vite-plugin-react-server"

if(getCondition() !== 'react-server'){
  throw new Error('-10 poision damage')
}

Alternatively, you can pass the argument for the react- prefix to just get client or server back.

import { getCondition } from "vite-plugin-react-server"

import(`plugin.${getCondition('')}.js`)

The main entry point adapts based on the environment:

  • Client Mode (default) → Does not require the react-server condition, uses a worker thread for RSC requests
  • Server Mode (NODE_OPTIONS="--conditions react-server") → Does not need worker thread for RSC requests

Custom composition

You can pick and choose only the plugins you like to get the desired behavior as well. For example, we can choose only to use the preserver, the transformer, static plugin, etc.

Worker support

The client plugin uses the rsc-worker to create server side streams. The server plugin uses the html-worker to create client side html. If you don't want to use the rsc-worker, simply don't serve the plugin without the react-server condition. If you don't want to use the html-worker simply don't configure the build.pages option.

Custom Worker

Both workers can be customized using the htmlWorkerPath and rscWorkerPath respectively. The paths will be used to create the workers instead of the prebuilt worker included with this plugin. If these paths are defined, they will be made part of your application build as well.

Keep in mind that, using your custom worker means interacting with the message system of this plugin during development/static generation process.

For more information on creating your custom workers, see docs

Plugin Usage

import { defineConfig, type Plugin } from "vite";
import { vitePluginReactClient } from "vite-plugin-react-server";
import { config } from "./vite.react.config";
import type { StreamPluginOptions } from "vite-plugin-react-server/server";

const createRouter = (file: "props.ts" | "page.tsx") => (url: string) => {
  switch (url) {
    case "/bidoof":
    case "/bidoof/index.rsc":
      return `src/page/bidoof/${file}`;
    case "/404":
    case "/404/index.rsc":
      return `src/page/404/${file}`;
    case "/":
    case "/index.rsc":
      return `src/page/${file}`;
    default:
      throw new Error(`Unknown route: ${url}`);
  }
};

export const config = {
  moduleBase: "src",
  Page: createRouter("page.tsx"),
  props: createRouter("props.ts"),
  Html: Html,
  build: {
    pages: ["/", "/bidoof", "/404"	],
  },
} satisfies StreamPluginOptions;

export default defineConfig({
  plugins: vitePluginReactClient(config),
});

Built-in React Server Components

This plugin has two built-in React Component, each can be configured through the options to be your own component. Defining your custom React server components will affect the final production output, they won't be used during development.

  • Html - used as the wrapper for production pages (use vite's index.html for the development wrapper and entry point for client files)
  • CssCollector - used to emit <link> and <style> tags based on css config

Build Steps

vite build

Targets browsers, outputs to dist/static.

vite build --ssr

Targets non-react-server node environment, used for server-side-rendering, outputs to dist/client.

NODE_OPTIONS="--conditions=react-server" vite build

Targets react-server-only environment, outputs to dist/server. In this case, ssr is implied and defaults to true.


vite-plugin-react-server

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

export default defineConfig({
  plugins: vitePluginReactServer(config),
});

Running in Development

NODE_OPTIONS="--conditions=react-server" vite

Is the recommended way for a more direct server pipeline that doesn't require a rsc-worker.

To develop the app using the rsc-worker, simply run

vite

without the react-server condition.

Static Site Generation

Single-out the static generation step by only inluding the static plugin. Expects client and server folders to be there.

import { defineConfig, Plugin } from "vite";
import { reactStaticPlugin } from "vite-plugin-react-server/static";
import { config } from "./vite.react.config";

export default defineConfig({
  plugins: [reactStaticPlugin(config)],
});

Example output structure:

dist/static/index.html
dist/static/index.rsc
dist/static/about/index.html
dist/static/about/index.rsc

This plugin is included by default when the react-server condition is set.


Configuration

moduleBase

const config = {
  moduleBase: "src",
}

Defines the root directory for project modules. This can be customized.

moduleBasePath

moduleBasePath: "",

Passed as the second argument to renderToPipeableStream for server-side rendering.

moduleBaseURL

moduleBaseURL: "https://github.com/my-gh-pages",

Defines asset URL resolution for CSS collectors and bootstrapModule.

Page and props Mapping

Page: (id) => join(id.replace('index.rsc',''), 'page.tsx')

Defines how pages are mapped to file paths.

props: (id) => join(id.replace('index.rsc',''), 'props.ts')

Defines how to load the initial props of the page file.

If you do not want prop files, just don't define it.

pageExportName: 'Page',

Changes the default name "Page"

propsExportName: 'props',

Changes the default name "props"


Example Setup

package.json Scripts

"scripts": {
  "build": "build:static && build:client && build:server",
  "dev": "NODE_OPTIONS='--conditions react-server' vite",
  "start": "vite",
  "build:server": "NODE_OPTIONS='--conditions react-server' vite build",
  "build:client": "vite build --ssr",
  "build:static": "vite build"
}

Sample Page Component

// src/my-page.tsx
export const Page = ({ name }) => {
  return <div>Hello {name}</div>;
};

Sample Props File

// src/my-props.ts
export const props = {
  name: "John Doe",
};

Contributions

If you want to help develop or maintain the plugin feel free to open a PR or issue on GitHub.