RSC: createServerFn handlers cannot use react-dom/server (blocks @react-email/render, etc.)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.1k
- Forks
- 1.9k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 143
Description
Summary
When rsc: { enabled: true } is passed to the tanstackStart Vite plugin, createServerFn handlers are resolved with the react-server condition. This makes it impossible to use any library that depends on react-dom/server — including @react-email/render — inside a server function, even though the code only ever runs on the server.
Any import of react-dom/server (or react-dom/server.edge) resolves to react-dom/server.react-server.js, which simply throws:
react-dom/server is not supported in React Server Components.
By contrast, a Nitro/Elysia route handler defined in a server file route (e.g. routes/api.\$.ts with server.handlers) runs without the react-server condition and the same code works fine. This suggests the constraint is specific to how createServerFn is bundled when RSC is enabled, not a fundamental limitation.
Reproduction
Minimal repro: a createServerFn handler that dynamically imports @react-email/render and renders any React Email template.
```ts
// src/lib/nodemailer.ts
const sendEmail = async ({ react }: { react: JSX.Element }) => {
const { render } = await import('@react-email/render');
const html = await render(react); // throws in RSC server function
// ...
};
```
```ts
// src/routes/_auth/register.tsx
export const handleForm = createServerFn({ method: 'POST' })
.inputValidator(registerSchema)
.handler(async ({ data }) => {
// ...create user...
await sendEmail({ react: VerificationEmail({ name, url }) }); // ❌
});
```
```ts
// vite.config.ts
tanstackStart({
rsc: { enabled: true },
}),
rsc(),
```
What works
Putting the same render call inside an Elysia handler exposed via a server file route succeeds — same package, same component, same process:
```ts
// src/routes/api.$.ts
const app = new Elysia({ prefix: '/api' })
.get('/test-email-render', async () => {
const html = await render(VerificationEmail({ name: 'Test', url: 'https://example.com' }));
return { ok: true, length: html.length }; // ✅ { ok: true, length: 5690 }
});
```
What was tried
- Dynamic import of
@react-email/render— bundler still resolves it viareact-servercondition at request time. - Aliasing
@react-email/renderto itsdist/edge/index.mjs(which usesreact-dom/server.edge) — same error, becausereact-dom's ownexportsmap mapsreact-serverfor both./serverand./server.edgeto the throwing stub. - Pinning Vite
resolve.conditionsto preferworkerd— doesn't reach into the RSC environment.
Expected behavior
createServerFn handlers should either:
- Run in a server environment that does not apply the
react-servercondition (matching how server file route handlers work today), or - Provide a documented way to opt a server function out of the RSC bundle when it needs
react-dom/server(e.g. for transactional email rendering, PDF generation, etc.).
Email rendering with @react-email/render is a common server-side need and currently has no workable path when RSC is enabled, other than introducing an internal HTTP hop to a Nitro/Elysia route handler.
Environment
- `@tanstack/react-start`: 1.168.14
- `@vitejs/plugin-rsc`: 0.5.26
- `react` / `react-dom`: 19.2.6
- `vite`: 8.0.14
- `@react-email/render`: 2.0.8
- Runtime: Bun (Nitro preset: `bun`)
- OS: macOS (Darwin 25.5.0)
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 minimal createServerFn reproduction in src/lib/nodemailer.ts and the RSC-enabled tanstackStart configuration in vite.config.ts, then compare it with the working handler in src/routes/api.$.ts. Reproduce the react-server resolution failure and trace how the two handler types are bundled; done means a server function can render with @react-email/render without the throwing react-dom/server stub.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun, react, typescript, vite
- Domain
- backend, backend-api-design, build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100