432 lines
13 KiB
Markdown
432 lines
13 KiB
Markdown
|
|
# Next.js integration
|
|||
|
|
|
|||
|
|
<p class="description">Learn how to use Material UI with Next.js.</p>
|
|||
|
|
|
|||
|
|
## App Router
|
|||
|
|
|
|||
|
|
This section walks through the Material UI integration with the Next.js [App Router](https://nextjs.org/docs/app), an evolution of the [Pages Router](#pages-router), and, currently, the recommended way of building new Next.js applications starting from version 13.
|
|||
|
|
|
|||
|
|
### Installing the dependencies
|
|||
|
|
|
|||
|
|
Start by ensuring that you already have `@mui/material` and `next` installed.
|
|||
|
|
Then, run one of the following commands to install the dependencies:
|
|||
|
|
|
|||
|
|
<codeblock storageKey="package-manager">
|
|||
|
|
|
|||
|
|
```bash npm
|
|||
|
|
npm install @mui/material-nextjs @emotion/cache
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash pnpm
|
|||
|
|
pnpm add @mui/material-nextjs @emotion/cache
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash yarn
|
|||
|
|
yarn add @mui/material-nextjs @emotion/cache
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
</codeblock>
|
|||
|
|
|
|||
|
|
### Configuration
|
|||
|
|
|
|||
|
|
Inside `app/layout.tsx`, import the `AppRouterCacheProvider` and wrap all elements under the `<body>` with it:
|
|||
|
|
|
|||
|
|
```diff title="app/layout.tsx"
|
|||
|
|
+import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
|
|||
|
|
// or `v1X-appRouter` if you are using Next.js v1X
|
|||
|
|
|
|||
|
|
export default function RootLayout(props) {
|
|||
|
|
return (
|
|||
|
|
<html lang="en">
|
|||
|
|
<body>
|
|||
|
|
+ <AppRouterCacheProvider>
|
|||
|
|
{props.children}
|
|||
|
|
+ </AppRouterCacheProvider>
|
|||
|
|
</body>
|
|||
|
|
</html>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
:::info
|
|||
|
|
The `AppRouterCacheProvider` component is responsible for collecting the CSS generated by MUI System on the server, as Next.js is streaming chunks of the .html page to the client.
|
|||
|
|
|
|||
|
|
While it's not required to use the `AppRouterCacheProvider` component, it's recommended to use it to ensure that the styles are appended to the `<head>` and not rendering in the `<body>`.
|
|||
|
|
See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153 for why it's better.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
#### Custom cache (optional)
|
|||
|
|
|
|||
|
|
Use the `options` prop to override the default [cache options](https://emotion.sh/docs/@emotion/cache#options)—for example, the code snippet below shows how to change the CSS key to `css` (the default is `mui`):
|
|||
|
|
|
|||
|
|
```diff
|
|||
|
|
<AppRouterCacheProvider
|
|||
|
|
+ options={{ key: 'css' }}
|
|||
|
|
>
|
|||
|
|
{children}
|
|||
|
|
</AppRouterCacheProvider>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Font optimization
|
|||
|
|
|
|||
|
|
To integrate [Next.js font optimization](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) with Material UI, create a new file with the `'use client';` directive.
|
|||
|
|
Then create a theme using `var(--font-roboto)` as a value for the `typography.fontFamily` field.
|
|||
|
|
|
|||
|
|
```js title="src/theme.ts"
|
|||
|
|
'use client';
|
|||
|
|
import { createTheme } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
const theme = createTheme({
|
|||
|
|
typography: {
|
|||
|
|
fontFamily: 'var(--font-roboto)',
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
export default theme;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Finally, in `src/app/layout.tsx`, pass the theme to the `ThemeProvider`:
|
|||
|
|
|
|||
|
|
```diff title="app/layout.tsx"
|
|||
|
|
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
|
|||
|
|
+import { Roboto } from 'next/font/google';
|
|||
|
|
+import { ThemeProvider } from '@mui/material/styles';
|
|||
|
|
+import theme from '../theme';
|
|||
|
|
|
|||
|
|
+const roboto = Roboto({
|
|||
|
|
+ weight: ['300', '400', '500', '700'],
|
|||
|
|
+ subsets: ['latin'],
|
|||
|
|
+ display: 'swap',
|
|||
|
|
+ variable: '--font-roboto',
|
|||
|
|
+});
|
|||
|
|
|
|||
|
|
export default function RootLayout(props) {
|
|||
|
|
const { children } = props;
|
|||
|
|
return (
|
|||
|
|
+ <html lang="en" className={roboto.variable}>
|
|||
|
|
<body>
|
|||
|
|
<AppRouterCacheProvider>
|
|||
|
|
+ <ThemeProvider theme={theme}>
|
|||
|
|
{children}
|
|||
|
|
+ </ThemeProvider>
|
|||
|
|
</AppRouterCacheProvider>
|
|||
|
|
</body>
|
|||
|
|
</html>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
To learn more about theming, check out the [theming guide](/material-ui/customization/theming/) page.
|
|||
|
|
|
|||
|
|
### CSS theme variables
|
|||
|
|
|
|||
|
|
To use [CSS theme variables](/material-ui/customization/css-theme-variables/overview/), enable the `cssVariables` flag:
|
|||
|
|
|
|||
|
|
```diff title="src/theme.ts"
|
|||
|
|
'use client';
|
|||
|
|
const theme = createTheme({
|
|||
|
|
+ cssVariables: true,
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Learn more about [the advantages of CSS theme variables](/material-ui/customization/css-theme-variables/overview/#advantages) and how to [prevent SSR flickering](/material-ui/customization/css-theme-variables/configuration/#preventing-ssr-flickering).
|
|||
|
|
|
|||
|
|
### Using other styling solutions
|
|||
|
|
|
|||
|
|
If you are using a styling solution other than Emotion to customize Material UI components, set `enableCssLayer: true` in the `options` prop:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This option ensures that the styles generated by Material UI will be wrapped in a CSS `@layer mui` rule, which is overridden by anonymous layer styles when using Material UI with CSS Modules, Tailwind CSS, or even plain CSS without using `@layer`.
|
|||
|
|
|
|||
|
|
To learn more about it, see [the MDN CSS layer documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer).
|
|||
|
|
|
|||
|
|
### Next.js v16 Client Component restriction
|
|||
|
|
|
|||
|
|
If you encounter `Functions cannot be passed directly to Client Components` error from passing Next.js Link to Material UI `component` prop, you need to create a wrapper component with `use client` directive like the following:
|
|||
|
|
|
|||
|
|
```tsx title="src/components/Link.tsx"
|
|||
|
|
'use client';
|
|||
|
|
import Link, { LinkProps } from 'next/link';
|
|||
|
|
|
|||
|
|
export default Link;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Then, replace the Next.js Link with the wrapper component:
|
|||
|
|
|
|||
|
|
```diff title="src/app/page.tsx"
|
|||
|
|
- import Link from 'next/link';
|
|||
|
|
+ import Link from '../components/Link';
|
|||
|
|
...
|
|||
|
|
<Button component={Link} href="/about" variant="contained">
|
|||
|
|
Go to About Page
|
|||
|
|
</Button>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Pages Router
|
|||
|
|
|
|||
|
|
This section walks through the Material UI integration with the Next.js [Pages Router](https://nextjs.org/docs/pages/building-your-application), for both [Server-side Rendering](https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering) (SSR) and [Static Site Generation](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation) (SSG).
|
|||
|
|
|
|||
|
|
### Installing the dependencies
|
|||
|
|
|
|||
|
|
Start by ensuring that you already have `@mui/material` and `next` installed.
|
|||
|
|
Then, run one of the following commands to install the dependencies:
|
|||
|
|
|
|||
|
|
<codeblock storageKey="package-manager">
|
|||
|
|
|
|||
|
|
```bash npm
|
|||
|
|
npm install @mui/material-nextjs @emotion/cache @emotion/server
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash pnpm
|
|||
|
|
pnpm add @mui/material-nextjs @emotion/cache @emotion/server
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash yarn
|
|||
|
|
yarn add @mui/material-nextjs @emotion/cache @emotion/server
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
</codeblock>
|
|||
|
|
|
|||
|
|
### Configuration
|
|||
|
|
|
|||
|
|
Inside the `pages/_document.tsx` file:
|
|||
|
|
|
|||
|
|
- Import `documentGetInitialProps` and use it as the Document's `getInitialProps`.
|
|||
|
|
- Import `DocumentHeadTags` and render it inside the `<Head>`.
|
|||
|
|
|
|||
|
|
```diff title="pages/_document.tsx"
|
|||
|
|
+import {
|
|||
|
|
+ DocumentHeadTags,
|
|||
|
|
+ documentGetInitialProps,
|
|||
|
|
+} from '@mui/material-nextjs/v15-pagesRouter';
|
|||
|
|
// or `v1X-pagesRouter` if you are using Next.js v1X
|
|||
|
|
|
|||
|
|
export default function MyDocument(props) {
|
|||
|
|
return (
|
|||
|
|
<Html lang="en">
|
|||
|
|
<Head>
|
|||
|
|
+ <DocumentHeadTags {...props} />
|
|||
|
|
...
|
|||
|
|
</Head>
|
|||
|
|
<body>
|
|||
|
|
<Main />
|
|||
|
|
<NextScript />
|
|||
|
|
</body>
|
|||
|
|
</Html>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
+MyDocument.getInitialProps = async (ctx) => {
|
|||
|
|
+ const finalProps = await documentGetInitialProps(ctx);
|
|||
|
|
+ return finalProps;
|
|||
|
|
+};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Then, inside `pages/_app.tsx`, import the `AppCacheProvider` component and render it as the root element:
|
|||
|
|
|
|||
|
|
```diff title="pages/_app.tsx"
|
|||
|
|
+import { AppCacheProvider } from '@mui/material-nextjs/v15-pagesRouter';
|
|||
|
|
// Or `v1X-pages` if you are using Next.js v1X
|
|||
|
|
|
|||
|
|
export default function MyApp(props) {
|
|||
|
|
return (
|
|||
|
|
+ <AppCacheProvider {...props}>
|
|||
|
|
<Head>
|
|||
|
|
...
|
|||
|
|
</Head>
|
|||
|
|
...
|
|||
|
|
+ </AppCacheProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
:::info
|
|||
|
|
The `AppCacheProvider` component is responsible for collecting the CSS generated by MUI System on the server, as Next.js is rendering the .html page to the client.
|
|||
|
|
|
|||
|
|
While it's not required to use the `AppCacheProvider` component, it's recommended to use it to ensure that the styles are appended to the `<head>` and not rendering in the `<body>`.
|
|||
|
|
See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153 for why it's better.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
#### Custom cache (optional)
|
|||
|
|
|
|||
|
|
To use a custom [Emotion cache](https://emotion.sh/docs/@emotion/cache), pass it to the `emotionCache` property in `_document.tsx`:
|
|||
|
|
|
|||
|
|
```diff title="pages/_document.tsx"
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
MyDocument.getInitialProps = async (ctx) => {
|
|||
|
|
const finalProps = await documentGetInitialProps(ctx, {
|
|||
|
|
+ emotionCache: createCustomCache(),
|
|||
|
|
});
|
|||
|
|
return finalProps;
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Cascade layers (optional)
|
|||
|
|
|
|||
|
|
To enable [cascade layers](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Cascade_layers) (`@layer`), create a new cache with `enableCssLayer: true` and pass it to the `emotionCache` property in both `_document.tsx` and `_app.tsx`:
|
|||
|
|
|
|||
|
|
```diff title="pages/_document.tsx"
|
|||
|
|
+import { createEmotionCache } from '@mui/material-nextjs/v15-pagesRouter';
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
MyDocument.getInitialProps = async (ctx) => {
|
|||
|
|
const finalProps = await documentGetInitialProps(ctx, {
|
|||
|
|
+ emotionCache: createEmotionCache({ enableCssLayer: true }),
|
|||
|
|
});
|
|||
|
|
return finalProps;
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```diff title="pages/_app.tsx"
|
|||
|
|
+import { createEmotionCache } from '@mui/material-nextjs/v15-pagesRouter';
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
const clientCache = createEmotionCache({ enableCssLayer: true });
|
|||
|
|
|
|||
|
|
+ export default function MyApp({ emotionCache = clientCache }) {
|
|||
|
|
return (
|
|||
|
|
+ <AppCacheProvider emotionCache={emotionCache}>
|
|||
|
|
<Head>
|
|||
|
|
...
|
|||
|
|
</Head>
|
|||
|
|
...
|
|||
|
|
</AppCacheProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### App enhancement (optional)
|
|||
|
|
|
|||
|
|
Pass an array to the `plugins` property to enhance the app with additional features, like server-side-rendered styles if you're using JSS and styled-components.
|
|||
|
|
|
|||
|
|
Each plugin must have the following properties:
|
|||
|
|
|
|||
|
|
- `enhanceApp`: a higher-order component that receives the `App` component and returns a new app component.
|
|||
|
|
- `resolveProps`: a function that receives the initial props and returns a new props object.
|
|||
|
|
|
|||
|
|
When run, `enhanceApp` from each plugin is called first, from top to bottom, and then the process is repeated for `resolveProps`.
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { ServerStyleSheet } from 'styled-components';
|
|||
|
|
|
|||
|
|
MyDocument.getInitialProps = async (ctx) => {
|
|||
|
|
const jssSheets = new JSSServerStyleSheets();
|
|||
|
|
const styledComponentsSheet = new ServerStyleSheet();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const finalProps = await documentGetInitialProps(ctx, {
|
|||
|
|
emotionCache: createEmotionCache(),
|
|||
|
|
plugins: [
|
|||
|
|
{
|
|||
|
|
// styled-components
|
|||
|
|
enhanceApp: (App) => (props) =>
|
|||
|
|
styledComponentsSheet.collectStyles(<App {...props} />),
|
|||
|
|
resolveProps: async (initialProps) => ({
|
|||
|
|
...initialProps,
|
|||
|
|
styles: [
|
|||
|
|
styledComponentsSheet.getStyleElement(),
|
|||
|
|
...initialProps.styles,
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
// JSS
|
|||
|
|
enhanceApp: (App) => (props) => jssSheets.collect(<App {...props} />),
|
|||
|
|
resolveProps: async (initialProps) => {
|
|||
|
|
const css = jssSheets.toString();
|
|||
|
|
return {
|
|||
|
|
...initialProps,
|
|||
|
|
styles: [
|
|||
|
|
...initialProps.styles,
|
|||
|
|
<style
|
|||
|
|
id="jss-server-side"
|
|||
|
|
key="jss-server-side"
|
|||
|
|
// eslint-disable-next-line react/no-danger
|
|||
|
|
dangerouslySetInnerHTML={{ __html: css }}
|
|||
|
|
/>,
|
|||
|
|
<style id="insertion-point-jss" key="insertion-point-jss" />,
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
});
|
|||
|
|
return finalProps;
|
|||
|
|
} finally {
|
|||
|
|
styledComponentsSheet.seal();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### TypeScript
|
|||
|
|
|
|||
|
|
If you are using TypeScript, add `DocumentHeadTagsProps` to the Document's props interface:
|
|||
|
|
|
|||
|
|
```diff
|
|||
|
|
+import type { DocumentHeadTagsProps } from '@mui/material-nextjs/v15-pagesRouter';
|
|||
|
|
// or `v1X-pagesRouter` if you are using Next.js v1X
|
|||
|
|
|
|||
|
|
+export default function MyDocument(props: DocumentProps & DocumentHeadTagsProps) {
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Font optimization
|
|||
|
|
|
|||
|
|
To integrate [Next.js font optimization](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) with Material UI, open `pages/_app.tsx` and create a theme using `var(--font-roboto)` as a value for the `typography.fontFamily` field.
|
|||
|
|
|
|||
|
|
```diff title="pages/_app.tsx"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Head from 'next/head';
|
|||
|
|
import { AppProps } from 'next/app';
|
|||
|
|
import { AppCacheProvider } from '@mui/material-nextjs/v15-pagesRouter';
|
|||
|
|
+import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|||
|
|
+import { Roboto } from 'next/font/google';
|
|||
|
|
|
|||
|
|
+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 MyApp(props: AppProps) {
|
|||
|
|
const { Component, pageProps } = props;
|
|||
|
|
return (
|
|||
|
|
<AppCacheProvider {...props}>
|
|||
|
|
<Head>...</Head>
|
|||
|
|
+ <ThemeProvider theme={theme}>
|
|||
|
|
+ <main className={roboto.variable}>
|
|||
|
|
<Component {...pageProps} />
|
|||
|
|
+ </main>
|
|||
|
|
+ </ThemeProvider>
|
|||
|
|
</AppCacheProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
To learn more about theming, check out the [Theming guide](/material-ui/customization/theming/).
|
|||
|
|
|
|||
|
|
### CSS theme variables
|
|||
|
|
|
|||
|
|
To use [CSS theme variables](/material-ui/customization/css-theme-variables/overview/), enable the `cssVariables` flag:
|
|||
|
|
|
|||
|
|
```diff title="src/theme.ts"
|
|||
|
|
'use client';
|
|||
|
|
const theme = createTheme({
|
|||
|
|
+ cssVariables: true,
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Learn more about [the advantages of CSS theme variables](/material-ui/customization/css-theme-variables/overview/#advantages) and how to [prevent SSR flickering](/material-ui/customization/css-theme-variables/configuration/#preventing-ssr-flickering).
|