Skip to content

Instantly share code, notes, and snippets.

@schickling
Created October 22, 2025 18:09
Show Gist options
  • Save schickling/a465f01fa56cea3cd19c27675bd96e0d to your computer and use it in GitHub Desktop.
Save schickling/a465f01fa56cea3cd19c27675bd96e0d to your computer and use it in GitHub Desktop.
Redwood Dev SSR + WASM (workerd) — Problem Report

Redwood Dev SSR + WASM (workerd) — Problem Report

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.

1) Dep Optimizer Pulls lightningcss into SSR Runtime

Symptoms

  • Vite dep optimization fails when lightningcss’s node/index.js tries to require('../pkg').
  • Observed when rwsdk imports vite at runtime (SSR worker), which pulls in Vite CSS internals that import('lightningcss').

Evidence

  • We applied a local pnpm patch to rwsdk to avoid importing vite in SSR runtime:
    • patches/[email protected]: removes import { normalizePath as normalizePathSeparators } from 'vite' and inlines a simple normalizer in dist/lib/normalizeModulePath.mjs.
  • Vite config hardening in example: switch to PostCSS transformer, exclude lightningcss from optimization, alias lightningcss to a stub, and stub @parcel/watcher to avoid unenv child_process errors.

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

Root Cause

  • rwsdk SSR runtime helper imported vite, which caused Vite to include its node CSS pipeline in the optimizer; that code dynamically imports lightningcss which expects a native pkg via CommonJS require('../pkg'), breaking in the dev SSR worker runtime.

Proposed Upstream Fixes (Redwood)

  • Avoid importing vite in 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.

2) SSR Worker Cannot Load WASM via URL or Byte Compilation

Symptoms

  • 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 /@fs path 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.

Evidence

  • 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

Root Cause

  • 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?module fails to resolve, so the precompiled module never reaches the SSR context.

Proposed Upstream Fixes (Redwood)

  • 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’s vite:wasm-helper so that SSR receives a WebAssembly.Module export 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.Module for workerd.
  • Provide guidance (or defaults) so projects can import WASM as a module on SSR (import wasm from '...wasm?module') and use Emscripten instantiateWasm with new WebAssembly.Instance(wasm, info) — no byte compilation, no XHR.

3) rwsdk SSR Virtualization Does Not Pass Through WASM Helper IDs

Symptoms

  • Importing .../wa-sqlite.wasm?module from 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.

Evidence

  • Playwright dev SSR failures: repeated “Import ... wasm?module not found” in worker logs.
  • Switching to plain ...wasm yields the same: virtualization id still not resolved.

Root Cause

  • 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.Module is never invoked, and the import fails.

Proposed Upstream Fixes (Redwood)

  • 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/load pair in the SSR bundler that delegates to Vite’s wasm helper).

4) Node‑specific Packages Leak into SSR Worker Runtime

Symptoms

  • “unenv child_process.spawnSync is not implemented yet!” triggered by @parcel/watcher and detect-libc during optimization/runtime.

Evidence

  • We mitigated in the example by aliasing @parcel/watcher to 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

Root Cause

  • 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.

Proposed Upstream Fixes (Redwood)

  • 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.

5) No Built‑in SSR Dev Path for WASM Emscripten Modules

Symptoms

  • 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.

Evidence

  • Logs show Emscripten trying WebAssembly.instantiateStreaming → fallback → XHR → abort.

Root Cause

  • The default Emscripten loader assumes either browser (fetch/XHR) or Node; workerd SSR requires passing a compiled WebAssembly.Module through instantiateWasm and avoiding all runtime fetches.

Proposed Upstream Fixes (Redwood)

  • Provide a recommended pattern for SSR to override Emscripten instantiateWasm using 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.

6) Observability: Where SSR Fails is Hard to Triangulate

Symptoms

  • Multiple layers (Vite, rwsdk SSR virtualization, workerd) can produce errors, sometimes with virtualized specifiers, making it difficult to identify which layer rejected the import.

Evidence

  • The failing id /@id/virtual:rwsdk:ssr:/@fs/.../wa-sqlite.wasm?module suggests the module never reached Vite’s wasm helper.

Root Cause

  • 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.

Proposed Upstream Fixes (Redwood)

  • 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”).

Concrete Upstream Changes We Recommend

  1. Stop importing vite in rwsdk runtime helpers for SSR

    • Inline path normalization helpers; avoid pulling Vite transform internals.
  2. Pass wasm imports through Vite’s wasm helper in SSR

    • Make the SSR virtualization forward *.wasm (especially ?module) to vite:wasm-helper so SSR gets a compiled WebAssembly.Module export.
    • Alternatively, provide an SSR plugin point to resolve wasm ids and return a WebAssembly.Module directly for workerd.
  3. Document a canonical SSR Emscripten pattern

    • For workerd SSR, recommend instantiateWasm(info, receiveInstance) that calls new WebAssembly.Instance(precompiledModule, info) where precompiledModule comes from an import.
    • Example shape: import wasm from './lib.wasm?module' in SSR‑only loader.
  4. 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.
  5. 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.

Reproduction Summary (from this repo)

  • Example: examples/web-todomvc-redwood
  • Dev: pnpm --dir examples/web-todomvc-redwood dev then pnpm --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: instantiateStreaming missing, XHR missing → abort
    • lightningcss pulled into optimizer until rwsdk vite import was removed

Current Workarounds in This Repo (for context)

  • Patched rwsdk runtime helper to avoid importing vite.
  • Vite excludes and stubs to keep lightningcss and @parcel/watcher away 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.

Ask to Redwood Team

  • Is there a recommended way in rwsdk SSR dev to ensure *.wasm?module flows through to Vite’s wasm helper and yields a WebAssembly.Module at 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?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment