How to Integrate Material-UI with Next.js App Router Using @mui/material-nextjs
Use the AppRouterCacheProvider component from @mui/material-nextjs to create an Emotion cache that collects server-side CSS and streams style tags into the HTML response.
Material-UI provides a dedicated integration package @mui/material-nextjs that streamlines server-side rendering in Next.js 13+ applications using the App Router. This package exports the AppRouterCacheProvider component, which manages Emotion cache insertion during streaming SSR. According to the Material-UI source code, the provider wraps your application tree to ensure styles are correctly generated on the server and hydrated on the client.
Installation and Setup
First, install the integration package alongside Emotion cache (required peer dependency):
npm install @mui/material-nextjs @emotion/cache
Or using Yarn or pnpm:
yarn add @mui/material-nextjs @emotion/cache
# or
pnpm add @mui/material-nextjs @emotion/cache
Configuring the Root Layout
Wrap your application root with AppRouterCacheProvider in your app/layout.tsx file. Import the provider from the version-specific entry point that matches your Next.js version (e.g., v15-appRouter for Next.js 15).
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AppRouterCacheProvider>
{children}
</AppRouterCacheProvider>
</body>
</html>
);
}
How AppRouterCacheProvider Works
The integration relies on a thin wrapper around Emotion's caching system implemented in packages/mui-material-nextjs/src/v13-appRouter/appRouterV13.tsx. Understanding this architecture helps debug styling issues in complex applications.
Emotion Cache Architecture
The provider initializes an Emotion cache using createCache with a default key of mui. In the source code, the cache is configured with cache.compat = true to ensure generated class names match the classic Emotion format used by Material-UI components. This cache is then supplied to the React tree via Emotion's CacheProvider.
Server-Side Style Collection
During server-side rendering, the provider wraps the cache's insert method to record every style that is rendered. When Next.js calls useServerInsertedHTML (imported via the shim in packages/mui-material-nextjs/src/v13-appRouter/nextNavigation.cjs), the provider flushes the recorded styles and returns <style> elements that are injected into the streamed HTML. This mechanism ensures critical CSS arrives with the initial HTML payload.
CSS Cascade Layer Support
When the options.enableCssLayer flag is set to true, each style block is wrapped in @layer mui { … }. This allows developers to control style precedence when mixing Material-UI with Tailwind CSS, CSS Modules, or other styling solutions that utilize cascade layers.
Advanced Configuration
Enabling CSS Layers
To isolate Material-UI styles within a cascade layer—useful for resolving specificity conflicts with other CSS-in-JS solutions—pass the enableCssLayer option:
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
<AppRouterCacheProvider options={{ enableCssLayer: true, key: 'mui' }}>
{children}
</AppRouterCacheProvider>
Font Optimization with Next.js
Combine the cache provider with Next.js 13+ font optimization by passing the font variable to your theme:
'use client';
import { createTheme } from '@mui/material/styles';
import { ThemeProvider } from '@mui/material';
import { Roboto } from 'next/font/google';
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
const roboto = Roboto({
weight: ['300', '400', '500', '700'],
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto',
});
const theme = createTheme({
typography: { fontFamily: 'var(--font-roboto)' },
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={roboto.variable}>
<body>
<AppRouterCacheProvider>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}
Summary
@mui/material-nextjsprovidesAppRouterCacheProviderfor seamless Material-UI integration with Next.js App Router.- The provider creates an Emotion cache in
packages/mui-material-nextjs/src/v13-appRouter/appRouterV13.tsxthat tracks server-rendered styles. - It uses
useServerInsertedHTMLfrom Next.js (vianextNavigation.cjs) to stream style tags into the HTML response. - Enable
options.enableCssLayerto wrap styles in@layer muifor cascade control with Tailwind or CSS Modules. - Install alongside
@emotion/cacheas a peer dependency.
Frequently Asked Questions
What is the difference between @mui/material-nextjs and @emotion/cache?
@mui/material-nextjs is a thin integration wrapper that configures @emotion/cache specifically for Next.js App Router streaming SSR. While @emotion/cache provides the underlying caching mechanism, the Material-UI package handles the complex coordination between Emotion's style insertion and Next.js's useServerInsertedHTML hook, ensuring styles are correctly collected and injected during server-side rendering.
Why do I need to use a specific version import like v15-appRouter?
The versioned imports (e.g., v13-appRouter, v15-appRouter) correspond to Next.js major versions and ensure compatibility with specific App Router APIs. The underlying implementation in packages/mui-material-nextjs/src/v13-appRouter/appRouterV13.tsx remains consistent, but the entry points handle different Next.js dependency requirements and prevent client/server bundle mismatches.
How does enableCssLayer resolve style conflicts with Tailwind CSS?
When enableCssLayer is set to true, the provider wraps all Material-UI styles in @layer mui { ... }. This places MUI styles within a named cascade layer with lower priority than unlayered styles (like Tailwind's utility classes), allowing Tailwind utilities to override Material-UI component styles without requiring excessive specificity or !important flags.
Can I use a custom Emotion cache key with AppRouterCacheProvider?
Yes. While the default cache key is mui, you can override it via the options.key property. This is useful when running multiple Emotion instances or when integrating with other libraries that also use Emotion, though you should ensure the key matches across server and client to prevent hydration mismatches.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →