SPA → SSR (Next.js / Nuxt / SvelteKit)
Weeks Med riskMove a Vite SPA (React, Vue, or Svelte) onto a server-rendered framework (Next.js, Nuxt, or SvelteKit respectively) for SEO, faster TTFB, and real route-level data loading.
The Mushi Mushi bits are: env-var prefix changes, where to mount the provider so it survives navigation, and what to do about hydration warnings. The framework-level migration mechanics are upstream:
- React SPA → Next.js App Router: Next.js: migrating from Vite
- Vue SPA → Nuxt: Nuxt installation
- Svelte SPA → SvelteKit: creating a SvelteKit project
Env-var prefix mapping
This is the change that most often breaks the SDK after a move:
| Vite SPA | Next.js | Nuxt | SvelteKit |
|---|---|---|---|
VITE_MUSHI_PROJECT_ID | NEXT_PUBLIC_MUSHI_PROJECT_ID | NUXT_PUBLIC_MUSHI_PROJECT_ID | VITE_MUSHI_PROJECT_ID (unchanged) |
VITE_MUSHI_API_KEY | NEXT_PUBLIC_MUSHI_API_KEY | NUXT_PUBLIC_MUSHI_API_KEY | VITE_MUSHI_API_KEY (unchanged) |
import.meta.env.VITE_* | process.env.NEXT_PUBLIC_* | useRuntimeConfig().public.* (declare the keys in nuxt.config) | import.meta.env.VITE_* |
SvelteKit runs on Vite, so VITE_ variables keep working; if you prefer
SvelteKit’s own PUBLIC_ prefix, read them from $env/static/public
instead. Rerunning npx mushi-mushi init in the new project writes the
prefix the CLI uses for that framework into your .env.local.
Migration checklist (generic, picks the right framework as you go)
- Step 01Pick the target framework
- Step 02Bootstrap the new framework alongside the old SPA
- Step 03Port shared design tokens / global CSS first
- Step 04Migrate routes one at a time
- Step 05Move data fetching to the server
- Step 06Rename env vars per the table above
- Step 07Mount Mushi Mushi at the root of the new framework
- Step 08Verify no hydration warnings
- Step 09For Nuxt/Next, use a .client suffix or "use client" for Mushi Mushi code
- Step 10Confirm SSR routes are crawlable
- Step 11Redirect old SPA routes to the new ones (when all migrated)
- Step 12Smoke-test Mushi Mushi from a server-rendered page
Where the Mushi Mushi widget lives across SSR vs CSR
The Mushi Mushi visual widget mounts client-side, in a Shadow DOM child of
document.body, after Mushi.init() runs. Because it doesn’t render in
the server-side HTML, you’ll see:
- ✅ No hydration warnings caused by the widget itself
- ✅ Crawlers don’t see the widget (it’s not in the initial HTML)
- ⚠️ The widget appears only after the client-side JavaScript runs, so on slow networks the page shows briefly without it (intentional, to keep server HTML lean)
Common gotchas
useMushi()in a Server Component (Next.js App Router): build error. Add'use client'. See the Next.js Pages → App Router guide for the recommended pattern.- Calling
Mushi.init()as well as the framework adapter. Since 1.0,MushiPlugin(Vue/Nuxt) andinitMushi()(Svelte) initialise the SDK themselves; a secondMushi.init()only logs “Already initialized”. Remove it. - Env-var prefix typos. The SDK throws an error saying
projectIdandapiKeyare required. Runnpx mushi-mushi initto regenerate the env section.