How to Implement a Custom Material UI Nextjs Theme in the App Router for Optimal Performance
Enable cssVariables: true in your MUI theme configuration and wrap your root layout with ThemeProvider and InitColorSchemeScript to achieve zero-config SSR styling consistency in Next.js 15.
Integrating Material UI with Next.js requires careful handling of server-side rendering to prevent hydration mismatches and styling flashes. This guide demonstrates how to configure a custom Material UI Nextjs theme using the App Router architecture, leveraging CSS variables for optimal performance and consistent styling across server and client environments.
Why CSS Variables Are Essential for Material UI Nextjs Performance
Material UI v5 and later support a cssVariables configuration that generates CSS custom properties instead of inline styles. This approach eliminates the need for runtime style injection on the client, reducing JavaScript execution time and preventing the flash of unstyled content (FOUC) during hydration.
In the Next.js repository, the benchmark application at turbopack/benchmark-apps/mui/theme.tsx demonstrates this configuration:
import { createTheme } from '@mui/material/styles';
import { red } from '@mui/material/colors';
const theme = createTheme({
cssVariables: true,
palette: {
primary: { main: '#556cd6' },
secondary: { main: '#19857b' },
error: { main: red.A400 },
},
});
export default theme;
Setting cssVariables: true allows the browser to compute final CSS values without re-injecting style tags, providing the fastest First Contentful Paint and avoiding style-order mismatches between server and client renders.
Configuring the Root Layout with ThemeProvider
The App Router architecture in Next.js 15 uses a root layout file (app/layout.tsx) that wraps all pages. Placing the ThemeProvider at this level ensures every route segment receives consistent theming while supporting both Server Components and Client Components.
Based on the implementation pattern found in turbopack/benchmark-apps/mui/index.tsx and the routing logic in packages/next/src/client/components/app-router.tsx, your layout should import InitColorSchemeScript to prevent hydration mismatches:
import React from 'react';
import ThemeProvider from '@mui/material/styles/ThemeProvider';
import CssBaseline from '@mui/material/CssBaseline';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
import theme from '@/app/theme';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<ThemeProvider theme={theme}>
<InitColorSchemeScript attribute="class" />
<CssBaseline />
{children}
</ThemeProvider>
</body>
</html>
);
}
The InitColorSchemeScript component injects the initial color-scheme class (light or dark) on the server, ensuring the UI does not flash between modes when hydration occurs. This pattern aligns with the styled-components example in examples/with-styled-components/app/layout.tsx, adapted for Material UI's specific requirements.
Using Material UI Components in Server Components
One advantage of the App Router is that pages default to Server Components. Because the theme is provided at the layout level, Server-rendered components automatically receive the theme through CSS variables without requiring client-side JavaScript.
Create a page component that uses MUI components directly:
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
export default function HomePage() {
return (
<Box sx={{ p: 4 }}>
<Typography variant="h4" gutterBottom>
Welcome to My Material UI Nextjs App
</Typography>
<Button variant="contained" color="primary">
Primary Action
</Button>
</Box>
);
}
Since cssVariables: true generates static CSS custom properties, the server can render these components with correct styling without executing Emotion's runtime on the client. This eliminates the need for a custom CacheProvider and reduces bundle size.
Implementing Dark Mode with Client Components
To allow users to toggle between light and dark modes, create a Client Component that interacts with the theme's color scheme. This component must use the 'use client' directive because it manages browser state.
'use client';
import * as React from 'react';
import { useColorScheme } from '@mui/material/styles';
import Switch from '@mui/material/Switch';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
export default function ModeSwitch() {
const { mode, setMode } = useColorScheme();
const toggle = () => setMode(mode === 'dark' ? 'light' : 'dark');
return (
<Box sx={{ position: 'fixed', top: 16, right: 16 }}>
<FormControlLabel
control={<Switch checked={mode === 'dark'} onChange={toggle} />}
label={mode === 'dark' ? 'Dark' : 'Light'}
/>
</Box>
);
}
Place this component inside your RootLayout after CssBaseline. The InitColorSchemeScript ensures the server renders the correct initial mode, while this Client Component handles subsequent user interactions without causing hydration mismatches.
Summary
- Enable CSS variables by setting
cssVariables: truein yourcreateThemeconfiguration to eliminate runtime style injection and prevent hydration mismatches. - Place
ThemeProviderinapp/layout.tsxto ensure consistent theming across all routes while supporting both Server and Client components. - Include
InitColorSchemeScriptimmediately inside the provider to inject the correct color-scheme class on the server and prevent flash of incorrect theme. - Use MUI components directly in Server components when possible, as CSS variables allow the server to render styled HTML without client-side JavaScript.
- Create Client components only for interactive features like theme toggling, using the
useColorSchemehook to manage state without breaking SSR consistency.
Frequently Asked Questions
Do I need Emotion's CacheProvider with Material UI Nextjs?
No. When you enable cssVariables: true in your theme configuration, Material UI generates CSS custom properties instead of relying on Emotion's runtime injection. This eliminates the need for a custom CacheProvider and reduces your JavaScript bundle size, as the styles are handled natively by the browser.
Can I use Material UI components in Nextjs Server Components?
Yes. With the cssVariables configuration enabled, Material UI components can render correctly in Server Components because the theme values are embedded as CSS variables in the HTML. The ThemeProvider in your root layout supplies the theme context, allowing pages to use MUI components without marking them as client components, provided they don't use browser-only APIs or event handlers.
How do I prevent flash of unstyled content with Material UI Nextjs?
Place the InitColorSchemeScript component immediately inside your ThemeProvider in the root layout. This script injects the correct color-scheme class (light or dark) during server-side rendering before React hydrates. Combined with cssVariables: true, this ensures the browser applies the correct styles immediately on page load, preventing any visual flash between default and user-preferred themes.
Where should I place the ThemeProvider in the Nextjs App Router?
Always place the ThemeProvider in your root layout file at app/layout.tsx. This ensures the provider wraps every page and route segment in your application. In the App Router architecture, the layout is a Server Component that executes once per route segment, making it the optimal location for theme context that must persist across client-side navigations while supporting initial server-side rendering.
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 →