Repository navigation
[plugin-rsc] Root identity probe triggers mid-request dependency optimization #1473
Description
Bug
The first cold request to a next-intl app under @cloudflare/vite-plugin returns 500 with Invalid hook call / Cannot read properties of null (reading 'use'). The next request succeeds.
The trigger is the second resolution in rsc:virtual-client-package. The actual import resolves from inside node_modules; the identity check resolves again from <root>/index.html. For root-resolvable transitive dependencies, that second lookup calls Vite's registerMissingImport, causing optimization and a reload during rendering. Here it discovers @formatjs/intl-localematcher, negotiator, @formatjs/fast-memoize, and intl-messageformat.
Reproduction
Application files and configuration are in the downstream report. Use its npm installation, next-intl navigation Link, client translation component, and locale middleware/proxy.
rm -rf node_modules/.vite .vinext
npm run dev # port 3101From another terminal, without opening the app first:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3101/en # 500
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3101/en # 200Expected: the first request succeeds; a package-identity check does not schedule dependency optimization.
Tested package patch
This is the exact patch tested against published plugin-rsc 0.5.35, with published vinext unchanged. Save as patches/@vitejs+plugin-rsc+0.5.35.patch for patch-package, or apply with git apply from the app root.
diff --git a/node_modules/@vitejs/plugin-rsc/dist/plugin-CLvIS10V.js b/node_modules/@vitejs/plugin-rsc/dist/plugin-CLvIS10V.js
--- a/node_modules/@vitejs/plugin-rsc/dist/plugin-CLvIS10V.js
+++ b/node_modules/@vitejs/plugin-rsc/dist/plugin-CLvIS10V.js
@@ -1511,17 +1511,26 @@
resolveId: {
order: "pre",
async handler(source, importer, options) {
+ if (options.custom?.["rsc:package-identity-probe"]) return;
if (this.environment.name === serverEnvironmentName && bareImportRE.test(source) && !(source === "client-only" || source === "server-only")) {
const resolved = await this.resolve(source, importer, options);
if (resolved && resolved.id.includes("/node_modules/")) {
const rootImporter = path.join(this.environment.config.root, "index.html");
- let resolvedAtRoot;
+ const scanRoot = this.environment.mode === "dev" && !this.environment.config.optimizeDeps.noDiscovery;
+ let resolvedAtRoot;
try {
- resolvedAtRoot = await this.resolve(source, rootImporter, options);
+ resolvedAtRoot = scanRoot
+ ? await this.environment.pluginContainer.resolveId(source, rootImporter, {
+ ...options,
+ scan: true,
+ custom: { ...options.custom, "rsc:package-identity-probe": true },
+ })
+ : await this.resolve(source, rootImporter, options);
} catch {
return;
}
- if (!resolvedAtRoot || resolvedAtRoot.id !== resolved.id) return;
+ if (!resolvedAtRoot || (resolvedAtRoot.id !== resolved.id &&
+ (!scanRoot || resolvedAtRoot.id !== resolved.id.replace(/\?v=[a-f\d]+$/, "")))) return;
packageSources.set(resolved.id, source);
return resolved;
}The patch keeps normal import resolution and the root-identity guard, bypasses recursive probes, and tolerates only Vite's terminal version query. Build mode and explicit noDiscovery: true retain the existing path.
Proposed upstream fix: give this diagnostic lookup a supported way to resolve without discovering optimized dependencies. The prototype uses the internal container scan option; passing it to this.resolve() alone does not work because Vite inherits the caller's scan state.
Validation
Four clean starts failed before the patch; all four succeeded after changing plugin-rsc alone. Restoring the package restored the failure. Tested Cloudflare compatibility dates: 2026-09-27 and 2026-02-12. Eight focused resolver checks and a production build also passed; this does not establish browser-hydration or full upstream-suite compatibility.
Versions: macOS arm64, Node 24.21.0, npm, Vite 8.3.1, plugin-rsc 0.5.35, Cloudflare Vite plugin 1.61.0, next-intl 4.14.7, React 19.3.0, vinext 1.0.0-beta.13.
Related: #1273. This identifies a specific trigger for mid-request optimization.
Activity
james-elicx commentedon Sep 27, 2026
I think it would be nice for the probe to work without triggering re-optimisation. Is that something you're open to exploring @hi-ogawa ?
hi-ogawa commentedon Sep 28, 2026
I have a same sentiment that
this.resolvecan be invasive for server deps optimization. We added 2nd rootthis.resolvethere recently for some edge case and wondering if that's what triggers error, or was the 1st one already the culprit? Either way, I'm fine with adjusting some condition around there specifically fornoDiscovery = falsecase. I cannot tell yet if the above patch is reasonable a fix for that.james-elicx commentedon Sep 30, 2026
I think it is the second one that triggers it. cloudflare/vinext#3510 was what led me to making this issue.
When my agent was debugging, it found the following:
So it looks like it resolves the actual module, then resolves again from index.html, registers it for optimisation, and reloads.
So I think if the probe wasn't happening, this wouldn't be an issue?