Next.js App Router
Learn how to use Joy UI with the Next.js App Router.
Next.js and React Server Components
The Next.js App Router implements React Server Components, a new feature introduced in React 18.
To support the App Router, currently all components and hooks from Joy UI and other MUI libraries are exported with the "use client"
directive.
Using Joy UI with the App Router
To set up Joy UI, create a custom ThemeRegistry
component that combines the Emotion CacheProvider
, Joy UI's CssVarsProvider
and the useServerInsertedHTML
hook from next/navigation
as follows:
// app/ThemeRegistry.tsx
'use client';
import createCache from '@emotion/cache';
import { useServerInsertedHTML } from 'next/navigation';
import { CacheProvider } from '@emotion/react';
import { CssVarsProvider } from '@mui/joy/styles';
import CssBaseline from '@mui/joy/CssBaseline';
import theme from '/path/to/custom/theme'; // OPTIONAL
// This implementation is from emotion-js
// https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902
export default function ThemeRegistry(props) {
const { options, children } = props;
const [{ cache, flush }] = React.useState(() => {
const cache = createCache(options);
cache.compat = true;
const prevInsert = cache.insert;
let inserted: string[] = [];
cache.insert = (...args) => {
const serialized = args[1];
if (cache.inserted[serialized.name] === undefined) {
inserted.push(serialized.name);
}
return prevInsert(...args);
};
const flush = () => {
const prevInserted = inserted;
inserted = [];
return prevInserted;
};
return { cache, flush };
});
useServerInsertedHTML(() => {
const names = flush();
if (names.length === 0) {
return null;
}
let styles = '';
for (const name of names) {
styles += cache.inserted[name];
}
return (
<style
key={cache.key}
data-emotion={`${cache.key} ${names.join(' ')}`}
dangerouslySetInnerHTML={{
__html: styles,
}}
/>
);
});
return (
<CacheProvider value={cache}>
<CssVarsProvider theme={theme}>
{/* the custom theme is optional */}
<CssBaseline />
{children}
</CssVarsProvider>
</CacheProvider>
);
}
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<ThemeRegistry options={{ key: 'joy' }}>{children}</ThemeRegistry>
</body>
</html>
);
}
Function props
Props passed from server components—for example page.js
or other routing files—must be serializable.
This works without any additional directives:
// app/page.tsx
import Sheet from '@mui/joy/Sheet';
import Typography from '@mui/joy/Typography';
export default function Page() {
return (
<>
<Sheet variant="outlined">
<Typography fontSize="sm">Hello World</Typography>
</Sheet>
</>
);
}