Repository navigation
lazy() moduleUrl with a leading slash never resolves: dev emits //src/…, the build manifest lookup misses #390
Description
Summary
A lazy() call whose hand-written moduleUrl has a leading slash ("/src/Page.tsx") never resolves. The plugin's key format is project-relative (src/Page.tsx), and nothing normalizes the slash:
- Dev: the generated URL becomes protocol-relative (
//src/Page.tsx). The preload fetcheshttp://src/Page.tsxand fails, andhydrate()falls back to a client render. - Build: the client manifest has no
/src/Page.tsxkey. The server logsAsset manifest returned no client assets for module "/src/Page.tsx"and ships no root module map. On the client, hydration throwslazy() module "/src/Page.tsx" … was not preloaded before hydration, and reactivity halts.
This was split out of solidjs/solid#3338, where it reproduced the reporter's "page looks hydrated but every signal is dead" symptom.
Reproduction
From the reproduction on solidjs/solid#3338: the solid-v2/fullstack template (solid-js / @solidjs/web 2.0.0-rc.6, @solidjs/router 2.0.0-next.21, @solidjs/vite-plugin 3.0.0-next.39), with the <Loading> removed and a route component declared as:
const Page = lazy(() => import("./Page"), undefined, "/src/Page.tsx");- Production build: the server logs
Asset manifest returned no client assets for module "/src/Page.tsx". The client throwslazy() module "/src/Page.tsx" (hydration id "…") was not preloaded before hydrationfrom the router outlet. Reactivity halts (REACTIVITY_HALTED). - Dev: the root module map is serialized as
//src/Page.tsx. The preload requestshttp://src/Page.tsx, fails, and the fallback client render dies with an unhandledHydration Mismatch … key: undefined.
The same key without the slash ("src/Page.tsx") hydrates and stays reactive in both modes. See also the first diagnosis comment.
Expected behavior
A leading / on a moduleUrl key names the same module as the project-relative key. Either:
- it's normalized (
/src/Page.tsx→src/Page.tsx) before the dev URL is built and before the build-manifest lookup, so both modes resolve the module; or - if a leading slash is meant to be invalid, it's rejected with a clear error naming the expected project-relative format, not a silent miss.
Where it likely lives
A hand-written third argument bypasses the compiler's lazy() pass (transformLazy only rewrites one- and two-argument calls). So the user's string reaches the resolvers as-is.
- Dev:
devModuleUrlinsrc/dev-manifest.tsand its mirrormoduleUrl()in the generateddevManifestCodeinsrc/index.ts. Both buildbase + "/" + key, which gives//src/Page.tsxfor a slash-prefixed key. The comment ondevManifestCodesays to keep the two in sync. - Build: the
virtual:solid-manifestloadhook insrc/index.tsexports the raw Vite manifest (viastampClientEntry). The key lookup itself ismanifest[moduleUrl]in@solidjs/web'sresolveAssets(packages/web/src/server.tsin solidjs/solid). The production half could be fixed on either side: the plugin normalizing keys in what it exports, or the runtime normalizing before lookup. That's worth deciding before a fix, so dev and build agree.
Related but distinct: #263 (Windows separators in generated keys), #298 (dev base and root-external keys), and #299 (query strings). None of them cover a leading slash on a hand-written key.
Filed on behalf of @ryansolid. — Claude via Cursor
Activity
ryansolid commentedon Oct 8, 2026
Fixed by #399 (merged to
nextin 9aa91f1): slash-prefixedlazy()moduleUrlkeys now resolve in dev and in the build manifest, so a leading slash no longer produces//src/…or misses the manifest lookup. Will ship in the nextnextrelease.