Version 1.0.0 (Material UI v9)
Version notice: This skill targets Material UI v9 (
>=9.0.0 <10.0.0). If you are using a different major version, verify the API details before following this guidance.
Note: This document is for agents and LLMs integrating Material UI with Next.js. Source:
docs/data/material/integrations/nextjs/nextjs.mdand related integration docs in this repository.
Material UI uses Emotion for styles. On Next.js you must wire an Emotion cache so SSR and streaming produce correct CSS (prefer injecting styles into head instead of only body). The @mui/material-nextjs package supplies AppRouterCacheProvider (App Router) and AppCacheProvider / DocumentHeadTags (Pages Router). Material UI components ship as client components ("use client"); they still SSR but are not React Server Components. Match the package import suffix (for example v15-appRouter) to your Next.js major version.
- App Router (recommended)
- Pages Router
- Fonts (
next/font) - CSS theme variables and SSR
- Other styling stacks (CSS layers)
- Next.js Link and
componentprop - Further reading
Have @mui/material and next installed, then add:
@mui/material-nextjs@emotion/cache
Example: pnpm add @mui/material-nextjs @emotion/cache
In app/layout.tsx, wrap everything under <body> with AppRouterCacheProvider from the entry that matches your Next major, for example:
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
(Use the v1X-appRouter path that matches your Next.js version if not on v15.)
Why: it collects CSS from MUI System during server rendering and streaming so styles attach predictably; it is recommended so styles go to <head> instead of only <body>. See Next.js integration—Configuration.
Pass options to AppRouterCacheProvider to override Emotion cache options, for example key: 'css' (the default MUI key is mui). See Next.js integration—Custom cache (optional).
Dashboards and internal tools often combine MUI client components with URL-driven UI (filters, tabs, pagination) using useSearchParams() from next/navigation.
Next.js expects a <Suspense> boundary around the part of the tree that uses useSearchParams (and similar patterns that opt the route into client-side rendering), otherwise you can get build failures or runtime errors about a missing Suspense boundary.
Practical pattern: keep app/.../page.tsx as a server component when possible; render a client subtree that uses components such as Table, Tabs, or TextField and is tied to the query string inside <Suspense> from that server page. Do not use fallback={null} for UI that occupies layout space (toolbars, filters, and similar); it tends to cause layout shift when the client mounts. Use a fallback that matches the real layout (for example Skeleton with Stack or Box and the same minHeight and rough dimensions as the final UI). Full example: Next.js integration—URL-driven UI and the Suspense boundary.
Official reference: Next.js—useSearchParams (static rendering and Suspense notes vary by major version).
Add @mui/material-nextjs, @emotion/cache, and @emotion/server.
Example: pnpm add @mui/material-nextjs @emotion/cache @emotion/server
- Import
DocumentHeadTagsanddocumentGetInitialPropsfrom thev15-pagesRouter(or matchingv1X-pagesRouter) entry. - Render
<DocumentHeadTags {...props} />inside<Head>. - Assign
getInitialPropsto calldocumentGetInitialProps.
Wrap the app with AppCacheProvider from the same major entry (for example v15-pagesRouter).
- Pass a custom
emotionCacheintodocumentGetInitialPropsoptions when needed. - For
@layer, usecreateEmotionCache({ enableCssLayer: true })from@mui/material-nextjs, pass it from_documentand align_appwith the same cache pattern. See Next.js integration—Cascade layers (optional).
Extend Document props with DocumentHeadTagsProps from the same import path. See Next.js integration—TypeScript.
App Router: theme modules that call createTheme need 'use client' when they are consumed from server components. Use next/font/google (or local fonts), set variable: '--font-…', put className={font.variable} on <html> (or as in docs), and set typography.fontFamily to 'var(--font-…)'. Wrap with ThemeProvider inside AppRouterCacheProvider as needed.
Pages Router: similar pattern in pages/_app.tsx with AppCacheProvider and ThemeProvider.
Details: Next.js integration—Font optimization (App) and Next.js integration—Font optimization (Pages).
Enable cssVariables: true in createTheme when using CSS theme variables. For SSR flicker and InitColorSchemeScript, follow CSS theme variables—Preventing SSR flickering and CSS theme variables overview—Advantages. Add suppressHydrationWarning to <html> when using colorSchemes — the color scheme attribute is written client-side on first render and will otherwise produce a React hydration mismatch.
If you combine MUI with Tailwind CSS, CSS Modules, or other global CSS, set enableCssLayer: true on AppRouterCacheProvider:
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
That wraps MUI output in @layer mui so anonymous layers can override as intended. See Next.js integration—Using other styling solutions and MDN—@layer.
Next.js v16: passing next/link directly into component can trigger "Functions cannot be passed directly to Client Components". Fix: a small client re-export:
'use client';
import Link, { LinkProps } from 'next/link';
export default Link;Import that wrapper and use component={Link} on Button and similar. See Next.js integration—Next.js v16 Client Component restriction.
Pages Router and theme-wide patterns: see Routing libraries—Next.js Pages Router and the material-ui-nextjs-pages-router-ts example.
| Topic | Link |
|---|---|
| Full integration guide | Next.js integration |
| Example (App Router, TypeScript) | material-ui-nextjs-ts |
| Routing + Link adapters | Routing libraries |
| RSC vs SSR (terminology) | React WG discussion |
Import path cheat sheet: reference.md.