Оптимизация для продакшена

Одной из ключевых задач при подготовке приложения на основе Material UI к продакшен-среде является уменьшение размера итогового JavaScript-бандла. Чем меньше вес загружаемых ресурсов, тем быстрее происходит загрузка приложения, особенно при медленном соединении.

Material UI содержит большое количество компонентов, утилит и вспомогательных функций. При некорректном импорте может происходить включение в бандл значительного количества неиспользуемого кода.

Tree Shaking

Современные сборщики (Webpack, Vite, Rollup, esbuild) поддерживают механизм tree shaking, позволяющий удалять неиспользуемые части библиотек. Для корректной работы необходимо использовать точечные импорты компонентов.

Неправильный вариант импорта:

import { Button, TextField } from '@mui/material';

Такой импорт может привести к включению в бандл дополнительных зависимостей.

Рекомендуемый вариант:

import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';

При таком подходе в бандл попадает только код конкретного компонента и его необходимых зависимостей.

Использование babel-плагинов

Для проектов, использующих Babel, может применяться плагин автоматической оптимизации импортов:

babel-plugin-import

Этот инструмент автоматически преобразует групповые импорты в точечные, снижая вероятность попадания лишнего кода в сборку.

Анализ размера бандла

Для оценки эффективности оптимизации применяются инструменты анализа:

  • webpack-bundle-analyzer
  • source-map-explorer
  • rollup-plugin-visualizer

Пример использования source-map-explorer:

npx source-map-explorer build/static/js/*.js

Анализ позволяет определить:

  • самые тяжёлые зависимости
  • повторяющиеся библиотеки
  • неиспользуемые модули

Code Splitting

Code Splitting — разделение кода на независимые части, которые загружаются только при необходимости. В больших приложениях это значительно ускоряет начальную загрузку.

Material UI не препятствует разделению кода, поэтому компоненты можно подгружать динамически.

Динамический импорт компонентов

React поддерживает ленивую загрузку через React.lazy.

import React, { Suspense } from 'react';

const SettingsPage = React.lazy(() => import('./pages/SettingsPage'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <SettingsPage />
    </Suspense>
  );
}

Компоненты Material UI внутри лениво загружаемых страниц также будут попадать только в соответствующий чанк.

Разделение по маршрутам

Наиболее распространённый подход — загрузка страниц по маршрутам.

const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));

В результате:

  • основной бандл содержит только базовую инфраструктуру
  • страницы подгружаются при переходе

Оптимизация иконок

Библиотека @mui/icons-material содержит тысячи SVG-иконок. Неправильный импорт может значительно увеличить размер бандла.

Точечный импорт

Неправильный вариант:

import { Delete, Edit } from '@mui/icons-material';

Правильный вариант:

import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';

В этом случае подключаются только используемые иконки.

Использование SVG напрямую

В проектах с высокой чувствительностью к размеру бандла можно отказаться от @mui/icons-material и использовать собственные SVG.

import { ReactComponent as Logo } from './icons/logo.svg';

Это уменьшает количество зависимостей и снижает размер JavaScript.


Оптимизация стилей

Material UI использует CSS-in-JS, основанный на библиотеке Emotion. При продакшен-сборке необходимо обеспечить правильную генерацию и кэширование стилей.

Production режим Emotion

Emotion автоматически переключается в оптимизированный режим при NODE_ENV=production.

Особенности production режима:

  • минимизированные имена классов
  • удаление debug-метаданных
  • оптимизация генерации CSS

Серверный рендеринг и извлечение CSS

При использовании SSR (например, с Next.js) важно извлекать стили на сервере, чтобы избежать Flash of Unstyled Content (FOUC).

Пример серверной конфигурации:

import createEmotionServer from '@emotion/server/create-instance';
import createCache from '@emotion/cache';

const cache = createCache({ key: 'css' });
const { extractCriticalToChunks, constructStyleTagsFromChunks } =
  createEmotionServer(cache);

После рендеринга HTML стили извлекаются и вставляются в <head> документа.


Кэширование темы

Тема Material UI создаётся функцией createTheme. Если она создаётся при каждом рендере, это приводит к лишним вычислениям и перерисовкам.

Использование memoization

import { createTheme, ThemeProvider } from '@mui/material/styles';
import { useMemo } from 'react';

function App() {
  const theme = useMemo(() => createTheme({
    palette: {
      mode: 'light'
    }
  }), []);

  return (
    <ThemeProvider theme={theme}>
      <Application />
    </ThemeProvider>
  );
}

Преимущества:

  • предотвращение повторного создания темы
  • уменьшение количества ререндеров

Уменьшение количества перерисовок

Некоторые компоненты Material UI являются сложными и могут вызывать значительные обновления DOM.

Использование React.memo

const UserCard = React.memo(function UserCard({ user }) {
  return (
    <Card>
      <CardContent>{user.name}</CardContent>
    </Card>
  );
});

React.memo предотвращает повторный рендер при неизменных пропсах.

Оптимизация списков

При отображении больших списков компонентов (List, Table) рекомендуется применять виртуализацию.

Популярные библиотеки:

  • react-window
  • react-virtualized

Пример с react-window:

import { FixedSizeList } from 'react-window';

function Row({ index, style }) {
  return (
    <div style={style}>
      Item {index}
    </div>
  );
}

Это позволяет отображать только видимые элементы списка.


Оптимизация таблиц

Компонент Table Material UI может становиться узким местом производительности при большом количестве строк.

Основные методы оптимизации:

  • виртуализация строк
  • пагинация
  • серверная фильтрация
  • серверная сортировка

Пример пагинации:

<TablePagination
  component="div"
  count={1000}
  page={page}
  rowsPerPage={rowsPerPage}
  onPageCha nge={handleChangePage}
/>

Таким образом одновременно рендерится ограниченное количество строк.


Lazy загрузка тяжелых компонентов

Некоторые компоненты Material UI имеют значительное количество зависимостей:

  • DatePicker
  • DataGrid
  • Autocomplete
  • Rich text editors

Загрузка таких компонентов может быть отложена.

const DataGrid = React.lazy(() => import('@mui/x-data-grid'));

Это уменьшает первоначальный размер бандла.


Production сборка

Финальная сборка должна выполняться в production режиме.

Для большинства инструментов используется переменная среды:

NODE_ENV=production

Production режим включает:

  • минификацию кода
  • удаление development-проверок
  • оптимизацию React
  • сжатие модулей

Пример сборки

Для проектов на Vite:

npm run build

Для Create React App:

npm run build

Результатом является папка build, содержащая оптимизированные ресурсы.


Сжатие ресурсов

После сборки ресурсы должны дополнительно сжиматься.

Наиболее распространённые алгоритмы:

  • Gzip
  • Brotli

Пример настройки для сервера Nginx:

gzip on;
gzip_types text/plain application/javascript text/css application/json;

Brotli обеспечивает ещё более эффективное сжатие, особенно для JavaScript.


Кэширование статических ресурсов

Правильная настройка HTTP-кэширования позволяет браузеру повторно использовать ранее загруженные файлы.

Material UI не требует специальных настроек, но рекомендуется использовать:

  • content hashing
  • long-term caching

Имена файлов при сборке обычно содержат хеш:

main.8a2f4c1.js

Это позволяет безопасно кэшировать файлы на длительный срок.

Пример HTTP-заголовков:

Cache-Control: public, max-age=31536000, immutable

Оптимизация шрифтов

Material UI по умолчанию использует шрифт Roboto. Неправильное подключение может замедлять загрузку страницы.

Самостоятельное хостингование шрифтов

Вместо загрузки из Google Fonts можно разместить файлы локально.

Преимущества:

  • контроль над кэшированием
  • отсутствие дополнительных DNS-запросов
  • улучшенная приватность

Использование font-display

@font-face {
  font-family: 'Roboto';
  src: url('/fonts/roboto.woff2') format('woff2');
  font-display: swap;
}

Это предотвращает блокировку рендеринга текста.


Проверка производительности

После всех оптимизаций важно измерить реальные показатели.

Основные инструменты:

  • Lighthouse
  • WebPageTest
  • Chrome DevTools Performance
  • Core Web Vitals

Особое внимание уделяется метрикам:

  • LCP (Largest Contentful Paint)
  • FID (First Input Delay)
  • CLS (Cumulative Layout Shift)

Material UI при корректной оптимизации не оказывает значительного влияния на эти показатели.


Контроль зависимостей

Крупные приложения со временем накапливают множество зависимостей, увеличивающих размер бандла.

Рекомендуется регулярно выполнять аудит:

npm ls

или

pnpm why

Это позволяет определить:

  • дублирующиеся версии библиотек
  • устаревшие пакеты
  • ненужные зависимости

Удаление лишних пакетов снижает размер финальной сборки и ускоряет загрузку приложения.