Related plugins
Describe the bug
@vitejs/plugin-rsc emits two flavors of "client reference proxy" virtual modules in dev: client-in-server-package-proxy (when a server-env file imports a package file via a relative or absolute path) and client-package-proxy (when a server-env file imports via a bare specifier). Both load handlers crash for us under the @cloudflare/sandbox SDK invocation path on first paint of the page, with two distinct failure modes depending on which one is hit. In normal vite dev, optimizeDeps rewrites the imports and masks both — under cf-sandbox SDK, the cache misses and the bugs surface.
Filed here at the maintainer's suggestion (cloudflare/vinext#1031 comment).
Bug 1 — client-in-server-package-proxy embeds absolute filesystem paths verbatim
Source location
packages/plugin-rsc/src/.../plugins.ts (bundled at dist/plugin-*.js). The branching logic that picks which proxy URL to emit:
const packageSource = packageSources.get(id);
if (!packageSource && this.environment.mode === "dev" && id.includes("/node_modules/")) {
// CASE A — file is under node_modules but its import wasn't a bare specifier
importId = `/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/${encodeURIComponent(id)}`;
} else if (packageSource) {
if (this.environment.mode === "dev") {
// CASE B — file's import WAS a bare specifier (recorded by the resolveId hook)
importId = `/@id/__x00__virtual:vite-rsc/client-package-proxy/${packageSource}`;
}
// …
} else if (this.environment.mode === "dev") {
// CASE C — file is in user code (outside node_modules)
importId = normalizeViteImportAnalysisUrl(manager.server.environments[browserEnvironmentName], id);
}
CASE A's load handler:
id = decodeURIComponent(id.slice(49));
return `
export * from ${JSON.stringify(id)};
import * as __all__ from ${JSON.stringify(id)};
export default __all__.default;
`;
The decoded id is the absolute filesystem path of the package file. Embedded as a string literal in the module body.
Symptom
[plugin:vite:import-analysis] Failed to resolve import
"/workspace/node_modules/<pkg>/dist/shims/X.js"
from "virtual:vite-rsc/client-in-server-package-proxy/..."
Root cause
Vite treats string-literal imports beginning with / as URL-relative-to-server-root, not filesystem-absolute. With root = /workspace, the import resolution looks up <root>/workspace/foo = /workspace/workspace/foo and fails. Triggers for any project root that is a path prefix of the imported file.
Suggested fix
Three angles, in increasing scope:
- Wrap the embedded path in
/@fs/ form. Vite already treats /@fs/<absolute-path> as filesystem-absolute and skips URL-relative interpretation. One-line change.
- Resolve absolute path → bare specifier in CASE A before deciding URL form. Walk to nearest
package.json, derive <name>/<subpath>, route through CASE B's existing path with that synthetic packageSource. Fixes the underlying mismatch.
- Run
normalizeViteImportAnalysisUrl in CASE A (the same helper CASE C uses). Unifies the three branches.
Option 1 is the lowest-risk one-liner. Option 2 is more correct.
Bug 2 — client-package-proxy crashes when clientReferenceMetaMap lookup misses
Source location
client-package-proxy's load handler (sibling to CASE B above):
load: {
filter: { id: prefixRegex("\0virtual:vite-rsc/client-package-proxy/") },
async handler(id) {
if (id.startsWith("\0virtual:vite-rsc/client-package-proxy/")) {
assert(this.environment.mode === "dev");
const source = id.slice(39);
return `export {${
Object.values(manager.clientReferenceMetaMap).find((v) => v.packageSource === source).exportNames.join(",")
}} from ${JSON.stringify(source)};\n`;
}
}
}
The find(...) returns undefined when the rsc env hasn't yet transformed the package's file — the lookup runs before clientReferenceMetaMap is populated for this packageSource.
Symptom
TypeError: Cannot read properties of undefined (reading 'exportNames')
at LoadPluginContext.handler (.../@vitejs/plugin-rsc/dist/plugin-*.js)
at EnvironmentPluginContainer.load (.../vite/dist/node/chunks/node.js)
Fires on first paint when the proxy URL is fetched before the corresponding source file has been transformed in the rsc env.
Root cause
Race between two passes in plugin-rsc:
- Pass A (server env transforms a file with
'use client' boundary): populates clientReferenceMetaMap[id] with packageSource + exportNames.
- Pass B (proxy URL fetched, load handler runs): reads
clientReferenceMetaMap to resolve which exports to forward.
Pass B can land before Pass A under certain runtimes (we observe this consistently under @cloudflare/sandbox SDK's containerFetch invocation path). Tracked previously at cloudflare/vinext#1008 — claim of fix in vinext 0.0.46 doesn't hold in our setup.
Suggested fix
Make the load handler defensive: when the metadata isn't there yet, fall back to wildcard re-export instead of throwing.
const meta = Object.values(manager.clientReferenceMetaMap).find((v) => v.packageSource === source);
return meta
? `export {${meta.exportNames.join(",")}} from ${JSON.stringify(source)};\n`
: `export * from ${JSON.stringify(source)};\n`;
Wildcard loses the explicit-named-export signal but doesn't crash; subsequent requests after Pass A completes will use the precise form. Cleaner alternative: have the load handler await Pass A's completion (e.g. by triggering the source file's transform on demand if metadata is missing).
How they relate
CASE A and the bare-specifier path (CASE B → client-package-proxy) are alternative routes the same 'use client' boundary can take. Whether a package file ends up in CASE A or CASE B comes down to whether the FIRST import that reached it was a bare specifier or a relative path. Both paths have failure modes; consumers (vinext, ourselves) currently work around CASE A by rewriting all relative shim imports to bare specifiers, and around CASE B by patching the load handler to fall back on miss.
Both fixes belong in plugin-rsc rather than in every consuming package.
Reproduce
CodeSandbox / StackBlitz can't host this — Docker-based runtime is required (bug only surfaces under cf-sandbox SDK's containerFetch invocation; vite's optimizeDeps cache masks both bugs in any direct dev-server run). Standalone Docker repro is the smallest form available.
Repro repo: https://github.com/eashish93/vite-plugin-rsc-bug (also attached as zip at cloudflare/vinext#1031).
git clone https://github.com/eashish93/vite-plugin-rsc-bug
cd vinext-shim-bug-repro/main
bun install
bun run dev
# open http://localhost:5200/ → click "Start sandbox"
Without any workaround applied: Bug 1 fires on first paint. With Bug 1 patched (rewrite relative imports to bare specifiers): Bug 2 fires instead. Both need to be addressed for the page to render under cf-sandbox SDK.
Versions
Workarounds currently in production
- Bug 1: post-install rewrite of
../shims/X.js → vinext/shims/X in node_modules/vinext/dist/**/*.js.
- Bug 2: post-install patch of
node_modules/@vitejs/plugin-rsc/dist/plugin-*.js to make the client-package-proxy load handler fall back to export * when clientReferenceMetaMap lookup misses.
Both scripts are small, idempotent, and re-applied on every bun install via postinstall.
Reproduction
https://github.com/eashish93/vite-plugin-rsc-bug
Steps to reproduce
No response
System Info
## System
- **OS:** macOS 26.3.1 (arm64 / Apple Silicon)
- **Runtime:** Node v25.2.1, Bun 1.3.11
- **Container runtime:** Docker 29.4.0 via OrbStack 2.1.1
- **Browser:** _(your browser + version — I don't have it)_
## Packages
| | |
|---|---|
| `vite` | 8.0.10 |
| `@vitejs/plugin-rsc` | 0.5.25 |
| `@vitejs/plugin-react` | 6.0.1 |
| `@cloudflare/vite-plugin` | 1.35.0 |
| `@cloudflare/sandbox` | 0.9.2 |
| `vinext` | 0.0.46 |
| `wrangler` | 4.87.0 |
Used Package Manager
bun
Logs
No response
Validations
Related plugins
plugin-react
plugin-react-swc
plugin-rsc
Describe the bug
@vitejs/plugin-rscemits two flavors of "client reference proxy" virtual modules in dev:client-in-server-package-proxy(when a server-env file imports a package file via a relative or absolute path) andclient-package-proxy(when a server-env file imports via a bare specifier). Both load handlers crash for us under the@cloudflare/sandboxSDK invocation path on first paint of the page, with two distinct failure modes depending on which one is hit. In normalvite dev,optimizeDepsrewrites the imports and masks both — under cf-sandbox SDK, the cache misses and the bugs surface.Filed here at the maintainer's suggestion (cloudflare/vinext#1031 comment).
Bug 1 —
client-in-server-package-proxyembeds absolute filesystem paths verbatimSource location
packages/plugin-rsc/src/.../plugins.ts(bundled atdist/plugin-*.js). The branching logic that picks which proxy URL to emit:CASE A's load handler:
The decoded
idis the absolute filesystem path of the package file. Embedded as a string literal in the module body.Symptom
Root cause
Vite treats string-literal imports beginning with
/as URL-relative-to-server-root, not filesystem-absolute. Withroot = /workspace, the import resolution looks up<root>/workspace/foo=/workspace/workspace/fooand fails. Triggers for any project root that is a path prefix of the imported file.Suggested fix
Three angles, in increasing scope:
/@fs/form. Vite already treats/@fs/<absolute-path>as filesystem-absolute and skips URL-relative interpretation. One-line change.package.json, derive<name>/<subpath>, route through CASE B's existing path with that syntheticpackageSource. Fixes the underlying mismatch.normalizeViteImportAnalysisUrlin CASE A (the same helper CASE C uses). Unifies the three branches.Option 1 is the lowest-risk one-liner. Option 2 is more correct.
Bug 2 —
client-package-proxycrashes whenclientReferenceMetaMaplookup missesSource location
client-package-proxy's load handler (sibling to CASE B above):The
find(...)returnsundefinedwhen the rsc env hasn't yet transformed the package's file — the lookup runs beforeclientReferenceMetaMapis populated for thispackageSource.Symptom
Fires on first paint when the proxy URL is fetched before the corresponding source file has been transformed in the rsc env.
Root cause
Race between two passes in plugin-rsc:
'use client'boundary): populatesclientReferenceMetaMap[id]withpackageSource+exportNames.clientReferenceMetaMapto resolve which exports to forward.Pass B can land before Pass A under certain runtimes (we observe this consistently under
@cloudflare/sandboxSDK'scontainerFetchinvocation path). Tracked previously at cloudflare/vinext#1008 — claim of fix in vinext 0.0.46 doesn't hold in our setup.Suggested fix
Make the load handler defensive: when the metadata isn't there yet, fall back to wildcard re-export instead of throwing.
Wildcard loses the explicit-named-export signal but doesn't crash; subsequent requests after Pass A completes will use the precise form. Cleaner alternative: have the load handler
awaitPass A's completion (e.g. by triggering the source file's transform on demand if metadata is missing).How they relate
CASE A and the bare-specifier path (CASE B →
client-package-proxy) are alternative routes the same'use client'boundary can take. Whether a package file ends up in CASE A or CASE B comes down to whether the FIRST import that reached it was a bare specifier or a relative path. Both paths have failure modes; consumers (vinext, ourselves) currently work around CASE A by rewriting all relative shim imports to bare specifiers, and around CASE B by patching the load handler to fall back on miss.Both fixes belong in plugin-rsc rather than in every consuming package.
Reproduce
CodeSandbox / StackBlitz can't host this — Docker-based runtime is required (bug only surfaces under cf-sandbox SDK's
containerFetchinvocation; vite'soptimizeDepscache masks both bugs in any direct dev-server run). Standalone Docker repro is the smallest form available.Repro repo: https://github.com/eashish93/vite-plugin-rsc-bug (also attached as zip at cloudflare/vinext#1031).
Without any workaround applied: Bug 1 fires on first paint. With Bug 1 patched (rewrite relative imports to bare specifiers): Bug 2 fires instead. Both need to be addressed for the page to render under cf-sandbox SDK.
Versions
@vitejs/plugin-rsc^0.5.25vite8.0.10@cloudflare/vite-plugin^1.35.0@cloudflare/sandbox^0.9.2vinext0.0.46(provides the failing relative-imports pattern; hooks inside deeply nested objects always cause state reset #1006 attempted to fix it on their side but twodist/server/*.jsfiles regressed, and fix(deps): update all non-major dependencies #1008 attempted to fix the metadata race in their plugin without resolving the underlying issue here)Workarounds currently in production
../shims/X.js→vinext/shims/Xinnode_modules/vinext/dist/**/*.js.node_modules/@vitejs/plugin-rsc/dist/plugin-*.jsto make theclient-package-proxyload handler fall back toexport *whenclientReferenceMetaMaplookup misses.Both scripts are small, idempotent, and re-applied on every
bun installviapostinstall.Reproduction
https://github.com/eashish93/vite-plugin-rsc-bug
Steps to reproduce
No response
System Info
Used Package Manager
bun
Logs
No response
Validations