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 guidesNext.js Pages → App Router

Next.js Pages → App Router

Days Med risk

Move a Next.js Pages-Router app to the App Router incrementally. The two routers can coexist (the App Router takes precedence for matching routes), so you don’t need a flag day.

This guide focuses on the Mushi-specific bits — provider placement, CSP for static export, server-component vs client-component boundaries. For the framework-level migration mechanics see the official Next.js migration guide .

Same Mushi project, same API key. The provider import changes from pages/_app.tsx to app/providers.tsx, but everything downstream of it works the same.

API mapping (Pages → App)

Pages RouterApp Router
pages/_app.tsx (<MushiProvider> mounted here)app/providers.tsx (client component) imported in app/layout.tsx
pages/_document.tsxapp/layout.tsx
getServerSidePropsServer Component fetch
getStaticProps / getStaticPathsgenerateStaticParams + Server Component fetch
useRouter().queryuseSearchParams() + useParams()
next/headmetadata export OR <head> in layout

Migration checklist

Migration checklist
10 required steps
  1. Step 01
    Audit dependencies for App Router compatibility
  2. Step 02
    Create the app/ directory alongside pages/
  3. Step 03
    Create app/layout.tsx (the new root)
  4. Step 04
    Create app/providers.tsx with the Mushi provider
  5. Step 05
    Delete pages/_app.tsx (only AFTER you have an app/layout.tsx)
  6. Step 06
    Port routes one at a time
  7. Step 07
    Convert data fetching to Server Components
  8. Step 08
    Update CSP for App Router
  9. Step 09
    Re-test static export (if applicable)
  10. Step 10
    Smoke-test on every primary route

Where to put <MushiProvider> (the most asked question)

At the root of your tree, in a 'use client' component, mounted by the root layout. This means:

  • app/providers.tsx (client component) imported into app/layout.tsx
  • ❌ Inside an individual page (loses provider context across navigations)
  • ❌ As a Server Component (the SDK uses React state — must be client)

The cost of one client boundary at the root is minimal (the rest of your tree can still be server-rendered) and it matches how Next-Auth’s SessionProvider, react-query’s QueryClientProvider, and similar libs are placed.

Server Components and useMushi()

useMushi(), useMushiReport(), and the visual widget all require a client component. That’s expected — Mushi captures user-side context (console, network, screenshot) which only exists in the browser.

If you need user-facing context inside a Server Component (e.g. to pre-render a “Report a problem with this article” button with a contextual route), pass the data down as props and let the client child call Mushi:

// app/articles/[slug]/page.tsx (Server Component) export default async function Page({ params }: { params: { slug: string } }) { const article = await fetchArticle(params.slug) return <ArticleReportButton articleId={article.id} /> } // app/articles/[slug]/ArticleReportButton.tsx (Client Component) 'use client' import { useMushiReport } from '@mushi-mushi/react' export function ArticleReportButton({ articleId }: { articleId: string }) { const { submit } = useMushiReport() return <button onClick={() => submit({ description: 'Article issue', metadata: { articleId } })}>Report</button> }

Common gotchas

  • Two providers mounted. If you forget to delete pages/_app.tsx after creating app/layout.tsx, both run, which double-fires events. Always remove the old one in the same PR.
  • useMushi() in a Server Component. Build error. Add 'use client' to the file or move the hook into a child client component.
  • Stale env vars after rename. App Router enforces the NEXT_PUBLIC_* prefix more strictly than Pages did; non-prefixed vars are now strictly server-only. Rename if migrating from a Pages app that relied on process.env.MUSHI_*.

References

Last updated on