npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@vitejs/plugin-rsc

v0.5.5

Published

React Server Components (RSC) support for Vite.

Readme

@vitejs/plugin-rsc

This package provides React Server Components (RSC) support for Vite.

Features

  • Framework-agnostic: The plugin implements RSC bundler features and provides low level RSC runtime (react-server-dom) API without framework-specific abstractions.
  • Runtime-agnostic: Built on Vite environment API and works with other runtimes (e.g., @cloudflare/vite-plugin).
  • HMR support: Enables editing both client and server components without full page reloads.
  • CSS support: CSS is automatically code-split both at client and server components and they are injected upon rendering.

Getting Started

You can create a starter project by:

npm create vite@latest -- --template rsc

Examples

Start here: ./examples/starter - Recommended for understanding the package

  • Provides an in-depth overview of API with inline comments to explain how they function within RSC-powered React application.

Integration examples:

Basic Concepts

This example is a simplified version of ./examples/starter. You can read ./examples/starter/src/framework/entry.{rsc,ssr,browser}.tsx for more in-depth commentary, which includes server function handling and client-side RSC re-fetching/re-rendering.

This is the diagram to show the basic flow of RSC rendering process. See also https://github.com/hi-ogawa/vite-plugins/discussions/606.

graph TD

    subgraph "<strong>rsc environment</strong>"
        A["React virtual dom tree"] --> |"[@vitejs/plugin-rsc/rsc]<br /><code>renderToReadableStream</code>"| B1["RSC Stream"];
    end

    B1 --> B2
    B1 --> B3

    subgraph "<strong>ssr environment</strong>"
        B2["RSC Stream"] --> |"[@vitejs/plugin-rsc/ssr]<br /><code>createFromReadableStream</code>"| C1["React virtual dom tree"];
        C1 --> |"[react-dom/server]<br/>SSR"| E["HTML String/Stream"];
    end

    subgraph "<strong>client environment</strong>"
        B3["RSC Stream"] --> |"[@vitejs/plugin-rsc/browser]<br /><code>createFromReadableStream</code>"| C2["React virtual dom tree"];
        C2 --> |"[react-dom/client]<br/>CSR: mount, hydration"| D["DOM Elements"];
    end

    style A fill:#D6EAF8,stroke:#333,stroke-width:2px
    style B1 fill:#FEF9E7,stroke:#333,stroke-width:2px
    style B2 fill:#FEF9E7,stroke:#333,stroke-width:2px
    style B3 fill:#FEF9E7,stroke:#333,stroke-width:2px
    style C1 fill:#D6EAF8,stroke:#333,stroke-width:2px
    style C2 fill:#D6EAF8,stroke:#333,stroke-width:2px
    style D fill:#D5F5E3,stroke:#333,stroke-width:2px
    style E fill:#FADBD8,stroke:#333,stroke-width:2px
import rsc from '@vitejs/plugin-rsc'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    // add plugin
    rsc(),
  ],

  // specify entry point for each environment.
  environments: {
    // `rsc` environment loads modules with `react-server` condition.
    // this environment is responsible for:
    // - RSC stream serialization (React VDOM -> RSC stream)
    // - server functions handling
    rsc: {
      build: {
        rollupOptions: {
          input: {
            index: './src/framework/entry.rsc.tsx',
          },
        },
      },
    },

    // `ssr` environment loads modules without `react-server` condition.
    // this environment is responsible for:
    // - RSC stream deserialization (RSC stream -> React VDOM)
    // - traditional SSR (React VDOM -> HTML string/stream)
    ssr: {
      build: {
        rollupOptions: {
          input: {
            index: './src/framework/entry.ssr.tsx',
          },
        },
      },
    },

    // client environment is used for hydration and client-side rendering
    // this environment is responsible for:
    // - RSC stream deserialization (RSC stream -> React VDOM)
    // - traditional CSR (React VDOM -> Browser DOM tree mount/hydration)
    // - refetch and re-render RSC
    // - calling server functions
    client: {
      build: {
        rollupOptions: {
          input: {
            index: './src/framework/entry.browser.tsx',
          },
        },
      },
    },
  },
})
import { renderToReadableStream } from '@vitejs/plugin-rsc/rsc'

// the plugin assumes `rsc` entry having default export of request handler
export default async function handler(request: Request): Promise<Response> {
  // serialize React VDOM to RSC stream
  const root = (
    <html>
      <body>
        <h1>Test</h1>
      </body>
    </html>
  )
  const rscStream = renderToReadableStream(root)

  // respond direct RSC stream request based on framework's convention
  if (request.url.endsWith('.rsc')) {
    return new Response(rscStream, {
      headers: {
        'Content-type': 'text/x-component;charset=utf-8',
      },
    })
  }

  // delegate to SSR environment for html rendering
  // `loadModule` is a helper API provided by the plugin for multi environment interaction.
  const ssrEntry = await import.meta.viteRsc.loadModule<
    typeof import('./entry.ssr.tsx')
  >('ssr', 'index')
  const htmlStream = await ssrEntry.handleSsr(rscStream)

  // respond html
  return new Response(htmlStream, {
    headers: {
      'Content-type': 'text/html',
    },
  })
}

// add `import.meta.hot.accept` to handle server module change efficiently
if (import.meta.hot) {
  import.meta.hot.accept()
}
import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr'
import { renderToReadableStream } from 'react-dom/server.edge'

export async function handleSsr(rscStream: ReadableStream) {
  // deserialize RSC stream back to React VDOM
  const root = await createFromReadableStream(rscStream)

  // helper API to allow referencing browser entry content from SSR environment
  const bootstrapScriptContent =
    await import.meta.viteRsc.loadBootstrapScriptContent('index')

  // render html (traditional SSR)
  const htmlStream = renderToReadableStream(root, {
    bootstrapScriptContent,
  })

  return htmlStream
}
import { createFromReadableStream } from '@vitejs/plugin-rsc/browser'
import { hydrateRoot } from 'react-dom/client'

async function main() {
  // fetch and deserialize RSC stream back to React VDOM
  const rscResponse = await fetch(window.location.href + '.rsc')
  const root = await createFromReadableStream(rscResponse.body)

  // hydration (traditional CSR)
  hydrateRoot(document, root)
}

main()

Environment helper API

The plugin provides an additional helper for multi environment interaction.

Available on rsc or ssr environment

import.meta.viteRsc.loadModule

  • Type: (environmentName: "ssr" | "rsc", entryName: string) => Promise<T>

This allows importing ssr environment module specified by environments.ssr.build.rollupOptions.input[entryName] inside rsc environment and vice versa.

During development, by default, this API assumes both rsc and ssr environments execute under the main Vite process. When enabling rsc({ loadModuleDevProxy: true }) plugin option, the loaded module is implemented as a proxy with fetch-based RPC to call in node environment on the main Vite process, which for example, allows rsc environment inside cloudflare workers to access ssr environment on the main Vite process. This proxy mechanism uses turbo-stream for serializing data types beyond JSON, with custom encoders/decoders to additionally support Request and Response instances.

During production build, this API will be rewritten into a static import of the specified entry of other environment build and the modules are executed inside the same runtime.

For example,

// ./entry.rsc.tsx
const ssrModule = await import.meta.viteRsc.loadModule("ssr", "index");
ssrModule.renderHTML(...);

// ./entry.ssr.tsx (with environments.ssr.build.rollupOptions.input.index = "./entry.ssr.tsx")
export function renderHTML(...) {}

Available on rsc environment

import.meta.viteRsc.loadCss

[!NOTE] The plugin automatically injects CSS for server components. See the CSS Support section for detailed information about automatic CSS injection.

  • Type: (importer?: string) => React.ReactNode

This allows collecting css which is imported through a current server module and injecting them inside server components.

import './test.css'
import dep from './dep.tsx'

export function ServerPage() {
  // this will include css assets for "test.css"
  // and any css transitively imported through "dep.tsx"
  return (
    <>
      {import.meta.viteRsc.loadCss()}
      ...
    </>
  )
}

When specifying loadCss(<id>), it will collect css through the server module resolved by <id>.

// virtual:my-framework-helper
export function Assets() {
  return <>
    {import.meta.viteRsc.loadCss("/routes/home.tsx")}
    {import.meta.viteRsc.loadCss("/routes/about.tsx")}
    {...}
  </>
}

// user-app.tsx
import { Assets } from "virtual:my-framework-helper";

export function UserApp() {
  return <html>
    <head>
      <Assets />
    </head>
    <body>...</body>
  </html>
}

Available on ssr environment

import.meta.viteRsc.loadBootstrapScriptContent("index")

This provides a raw js code to execute a browser entry file specified by environments.client.build.rollupOptions.input.index. This is intended to be used with React DOM SSR API, such as renderToReadableStream

import { renderToReadableStream } from 'react-dom/server.edge'

const bootstrapScriptContent =
  await import.meta.viteRsc.loadBootstrapScriptContent('index')
const htmlStream = await renderToReadableStream(reactNode, {
  bootstrapScriptContent,
})

Available on client environment

rsc:update event

This event is fired when server modules are updated, which can be used to trigger re-fetching and re-rendering of RSC components on browser.

import { createFromFetch } from '@vitejs/plugin-rsc/browser'

import.meta.hot.on('rsc:update', async () => {
  // re-fetch RSC stream
  const rscPayload = await createFromFetch(fetch(window.location.href + '.rsc'))
  // re-render ...
})

Plugin API

@vitejs/plugin-rsc

  • Type: rsc: (options?: RscPluginOptions) => Plugin[];
import rsc from '@vitejs/plugin-rsc'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    rsc({
      // this is only a shorthand of specifying each rollup input via
      // `environments[name].build.rollupOptions.input.index`
      entries: {
        rsc: '...',
        ssr: '...',
        client: '...',
      },

      // by default, the plugin sets up middleware
      // using `default` export of `rsc` environment `index` entry.
      // this behavior can be customized by `serverHandler` option.
      serverHandler: false,

      // the plugin provides build-time validation of 'server-only' and 'client-only' imports.
      // this is enabled by default. See the "server-only and client-only import" section below for details.
      validateImports: true,

      // by default, the plugin uses a build-time generated encryption key for
      // "use server" closure argument binding.
      // This can be overwritten by configuring `defineEncryptionKey` option,
      // for example, to obtain a key through environment variable during runtime.
      // cf. https://nextjs.org/docs/app/guides/data-security#overwriting-encryption-keys-advanced
      defineEncryptionKey: 'process.env.MY_ENCRYPTION_KEY',

      // when `loadModuleDevProxy: true`, `import.meta.viteRsc.loadModule` is implemented
      // through `fetch` based RPC, which allows, for example, rsc environment inside
      // cloudflare workers to communicate with node ssr environment on main Vite process.
      loadModuleDevProxy: true,

      // by default, `loadCss()` helper is injected based on certain heuristics.
      // if it breaks, it can be opt-out or selectively applied based on files.
      rscCssTransform: { filter: (id) => id.includes('/my-app/') },

      // see `RscPluginOptions` for full options ...
    }),
  ],
  // the same options can be also specified via top-level `rsc` property.
  // this allows other plugin to set options via `config` hook.
  rsc: {
    // ...
  },
})

RSC runtime (react-server-dom) API

@vitejs/plugin-rsc/rsc

This module re-exports RSC runtime API provided by react-server-dom/server.edge and react-server-dom/client.edge such as:

  • renderToReadableStream: RSC serialization (React VDOM -> RSC stream)
  • createFromReadableStream: RSC deserialization (RSC stream -> React VDOM). This is also available on rsc environment itself. For example, it allows saving serialized RSC and deserializing it for later use.
  • decodeAction/decodeReply/decodeFormState/loadServerAction/createTemporaryReferenceSet
  • encodeReply/createClientTemporaryReferenceSet

@vitejs/plugin-rsc/ssr

This module re-exports RSC runtime API provided by react-server-dom/client.edge

  • createFromReadableStream: RSC deserialization (RSC stream -> React VDOM)

@vitejs/plugin-rsc/browser

This module re-exports RSC runtime API provided by react-server-dom/client.browser

  • createFromReadableStream: RSC deserialization (RSC stream -> React VDOM)
  • createFromFetch: a robust way of createFromReadableStream((await fetch("...")).body)
  • encodeReply/setServerCallback: server function related...

Tips

CSS Support

The plugin automatically handles CSS code-splitting and injection for server components. This eliminates the need to manually call import.meta.viteRsc.loadCss() in most cases.

  1. Component Detection: The plugin automatically detects server components by looking for:

    • Function exports with capital letter names (e.g., export function Page() {})
    • Default exports that are functions with capital names (e.g., export default function Page() {})
    • Const exports assigned to functions with capital names (e.g., export const Page = () => {})
  2. CSS Import Detection: For detected components, the plugin checks if the module imports any CSS files (.css, .scss, .sass, etc.)

  3. Automatic Wrapping: When both conditions are met, the plugin wraps the component with a CSS injection wrapper:

// Before transformation
import './styles.css'

export function Page() {
  return <div>Hello</div>
}

// After transformation
import './styles.css'

export function Page() {
  return (
    <>
      {import.meta.viteRsc.loadCss()}
      <div>Hello</div>
    </>
  )
}

Using React Canary and Experimental versions

To use React's canary or experimental versions with @vitejs/plugin-rsc, you have two options:

Option 1: Use preview releases from pkg.pr.new

You can use preview releases that bundle specific React versions. See PR #524 for instructions on installing these preview packages.

Option 2: Install react-server-dom-webpack directly

By default, @vitejs/plugin-rsc includes a vendored version of react-server-dom-webpack. However, when react-server-dom-webpack is installed in your project's dependencies, the plugin will automatically use it instead. This allows you to:

  • Stay up-to-date with the latest React Server Components runtime without waiting for plugin updates
  • Use specific React versions (stable, canary, or experimental)

Simply install the version you need:

{
  "dependencies": {
    "react": "canary",
    "react-dom": "canary",
    "react-server-dom-webpack": "canary"
  }
}

Or for experimental:

{
  "dependencies": {
    "react": "experimental",
    "react-dom": "experimental",
    "react-server-dom-webpack": "experimental"
  }
}

Using @vitejs/plugin-rsc as a framework package's dependencies

By default, @vitejs/plugin-rsc is expected to be used as peerDependencies similar to react and react-dom. When @vitejs/plugin-rsc is not available at the project root (e.g., in node_modules/@vitejs/plugin-rsc), you will see warnings like:

Failed to resolve dependency: @vitejs/plugin-rsc/vendor/react-server-dom/client.browser, present in client 'optimizeDeps.include'

This can be fixed by updating optimizeDeps.include to reference @vitejs/plugin-rsc through your framework package. For example, you can add the following plugin:

// package name is "my-rsc-framework"
export default function myRscFrameworkPlugin() {
  return {
    name: 'my-rsc-framework:config',
    configEnvironment(_name, config) {
      if (config.optimizeDeps?.include) {
        config.optimizeDeps.include = config.optimizeDeps.include.map(
          (entry) => {
            if (entry.startsWith('@vitejs/plugin-rsc')) {
              entry = `my-rsc-framework > ${entry}`
            }
            return entry
          },
        )
      }
    },
  }
}

Typescript

Types for global API are defined in @vitejs/plugin-rsc/types. For example, you can add it to tsconfig.json to have types for import.meta.viteRsc APIs:

{
  "compilerOptions": {
    "types": ["vite/client", "@vitejs/plugin-rsc/types"]
  }
}
import.meta.viteRsc.loadModule
//                  ^^^^^^^^^^
// <T>(environmentName: string, entryName: string) => Promise<T>

See also Vite documentation for vite/client types.

server-only and client-only import

You can use the server-only import to prevent accidentally importing server-only code into client bundles, which can expose sensitive server code in public static assets. For example, the plugin will show an error 'server-only' cannot be imported in client build for the following code:

  • server-utils.js
import 'server-only'

export async function getData() {
  const res = await fetch('https://internal-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
  return res.json()
}
  • client.js
'use client'
import { getData } from './server-utils.js' // ❌ 'server-only' cannot be imported in client build
...

Similarly, the client-only import ensures browser-specific code isn't accidentally imported into server environments. For example, the plugin will show an error 'client-only' cannot be imported in server build for the following code:

  • client-utils.js
import 'client-only'

export function getStorage(key) {
  // This uses browser-only APIs
  return window.localStorage.getItem(key)
}
  • server.js
import { getStorage } from './client-utils.js' // ❌ 'client-only' cannot be imported in server build

export function ServerComponent() {
  const data = getStorage("settings")
  ...
}

Note that while there are official npm packages server-only and client-only created by React team, they don't need to be installed. The plugin internally overrides these imports and surfaces their runtime errors as build-time errors.

This build-time validation is enabled by default and can be disabled by setting validateImports: false in the plugin options.

Credits

This project builds on fundamental techniques and insights from pioneering Vite RSC implementations. Additionally, Parcel and React Router's work on standardizing the RSC bundler/app responsibility has guided this plugin's API design: