You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Category suggestion: Show and tell Title suggestion:vite-plugin-msw-server — run msw/node during Vite dev & build (SSR loaders + SSG prerender)
Hey msw folks 👋
I kept hitting the same gap using msw with an isomorphic Vite app, so I wrote a small plugin to close it and wanted to share it here in case it's useful to others (and to get feedback from people who know msw far better than I do).
The problem
msw's browser worker (setupWorker) is fantastic for client-side requests. But in a Vite SSR/SSG app, a lot of fetching happens before the browser ever runs:
vite dev — your SSR entry / route loaders / data() hooks execute inside the Vite dev server, a Node process. Those server-side fetches hit the real API by default — the browser worker can't reach them.
vite build — during SSG prerendering, pages are rendered once at build time, again in Node. So your static output silently depends on a live API being reachable at build time, which makes prerenders flaky and non-deterministic.
So you end up with msw mocking the client half of an isomorphic app, while the server half quietly talks to production. You can wire up msw/node by hand, but then every app re-invents the "start the server at the right Vite lifecycle hook, and make sure prod never imports it" dance.
The idea
A Vite plugin whose only job is to start an msw/node server inside the Vite process, at the right moments:
configureServer → covers vite dev (SSR).
buildStart → covers vite build (SSG/prerender).
Two deliberate constraints to keep it clean:
No runtime dependency on msw. You own the msw install and hand the plugin yoursetupServer(...) instance. The plugin only needs the structural listen() shape — it stays decoupled from your handlers and your msw version.
Lazy by design. You pass a factory (() => import('./mocks/server')), so the mock module never enters the config's module graph until the plugin actually runs. Gate it with enable (an env flag) and a production build never imports msw or your handlers at all.
It's intentionally tiny — it mirrors the browser worker on the server, so together they mock both sides of an isomorphic app.
// vite.config.tsimport{defineConfig}from'vite'importmswServerfrom'@soroush.tech/vite-plugin-msw-server'exportdefaultdefineConfig({plugins: [mswServer({enable: process.env.VITE_APP_MSW_ACTIVE==='true',// factory ⇒ mock module is only imported when the plugin runsserver: ()=>import('./src/test/mocks/server').then((m)=>m.server),}),],})
Scope is explicitly dev + build only — ✅ dev-time SSR and build-time SSG/prerender, ❌ not your deployed production server (Vite isn't running there, so neither is the plugin). It works with any Vite-powered SSR/SSG setup where server-side fetching runs in Node — Vike, Astro, SvelteKit, Nuxt, Remix (Vite), or vanilla Vite SSR.
MIT, types included, 100% test coverage, vite ^6 || ^7 || ^8 as a peer dep.
Would love feedback — especially whether this overlaps with anything the msw team would rather see solved upstream, and whether the "pass your own server / factory" decoupling is the right call. Thanks for msw! 🙏
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Hey msw folks 👋
I kept hitting the same gap using msw with an isomorphic Vite app, so I wrote a small plugin to close it and wanted to share it here in case it's useful to others (and to get feedback from people who know msw far better than I do).
The problem
msw's browser worker (
setupWorker) is fantastic for client-side requests. But in a Vite SSR/SSG app, a lot of fetching happens before the browser ever runs:vite dev— your SSR entry / route loaders /data()hooks execute inside the Vite dev server, a Node process. Those server-side fetches hit the real API by default — the browser worker can't reach them.vite build— during SSG prerendering, pages are rendered once at build time, again in Node. So your static output silently depends on a live API being reachable at build time, which makes prerenders flaky and non-deterministic.So you end up with msw mocking the client half of an isomorphic app, while the server half quietly talks to production. You can wire up
msw/nodeby hand, but then every app re-invents the "start the server at the right Vite lifecycle hook, and make sure prod never imports it" dance.The idea
A Vite plugin whose only job is to start an
msw/nodeserver inside the Vite process, at the right moments:configureServer→ coversvite dev(SSR).buildStart→ coversvite build(SSG/prerender).Two deliberate constraints to keep it clean:
mswinstall and hand the plugin yoursetupServer(...)instance. The plugin only needs the structurallisten()shape — it stays decoupled from your handlers and your msw version.() => import('./mocks/server')), so the mock module never enters the config's module graph until the plugin actually runs. Gate it withenable(an env flag) and a production build never imports msw or your handlers at all.It's intentionally tiny — it mirrors the browser worker on the server, so together they mock both sides of an isomorphic app.
Scope is explicitly dev + build only — ✅ dev-time SSR and build-time SSG/prerender, ❌ not your deployed production server (Vite isn't running there, so neither is the plugin). It works with any Vite-powered SSR/SSG setup where server-side fetching runs in Node — Vike, Astro, SvelteKit, Nuxt, Remix (Vite), or vanilla Vite SSR.
The package
npm install -D @soroush.tech/vite-plugin-msw-server mswvite ^6 || ^7 || ^8as a peer dep.Would love feedback — especially whether this overlaps with anything the msw team would rather see solved upstream, and whether the "pass your own server / factory" decoupling is the right call. Thanks for msw! 🙏
Beta Was this translation helpful? Give feedback.
All reactions