vitejs / vitejs/vite-plugin-react
plugin-rsc: a directive that pins a module to the ssr environment ("use ssr"), built on import.meta.viteRsc.import
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1.2k
- Forks
- 269
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 19
Description
Proposal: a directive that pins a module to another environment ("use ssr"), built on import.meta.viteRsc.import
Follow-up to #1263, which was closed pointing at import.meta.viteRsc.import(..., { environment: 'ssr' }). That helper is the right mechanism, and this proposes the ergonomic layer on top of it, offered as a PR if the shape is welcome.
The pain, restated
Any library that renders React to HTML (@react-email/render, renderToString, PDF and Markdown renderers that use React) fails when called from a server function or an RSC route handler:
Error: react-dom/server is not supported in React Server Components.
As designed by React, and the cure is to run that code in the ssr environment. Today that means every framework on plugin-rsc (TanStack Start, vinext, ours) documents the same paragraph telling users to split a file and write:
const { renderEmail } = await import.meta.viteRsc.import<typeof import('./email-renderer')>(
'./email-renderer',
{ environment: 'ssr' },
)
It works, but it is the framework's plumbing leaking into an app: an env-specific global, a generic parameter that repeats the specifier, and an await at the call site because the import is now a promise.
Proposal
A module can declare which environment it belongs to, the way "use client" does:
// src/lib/email/render.tsx
"use ssr";
import { render } from '@react-email/render'
import { OtpEmail } from './otp-email'
export async function renderOtpEmail(code: string) {
return render(<OtpEmail code={code} />)
}
Everything else imports it normally. In the rsc environment the plugin replaces the module with proxies of its exports that call across:
const mod = import.meta.viteRsc.import('./render.tsx', { environment: 'ssr' })
export const renderOtpEmail = async (...args) => (await mod).renderOtpEmail(...args)
Dev and build both already work through the existing rsc:import-environment handling; the transform only needs to run ahead of it (enforce: 'pre', applyToEnvironment: rsc), so the module's own imports (react-dom/server among them) are never resolved in rsc at all.
Rules, enforced at transform time with the export named:
- exports are async functions (the call crosses environments; the answer is a promise)
- a value export, a sync function, a class,
export { }/export *is an error - pass data across, not elements: an element created in
rsccarries that side'sreact
Shape questions for the maintainers
- Name and generality.
"use ssr"reads well but hardcodes the conventional environment name. A generic form, e.g."use environment: ssr"or a plugin optiondirectives: { 'use ssr': 'ssr' }, keeps it framework-agnostic. Happy with either. - React's directive space.
"use *"directives are React's namespace; this one is bundler-only and never reaches React. Worth a note in the README rather than a blocker, I think. - A friendlier failure for the direct import. Independently of the directive: resolving
react-dom/server*inrscto a module that throws a message naming the app file that imported it and the fix. Cheap, and it turns the most-asked question into a message.
Prior art
Implemented and shipping in rsc-kit (packages/core/src/useSsr.ts, ~100 lines plus tests, including a fixture that renders a component with useState through renderToStaticMarkup from a server action): https://github.com/rsc-kit/rsc-kit/pull/96. I will adapt it to whatever shape you prefer and open the PR.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing rsc:import-environment handling described in the issue and compare the prior art in packages/core/src/useSsr.ts from rsc-kit. Review follow-up issue #1263 and the proposal's directive rules and shape questions. Done means an agreed directive design with matching dev/build behavior and tests covering valid async exports and rejected export forms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- build-system, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100