#1422·ripple

Client bundle crashes at boot (ReferenceError: process is not defined) when ripple.config.ts imports defineConfig from @ripple-ts/vite-plugin, silently breaking all interactivity

Author: chenzylabCreated Aug 13, 2026Updated Aug 13, 2026

Summary

ripple.config.ts gets bundled into the client build because the generated hydration entry (create_client_entry_source in @ripple-ts/vite-plugin) does import rippleConfig from '<configPath>' directly. Since a typical ripple.config.ts imports defineConfig from @ripple-ts/vite-plugin, the whole plugin module ends up in the client bundle too — including its module-level const IS_WINDOWS = process.platform === 'win32'; (packages/vite-plugin/src/index.js:48).

In the browser this throws ReferenceError: process is not defined at the top level of the client entry chunk. Because it's a top-level throw (not inside the try/catch that wraps hydrate()/mount()), it aborts the entire client bootstrap script before hydrate() ever runs. The result: SSR output looks correct, no console error about hydration mismatch, but every interactive component on the page is inert — click handlers never get attached, and there's no error surfaced that points at the real cause.

Reproduction

  1. create-ripple app (or any app) using @ripple-ts/vite-plugin's RenderRoute, with ripple.config.ts importing defineConfig from @ripple-ts/vite-plugin.
  2. Add any component with a click handler (e.g. a toggle/switch) to a render route page.
  3. vite build for the client, then serve the built output (node dist/server/entry.js with the adapter-node/bun/vercel entry, or preview).
  4. Open the page in a browser and click the interactive element.

Observed: nothing happens. DevTools console shows:

ReferenceError: process is not defined
    at .../assets/main-<hash>.js:<line>

pointing into the bundled @ripple-ts/vite-plugin code, not into any application code.

This reproduces identically in dev mode and in a production build — it is not an optimizeDeps/dep-scan artifact. (There's a related, separate dev-only noise issue where @tsrx/core's vite/dep-scan.js — which imports node:fs/promises — gets pulled into the client dep-scan; that one only affects the dev console and does not break interactivity, so it's not the same bug, just easy to conflate since both surface as "Node module in browser" errors.)

Root cause

packages/vite-plugin/src/index.js:48:

javascript
const IS_WINDOWS = process.platform === 'win32';

This is evaluated eagerly at module load, with no guard. IS_WINDOWS is only read inside createVirtualImportId (line ~132), a server-only helper for building Vite virtual module ids from Windows-style /@fs/C:/... paths — it has no reason to exist in a client bundle at all, but it still gets executed because the whole module is imported.

Suggested minimal fix

Guard the top-level access so an accidental client-side import doesn't crash the whole script:

javascript
const IS_WINDOWS = typeof process !== 'undefined' && process.platform === 'win32';

This alone would prevent the hard crash, though the deeper issue is that create_client_entry_source importing the whole ripple.config.ts (transitively pulling in @ripple-ts/vite-plugin, and whatever else the config file imports) into the client graph seems unintentional — SERVER_ONLY_ADAPTER_IDS already stubs out @ripple-ts/adapter-node/-bun/-vercel for exactly this reason, but @ripple-ts/vite-plugin itself isn't in that list. A more complete fix might extract only the route table from the config at build time rather than importing the live config module client-side, but I wanted to report the observable symptom first since it's a pretty severe one (silently breaks all interactivity with no actionable error).

Environment

  • ripple / @ripple-ts/vite-plugin / @ripple-ts/adapter-node: 0.3.119
  • Reproduced via SSR render routes (RenderRoute + Node adapter), both vite dev and production build (vite build + node dist/server/entry.js)