Для использования Material-UI (MUI) в проектах с серверным рендерингом (SSR, Server-Side Rendering) необходимо обеспечить корректное управление стилями на сервере и клиенте. MUI использует механизм генерации CSS через Emotion (или JSS в старых версиях), что требует особого подхода при SSR, чтобы избежать несоответствия стилей между серверной и клиентской отрисовкой.
Для проекта на React с SSR (например, на Next.js или Express) потребуются следующие пакеты:
npm install @mui/material @emotion/react @emotion/server @emotion/styled
@mui/material – сама библиотека компонентов.@emotion/react – библиотека для работы с
CSS-in-JS.@emotion/server – инструменты для генерации критических
CSS на сервере.@emotion/styled – утилиты для стилизованных
компонентов.MUI рекомендует создавать отдельный Emotion cache для сервера и клиента. Это позволяет корректно собирать и инжектить стили.
// createEmotionCache.js
import createCache from '@emotion/cache';
export default function createEmotionCache() {
return createCache({ key: 'css', prepend: true });
}
key: 'css' определяет префикс для всех классов.prepend: true гарантирует, что стили Emotion будут
вставлены перед стилями других библиотек, предотвращая конфликт.Для сервера важно собрать все стили до отправки HTML клиенту. Пример на Express:
import { renderToString } from 'react-dom/server';
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import createEmotionCache from './createEmotionCache';
import App from './App';
app.get('*', (req, res) => {
const cache = createEmotionCache();
const { extractCriticalToChunks, constructStyleTagsFromChunks } = createEmotionServer(cache);
const html = renderToString(
<CacheProvider value={cache}>
<App />
</CacheProvider>
);
const emotionChunks = extractCriticalToChunks(html);
const emotionCss = constructStyleTagsFromChunks(emotionChunks);
res.send(`
<!DOCTYPE html>
<html lang="ru">
<head>
${emotionCss}
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
CacheProvider обеспечивает передачу единого cache для
компонентов MUI.extractCriticalToChunks и
constructStyleTagsFromChunks генерируют критические CSS для
серверного рендеринга.<style> до загрузки клиентского
JavaScript предотвращает FOUC (Flash of Unstyled
Content).На клиенте необходимо повторно использовать тот же cache для предотвращения повторного генерации CSS и конфликта классов:
import { CacheProvider } from '@emotion/react';
import createEmotionCache from './createEmotionCache';
import ReactDOM from 'react-dom';
import App from './App';
const cache = createEmotionCache();
ReactDOM.hydrate(
<CacheProvider value={cache}>
<App />
</CacheProvider>,
document.getElementById('root')
);
ReactDOM.hydrate используется вместо
render для сохранения состояния, сгенерированного на
сервере.Next.js имеет встроенные возможности SSR, поэтому интеграция с MUI немного отличается:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import createEmotionCache from '../src/createEmotionCache';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => <App emotionCache={cache} {...props} />,
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionCss = emotionStyles.styles.map(style => (
<style
key={style.key}
data-emotion={`${style.key} ${style.ids.join(' ')}`}
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
styles: [...initialProps.styles, ...emotionCss],
};
}
render() {
return (
<Html lang="ru">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
enhanceApp позволяет передать свой Emotion cache в
корневой компонент.<Head> на
этапе SSR.<style> в <head> до
<body> предотвращает FOUC.Для SSR важно также передавать тему MUI на сервер:
import { ThemeProvider, createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: { main: '#1976d2' },
secondary: { main: '#dc004e' },
},
});
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
GlobalStyles или
CssBaseline.<head> и
<style> предотвращает FOUC и ошибки при гидрации
компонентов.Настройка SSR с MUI позволяет создавать полностью серверно-рендеренные приложения с корректной загрузкой стилей, быстрой первоначальной отрисовкой и минимизацией визуальных сбоев при гидрации на клиенте.