Next.js интеграция

Установка и базовая настройка

Для интеграции 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, который позволяет избежать проблем с гидрацией и стилизацией на клиенте и сервере.

Настройка 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>, чтобы избежать конфликтов с глобальными стилями.

Настройка _app.js и Server-Side Rendering

Для корректного 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,
};
  • CacheProvider обеспечивает единую точку для стилей Emotion.
  • CssBaseline стандартизирует стили браузера.
  • ThemeProvider подключает глобальную тему для всех компонентов MUI.

Настройка темы

Файл 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;
  • palette определяет основные цвета.
  • typography задает шрифты по умолчанию.

Интеграция с SSR через _document.js

Чтобы 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],
  };
};
  • extractCriticalToChunks собирает критические CSS-стили для SSR.
  • dangerouslySetInnerHTML вставляет стили в <head> до рендеринга страницы.

Использование MUI компонентов в страницах Next.js

Пример страницы 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>
  );
}
  • Container упрощает разметку и центрирует контент.
  • Typography используется для заголовков и текста.
  • Button демонстрирует стилизованную кнопку с цветовой схемой темы.

Работа с динамическими стилями

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 с доступом к теме.
  • Позволяет создавать условные и адаптивные стили без CSS-файлов.

Адаптивный дизайн и темы

MUI полностью поддерживает breakpoints для адаптивного дизайна:

<Box
  sx={{
    width: { xs: '100%', sm: '50%', md: '25%' },
    bgcolor: 'secondary.main',
    p: 2,
  }}
>
  Адаптивный блок
</Box>
  • xs, sm, md, lg, xl — стандартные брейкпоинты темы.
  • Значения автоматически изменяются в зависимости от ширины экрана.

Интеграция с Next.js API и динамическими данными

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>
  );
}
  • List и ListItem создают структурированный вывод данных.
  • Сочетание SSR и MUI обеспечивает быстрый рендер на клиенте без миграции стилей.

Оптимизация и производительность

  • Использование SSR уменьшает FOUC (Flash of Unstyled Content).
  • Компоненты MUI поддерживают lazy-loading через dynamic импорт Next.js:
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('../components/Chart'), { ssr: false });
  • Это позволяет загружать тяжелые компоненты только на клиенте, экономя ресурсы сервера.

Итоговый паттерн интеграции

  1. Установить MUI и Emotion.
  2. Создать Emotion Cache.
  3. Настроить ThemeProvider и CssBaseline в _app.js.
  4. Настроить SSR через _document.js.
  5. Использовать sx и styled для динамических и адаптивных стилей.
  6. Подключать компоненты MUI на страницах и работать с данными через API Next.js.

Такой подход обеспечивает полноценную интеграцию MUI с Next.js, поддерживает серверный рендеринг, адаптивность, динамические стили и высокую производительность приложения.