Skip to Content
v0.8.0 · shippedNative iOS / Android / Flutter / Capacitor SDKs, A2A discovery, SOC 2 readiness, residency, BYO storage, BYOK. Read the changelog →
Migration guidesSPA → SSR

SPA → SSR (Next.js / Nuxt / SvelteKit)

Weeks Med risk

Move 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-specific 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:

Env-var prefix mapping

This is the change that bites every team:

Vite SPANext.jsNuxtSvelteKit
VITE_MUSHI_PROJECT_IDNEXT_PUBLIC_MUSHI_PROJECT_IDNUXT_PUBLIC_MUSHI_PROJECT_IDPUBLIC_MUSHI_PROJECT_ID
VITE_MUSHI_API_KEYNEXT_PUBLIC_MUSHI_API_KEYNUXT_PUBLIC_MUSHI_API_KEYPUBLIC_MUSHI_API_KEY
import.meta.env.VITE_*process.env.NEXT_PUBLIC_*useRuntimeConfig().public.*import.meta.env.PUBLIC_*

The Mushi CLI handles this automatically: rerun npx mushi-mushi init after the migration and it’ll write the right prefix into your .env.local.

Migration checklist (generic, picks the right framework as you go)

Migration checklist
12 required steps
  1. Step 01
    Pick the target framework
  2. Step 02
    Bootstrap the new framework alongside the old SPA
  3. Step 03
    Port shared design tokens / global CSS first
  4. Step 04
    Migrate routes one at a time
  5. Step 05
    Move data fetching to the server
  6. Step 06
    Rename env vars per the table above
  7. Step 07
    Mount Mushi at the root of the new framework
  8. Step 08
    Verify no hydration warnings
  9. Step 09
    For Nuxt/Next, use a .client suffix or "use client" for Mushi components
  10. Step 10
    Confirm SSR routes are crawlable
  11. Step 11
    Redirect old SPA routes to the new ones (when all migrated)
  12. Step 12
    Smoke-test Mushi from a server-rendered page

Where the Mushi widget lives across SSR vs CSR

The 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 within ~100 ms of Mushi.init() resolving
  • ⚠️ Brief flash of un-widget’d page on slow networks (acceptable; this is intentional client-only behaviour 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.
  • Forgetting to call Mushi.init() in addition to the framework adapter — Vue and Svelte adapters do NOT mount the widget themselves. They wire composables / error handlers; you still need Mushi.init(credentials) to mount the visual widget.
  • Env-var prefix typos — symptom is projectId: undefined errors at runtime. Run npx mushi-mushi init to regenerate the env section correctly.

References

Last updated on