Skip to Content
v1.36.0 · shipped One-screen report · Looks like your app 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 run in the same app, one route at a time, so you don’t need a flag day. A given URL can live in only one of them.

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

Same Mushi Mushi project, same API key. App routes get the provider from app/providers.tsx; routes still in pages/ keep getting it from pages/_app.tsx until you move them. Everything downstream 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 Mushi provider
  5. Step 05
    Keep pages/_app.tsx until the last pages/ route is moved
  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 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 Mushi:

// app/articles/[slug]/page.tsx (Server Component) export default async function Page({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params // params is a Promise in Next.js 15+ const article = await fetchArticle(slug) return <ArticleReportButton articleId={article.id} /> } // app/articles/[slug]/ArticleReportButton.tsx (Client Component) 'use client' import { useMushiSdk } from 'mushi-mushi/react' export function ArticleReportButton({ articleId }: { articleId: string }) { const mushi = useMushiSdk() return <button onClick={() => void mushi?.captureEvent({ description: 'Article issue', metadata: { articleId } })}>Report</button> }

Common gotchas

  • Deleting pages/_app.tsx too early. Routes still in pages/ lose their providers, including Mushi Mushi’s. Delete _app only after the last pages/ route has moved.
  • Navigating between the two routers is a full page load. The Mushi Mushi SDK starts again on the other side; nothing else to do.
  • useMushi() in a Server Component. Build error. Add 'use client' to the file or move the hook into a child client component.
  • Env vars. Only NEXT_PUBLIC_* variables reach the browser, in both routers. If a Pages app passed a server-only MUSHI_* value down through getServerSideProps, rename it to NEXT_PUBLIC_MUSHI_* and read it in the client provider.

References

Last updated on