This document summarizes the issues we’re seeing when running Redwood SDK (rwsdk) with Vite dev + workerd SSR in a project using LiveStore. It groups the problems by area, provides concrete evidence (errors, file references), root‑cause analysis, and upstream solution ideas for the Redwood team.
Scope: Vite 7.x, @cloudflare/vite-plugin, [email protected], dev SSR runner targeting a worker-like (workerd) environment.
- Vite dep optimization fails when
lightningcss’snode/index.jstries torequire('../pkg'). - Observed when rwsdk imports
viteat runtime (SSR worker), which pulls in Vite CSS internals thatimport('lightningcss').
- We applied a local pnpm patch to rwsdk to avoid importing
vitein SSR runtime:patches/[email protected]: removesimport { normalizePath as normalizePathSeparators } from 'vite'and inlines a simple normalizer indist/lib/normalizeModulePath.mjs.
- Vite config hardening in example: switch to PostCSS transformer, exclude
lightningcssfrom optimization, aliaslightningcssto a stub, and stub@parcel/watcherto avoidunenv child_processerrors.
Files:
- patches/[email protected]:1
- examples/web-todomvc-redwood/vite.config.mts:1
- examples/web-todomvc-redwood/src/lightningcss-stub.ts:1
- examples/web-todomvc-redwood/src/parcel-watcher-stub.ts:1
- rwsdk SSR runtime helper imported
vite, which caused Vite to include its node CSS pipeline in the optimizer; that code dynamically importslightningcsswhich expects a nativepkgvia CommonJSrequire('../pkg'), breaking in the dev SSR worker runtime.
- Avoid importing
vitein SSR runtime helpers. Extract path normalization or other utilities into internal code that does not depend on Vite. - Ensure rwsdk runtime bundles do not import node-only transform stacks (CSS, lightningcss) in SSR worker.
- SSR snapshot generation fails in the dev SSR worker due to WASM restrictions and URL resolution issues.
Errors observed:
- “Invalid URL: /@fs/.../wa-sqlite.wasm” when code attempts to fetch a bare
/@fspath in SSR worker. - “WebAssembly.Module(): Wasm code generation disallowed by embedder” when constructing a module from bytes.
- When falling back to wa-sqlite default factory in SSR:
- “WebAssembly.instantiateStreaming is not a function — falling back to ArrayBuffer”
- “XMLHttpRequest is not defined” → Aborted(...), originating from Emscripten default path that expects XHR in non‑fetch contexts.
- Test run logs (examples/web-todomvc-redwood/test:e2e:dev) show SSR runner errors:
- Not found module id for wasm in SSR virtualization: Import "/@id/virtual:rwsdk:ssr:/@fs/.../wa-sqlite.wasm[?module]" not found.
- Followed by Emscripten wasm init failures when attempting default path.
Files:
- examples/web-todomvc-redwood/src/load-wasm.ssr.ts:1
- packages/@livestore/adapter-node/src/client-session/adapter.ts:146
- packages/@livestore/sqlite-wasm/src/load-wasm/mod.workerd.ts:1
- workerd SSR disallows WASM code generation from arbitrary bytes for security and doesn’t provide
XMLHttpRequest. - Emscripten default initialization in wa-sqlite tries to XHR or streaming-instantiate — both not supported in the dev SSR worker.
- Vite’s wasm helper (
?module) is not being resolved by the rwsdk SSR virtualization in dev; the id/@id/virtual:rwsdk:ssr:/@fs/.../wasm?modulefails to resolve, so the precompiled module never reaches the SSR context.
- Ensure the SSR virtualization layer resolves Vite’s wasm helper imports for worker SSR:
- Recognize and forward
*.wasm,*.wasm?module, or the internal virtual IDs to Vite’svite:wasm-helperso that SSR receives aWebAssembly.Moduleexport precompiled by the bundler. - Alternatively, add an SSR plugin hook inside rwsdk to intercept wasm imports and synthesize a module that exports a compiled
WebAssembly.Modulefor workerd.
- Recognize and forward
- Provide guidance (or defaults) so projects can import WASM as a module on SSR (
import wasm from '...wasm?module') and use EmscripteninstantiateWasmwithnew WebAssembly.Instance(wasm, info)— no byte compilation, no XHR.
- Importing
.../wa-sqlite.wasm?modulefrom SSR context yields “not found” on a virtual rwsdk SSR id:/@id/virtual:rwsdk:ssr:/@fs/.../wa-sqlite.wasm?module. - This indicates the dev SSR runner’s module resolution layer is not mapping that virtual id back through Vite’s wasm plugin.
- Playwright dev SSR failures: repeated “Import ... wasm?module not found” in worker logs.
- Switching to plain
...wasmyields the same: virtualization id still not resolved.
- The rwsdk SSR dev runner likely rewrites import specifiers into a “virtual:rwsdk:ssr” space but doesn’t forward those to Vite’s wasm transform. As a result, the helper that emits
export default WebAssembly.Moduleis never invoked, and the import fails.
- In the dev SSR implementation, detect virtualized wasm specifiers and route them through Vite’s own module graph/transform pipeline.
- Provide a documented hook/escape hatch so frameworks can register special handling for wasm in SSR (e.g., a
resolveId/loadpair in the SSR bundler that delegates to Vite’s wasm helper).
- “unenv child_process.spawnSync is not implemented yet!” triggered by
@parcel/watcheranddetect-libcduring optimization/runtime.
- We mitigated in the example by aliasing
@parcel/watcherto a stub and excluding these from optimizeDeps, both for client and server.
Files:
- examples/web-todomvc-redwood/vite.config.mts:1
- examples/web-todomvc-redwood/src/parcel-watcher-stub.ts:1
- The SSR worker environment is not Node; Node APIs (child_process) are not available. Dev/optimizer steps pulling such packages into SSR break at runtime.
- Harden SSR dev configuration to avoid pulling Node‑only dependencies into SSR runtime. If the SSR runner has its own optimizer layer, exclude or alias known Node‑only packages automatically in worker mode.
- Even after avoiding lightningcss and Node packages, wa-sqlite’s Emscripten init path still attempts unsupported APIs in workerd (XHR; streaming instantiate) when running in SSR.
- Logs show Emscripten trying
WebAssembly.instantiateStreaming→ fallback → XHR → abort.
- The default Emscripten loader assumes either browser (fetch/XHR) or Node; workerd SSR requires passing a compiled
WebAssembly.ModulethroughinstantiateWasmand avoiding all runtime fetches.
- Provide a recommended pattern for SSR to override Emscripten
instantiateWasmusing a precompiled module import. - Ensure the SSR dev runner can accept direct wasm module imports and not only URLs. For example: allow
import wasm from '...wasm?module'during SSR and pass that through untouched.
- Multiple layers (Vite, rwsdk SSR virtualization, workerd) can produce errors, sometimes with virtualized specifiers, making it difficult to identify which layer rejected the import.
- The failing id
/@id/virtual:rwsdk:ssr:/@fs/.../wa-sqlite.wasm?modulesuggests the module never reached Vite’s wasm helper.
- The SSR dev stack is a composition of tools; when an import fails in the SSR worker it’s not obvious whether Vite, the SSR runner, or the worker sandbox enforced the constraint.
- Add explicit diagnostics in rwsdk SSR runner when an import is blocked or cannot be resolved back to Vite’s transform. For WASM, detect and emit guidance in dev (e.g., “Use ?module and ensure this flag is enabled”).
-
Stop importing
vitein rwsdk runtime helpers for SSR- Inline path normalization helpers; avoid pulling Vite transform internals.
-
Pass wasm imports through Vite’s wasm helper in SSR
- Make the SSR virtualization forward
*.wasm(especially?module) tovite:wasm-helperso SSR gets a compiledWebAssembly.Moduleexport. - Alternatively, provide an SSR plugin point to resolve wasm ids and return a
WebAssembly.Moduledirectly for workerd.
- Make the SSR virtualization forward
-
Document a canonical SSR Emscripten pattern
- For workerd SSR, recommend
instantiateWasm(info, receiveInstance)that callsnew WebAssembly.Instance(precompiledModule, info)whereprecompiledModulecomes from an import. - Example shape:
import wasm from './lib.wasm?module'in SSR‑only loader.
- For workerd SSR, recommend
-
Guard against Node‑only packages in worker SSR
- Default excludes/aliases for known Node‑only deps in SSR dev mode (e.g.,
@parcel/watcher,detect-libc) or provide a preset for worker SSR.
- Default excludes/aliases for known Node‑only deps in SSR dev mode (e.g.,
-
Improve SSR dev diagnostics for virtualized ids
- Log where a virtual id failed to resolve and suggestions to enable wasm helper routing or configuration flags.
- Example: examples/web-todomvc-redwood
- Dev:
pnpm --dir examples/web-todomvc-redwood devthenpnpm --dir examples/web-todomvc-redwood test:e2e:dev - Evidence points:
- “Import ... /virtual:rwsdk:ssr:/@fs/.../wa-sqlite.wasm[?module] not found”
- Emscripten init path errors:
instantiateStreamingmissing, XHR missing → abort - lightningcss pulled into optimizer until rwsdk
viteimport was removed
- Patched rwsdk runtime helper to avoid importing
vite. - Vite excludes and stubs to keep lightningcss and
@parcel/watcheraway from optimizer/SSR runtime. - SSR-aware loader for wa-sqlite in example that avoids byte compilation in SSR; however, without upstream wasm helper pass-through in rwsdk SSR, dev SSR still can’t import the precompiled module path.
- Is there a recommended way in rwsdk SSR dev to ensure
*.wasm?moduleflows through to Vite’s wasm helper and yields aWebAssembly.Moduleat import time? - Would you accept a change to rwsdk SSR virtualization to map virtual wasm ids back into Vite’s pipeline?
- Are there documented SSR presets for worker environments to exclude/alias Node‑only dependencies by default?