Для интеграции MUI (Material-UI) с Next.js требуется установка необходимых пакетов:
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
@mui/material — основной пакет компонентов.@mui/icons-material — библиотека иконок.@emotion/react и @emotion/styled —
стилизация компонентов через Emotion.В Next.js версии 13+ с использованием App Router важно корректно настроить серверный рендеринг (SSR) для MUI. Для этого создается кастомный Emotion Cache, который позволяет избежать проблем с гидрацией и стилизацией на клиенте и сервере.
Создается отдельный файл createEmotionCache.js:
import createCache from '@emotion/cache';
export default function createEmotionCache() {
return createCache({ key: 'css', prepend: true });
}
key: 'css' — префикс для всех классов MUI.prepend: true — вставка стилей в начало
<head>, чтобы избежать конфликтов с глобальными
стилями.Для корректного SSR необходимо обернуть приложение в ThemeProvider и подключить CssBaseline:
import * as React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider, CssBaseline } from '@mui/material';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';
import { CacheProvider } from '@emotion/react';
const clientSideEmotionCache = createEmotionCache();
export default function MyApp(props) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
emotionCache: PropTypes.object,
pageProps: PropTypes.object.isRequired,
};
Файл theme.js:
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
typography: {
fontFamily: 'Roboto, Arial, sans-serif',
},
});
export default theme;
Чтобы MUI корректно работал на сервере и стили не мигрировали при гидрации, необходимо настроить кастомный Document:
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import createEmotionCache from '../src/createEmotionCache';
import theme from '../src/theme';
export default class MyDocument extends Document {
render() {
return (
<Html lang="ru">
<Head>
<meta name="theme-color" content={theme.palette.primary.main} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => <App emotionCache={cache} {...props} />,
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
key={style.key}
data-emotion={`${style.key} ${style.ids.join(' ')}`}
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
styles: [...React.Children.toArray(initialProps.styles), ...emotionStyleTags],
};
};
<head> до рендеринга страницы.Пример страницы index.js:
import React from 'react';
import { Button, Typography, Container } from '@mui/material';
export default function Home() {
return (
<Container maxWidth="sm" sx={{ textAlign: 'center', mt: 4 }}>
<Typography variant="h3" gutterBottom>
Добро пожаловать в MUI с Next.js
</Typography>
<Button variant="contained" color="primary">
Начать
</Button>
</Container>
);
}
MUI позволяет использовать sx и styled для
динамических стилей:
import { Box } from '@mui/material';
export default function DynamicBox({ isActive }) {
return (
<Box
sx={{
width: 200,
height: 100,
backgroundColor: isActive ? 'primary.main' : 'grey.300',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
Состояние: {isActive ? 'Активно' : 'Неактивно'}
</Box>
);
}
sx — мощный способ передавать стили inline с доступом к
теме.MUI полностью поддерживает breakpoints для адаптивного дизайна:
<Box
sx={{
width: { xs: '100%', sm: '50%', md: '25%' },
bgcolor: 'secondary.main',
p: 2,
}}
>
Адаптивный блок
</Box>
xs, sm, md, lg,
xl — стандартные брейкпоинты темы.MUI отлично комбинируется с динамическими данными в Next.js. Пример получения данных на сервере:
export async function getServerSideProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();
return {
props: { posts },
};
}
import { List, ListItem, ListItemText } from '@mui/material';
export default function Posts({ posts }) {
return (
<List>
{posts.slice(0, 10).map((post) => (
<ListItem key={post.id}>
<ListItemText primary={post.title} secondary={post.body} />
</ListItem>
))}
</List>
);
}
dynamic
импорт Next.js:import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('../components/Chart'), { ssr: false });
_app.js._document.js.sx и styled для динамических
и адаптивных стилей.Такой подход обеспечивает полноценную интеграцию MUI с Next.js, поддерживает серверный рендеринг, адаптивность, динамические стили и высокую производительность приложения.