cloudflare / cloudflare/vinext

Discussion: route handlers need a dedicated Vite env to fix #1237 + #1246

Open
#1,891 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
8.8k
Forks
406
Avg merge
2d 6h
Merged PRs (30d)
120

Description

## Background

This is a follow-up discussion to #1246 (and its parent #1237).

#1237 reports that App Route handlers running in the RSC env break `react-dom/server.edge` because the `react-server` export condition is applied transitively to `require('react')` inside `react-dom-server.edge.development.js`.

#1246 documents the full architectural conflict — the only known mitigation (loading route handlers in the SSR env) fixes #1237 cleanly but breaks `'use server'` calls from inside route handlers, because plugin-rsc's SSR-env transform replaces the function body with a server-reference stub whose `callServer` is `null` in that env.

#1246's option 3 ("add a dedicated `route-handler` Vite env") feels like the architecturally correct answer. I'd like to align with the maintainers before pushing more code.

## What's already been tried

I had a branch (`fix/route-handler-lazy-loader` in [digwis/vinext](https://github.com/digwis/vinext/tree/fix/route-handler-lazy-loader), now closed via #1890) that implemented the partial SSR-env fix from #1246's Path B:

- `app-ssr-entry.ts` — accept `AppRoute[]`, statically import every `route.routePath` into a `__appRouteHandlers` map, expose `loadAppRouteHandler(pattern)`.
- `app-rsc-manifest.ts` — drop the eager `getLazyLoaderVar(route.routePath)` registration, emit a thunk per route that calls `import.meta.viteRsc.loadModule("ssr", "index").loadAppRouteHandler(pattern)`.
- `index.ts` — pass resolved routes into `generateSsrEntry`.

Verified end-to-end with `@react-email/render` sending via Cloudflare Email Sending — works for the #1237 case, breaks for the `'use server'` case (exactly as #1246 predicts).

Local verification: 9 route-handler unit test files (86/86) pass; `tsc --noEmit` is clean.

## What I think option 3 looks like

A new Vite environment, e.g. `route-handler`, configured roughly as:

- `resolve.conditions`: `['edge-light', 'workerd', 'browser', 'import', 'module', 'default']` (no `react-server`)
- `optimizeDeps.exclude`: route handler entrypoints that statically import `react-dom/server.edge`
- **Not** transformed by plugin-rsc's SSR `'use server'` transform — so `'use server'` functions imported by route handlers keep their real body and can be called locally. (Server references, where the *client* needs to invoke a server function, stay on the existing path through plugin-rsc.)

The flow becomes:

1. `app-rsc-manifest.ts` continues to know patterns; route module loading is delegated as in the partial PR.
2. RSC dispatcher (`app-rsc-entry.ts`'s `dispatchMatchedRouteHandler`) calls `import.meta.viteRsc.loadModule("route-handler", "index")` to get the handler module.
3. `route-handler/index.ts` is a new virtual module that statically imports every `route.routePath` and exports a `loadAppRouteHandler(pattern)` resolver, mirroring the SSR-entry implementation.

## Open questions for maintainers

1. **Is "dedicated route-handler env" the direction you want to go**, or is there a simpler one-env fix on the plugin-rsc side (option 2 in #1246) you'd prefer to start with?
2. **`'use server'` semantics inside route handlers**: do you want them to behave as real function calls (current Next.js parity — server-only code path, no client bridge), or is the SSR-env stub acceptable as a documented limitation?
3. **Plugin-rsc coordination**: option 3 likely needs a small plugin-rsc change too (to skip its `'use server'` transform for files in the new env). Is a coordinated PR across both repos acceptable, or should the vinext side land first behind a flag?
4. **Should the work happen in this repo, or a feature branch first** for an AI review pass before opening a real PR? (CONTRIBUTING.md mentions BigBonk is requested by maintainers with write access.)

## What I can take on

Happy to drive the implementation if maintainers are aligned on option 3 (or whichever direction you prefer). The partial fix in `digwis/vinext@fix/route-handler-lazy-loader` is a clean starting point for the SSR-entry / manifest plumbing; the new `route-handler` env and the plugin-rsc coordination would be the additional work.

Contributor guide

Open the contributing guide

Research direction

Start with #1246, then inspect app-ssr-entry.ts, app-rsc-manifest.ts, index.ts, and app-rsc-entry.ts to understand the existing route-loading flow. Compare the proposed route-handler/index.ts entry point with the nine route-handler unit test files, and check plugin-rsc's 'use server' transform behavior. Done means maintainers select an architecture and the chosen approach supports both react-dom/server.edge and 'use server' route-handler cases, with tests and tsc --noEmit passing.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript, vite
Domain
backend, build-system
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.