Микрофронтенды: когда делить фронтенд между командами и как это сделать

Когда фронтенд-монолит мешает командам и когда хватит модульного монолита. Способы интеграции, Module Federation 2.0 и статус single-spa, границы по доменам, события между частями, дизайн-система, отказоустойчивость и постепенный переход.

Микрофронтенды — это способ разделить большой веб-интерфейс на части, которые разрабатывают, тестируют и выпускают разные команды независимо друг от друга. Пользователь видит один продукт, а внутри каталог, корзина, личный кабинет и админка живут в отдельных репозиториях или пакетах, собираются отдельными пайплайнами и обновляются в своём ритме. Подход решает организационную проблему — команды мешают друг другу в одном фронтенд-монолите, — но добавляет распределённую сложность: общие зависимости, согласованность дизайна, контракты между частями, мониторинг. Разберём, когда микрофронтенды оправданы, какие есть способы интеграции, что изменилось с выходом Module Federation 2.0, как проводить границы и как не превратить продукт в лоскутное одеяло. Когда фронтенд-монолит становится проблемой Одно приложение с общей сборкой хорошо работает, пока над ним трудится одна-две команды. Признаки того, что пора думать о разделении: Релизы синхронизированы принудительно. Команда оплаты не может выкатить исправление, пока команда каталога не доделала свою задачу в той же ветке. Сборка и проверки идут долго , и разработчики ждут пайплайн чаще, чем пишут код. Постоянные конфликты в общих файлах: маршрутизации, конфигурации, общих компонентах. Размыта ответственность. Непонятно, кто владеет конкретным разделом, и ошибки в нём неделями переходят из рук в руки. Нужно постепенно уйти со старого стека. Часть интерфейса написана на устаревшем фреймворке, и переписать всё разом нельзя. Если ни одного из этих признаков нет, микрофронтенды скорее навредят. Для команды до десяти фронтенд-разработчиков обычно достаточно хорошо устроенного модульного монолита: чёткие границы модулей внутри одного репозитория, правила зависимостей, монорепозиторий с Nx или Turborepo и кэшированием сборки. Способы интеграции На этапе сборки: пакеты Каждая часть публикуется как npm-пакет, основное приложение подключает их как зависимости. Это просто и не даёт накладных расходов во время работы, но независимого выпуска нет: чтобы обновить раздел, нужно пересобрать и выложить всё приложение. Подходит как промежуточный шаг и для разделения кода внутри одной команды. Iframe Самая сильная изоляция стилей и скриптов. Минусы тоже серьёзные: сложная коммуникация между фреймами, проблемы с адаптивной высотой, доступностью, навигацией и индексацией. Сегодня iframe оправдан в основном для встраивания сторонних или недоверенных приложений — платёжных форм, виджетов партнёров. Композиция на сервере Сервер или edge-прокси собирает страницу из HTML-фрагментов разных сервисов. Хорошо подходит для контентных страниц, где важны скорость первого показа и индексация: каждый фрагмент рендерится своим сервисом, а браузер получает готовую страницу. Во время работы в браузере Каждый микрофронтенд собирается и выкладывается отдельно, а оболочка — shell — загружает нужный модуль при переходе в раздел. Варианты реализации: Module Federation, нативные ES-модули с картами импорта (import maps поддерживаются всеми актуальными браузерами), фреймворк-оркестратор вроде single-spa, веб-компоненты как нейтральный контракт между разными фреймворками. Module Federation 2.0 Module Federation появился в webpack 5 в 2020 году и стал самым распространённым способом собирать микрофронтенды: приложение объявляет, какие модули отдаёт наружу, другое приложение подключает их во время работы, а общие зависимости вроде React загружаются один раз. Версия 2.0, которую развивает команда Web Infra из ByteDance, была представлена в апреле 2024 года, а в феврале 2026 года объявлена стабильной. Главные изменения по сравнению с исходной реализацией в webpack: Рантайм отделён от сборщика. Поддерживаются webpack, Rspack, Rsbuild, Rollup, Rolldown, Vite и Metro для React Native, есть интеграции с Modern.js, Next.js и Storybook. Манифест. Вместо одного remoteEntry.js приложение публикует mf-manifest.json с описанием модулей и зависимостей, что упрощает управление версиями и предзагрузку. Типы TypeScript для удалённых модулей подтягиваются автоматически, и контракты между командами проверяются компилятором. Рантайм-API для регистрации и загрузки удалённых модулей без пересборки оболочки — например, по конфигурации с сервера. Tree shaking общих зависимостей и расширение Chrome DevTools для просмотра графа модулей. Конфигурация удалённого приложения // webpack.config.js в микрофронтенде «catalog» const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack'); module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'catalog', exposes: { './CatalogApp': './src/CatalogApp', }, shared: { react: { singleton: true, requiredVersion: '^19.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^19.0.0' }, }, }), ], }; Оболочка // webpack.config.js в shell const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack'); module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'shell', remotes: { catalog: 'catalog@https://catalog.example.ru/mf-manifest.json', }, shared: { react: { singleton: true, requiredVersion: '^19.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^19.0.0' }, }, }), ], }; // App.tsx в shell import { lazy, Suspense } from 'react'; import { Routes, Route } from 'react-router-dom'; import { RemoteBoundary } from './RemoteBoundary'; const CatalogApp = lazy(() = import('catalog/CatalogApp')); export function App() { return ( Routes Route path="/catalog/*" element={ RemoteBoundary name="Каталог" Suspense fallback={ PageSkeleton / } CatalogApp / /Suspense /RemoteBoundary } / /Routes ); } Загрузка без пересборки оболочки Если список микрофронтендов и их адресов хранится в конфигурации на сервере, оболочка может регистрировать их во время работы через рантайм. import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'shell', remotes: [ { name: 'reports', entry: 'https://reports.example.ru/mf-manifest.json' }, ], }); const { ReportsApp } = await mf.loadRemote('reports/ReportsApp'); Так можно включать новые разделы для части пользователей или откатывать версию микрофронтенда, не трогая оболочку. single-spa: что с ним сейчас single-spa — один из первых фреймворков для микрофронтендов. Он управляет жизненным циклом приложений на разных фреймворках: регистрирует их, монтирует при переходе на нужный маршрут и размонтирует при уходе. Пакет по-прежнему широко используется, но последний релиз ядра, 6.0.3, вышел около двух лет назад. В феврале 2026 года участники сообщества открыли в репозитории обсуждение о статусе проекта: работа над версией 7.0 шла в 2025 году, но затем замедлилась, а совместимость с новыми версиями React, Angular и Vue вызывает вопросы. Для существующих проектов на single-spa это не повод срочно всё переделывать, но повод оценить риски. Для новых проектов, особенно когда все части пишутся на одном фреймворке, чаще выбирают Module Federation или композицию через маршрутизацию и ES-модули. Как проводить границы Самое дорогое решение в микрофронтендах — не выбор инструмента, а разрез продукта. Ошибку в границах исправлять намного дороже, чем поменять сборщик. По бизнес-доменам, а не по слоям. «Каталог», «Оформление заказа», «Личный кабинет», а не «команда компонентов» и «команда форм». Одна часть — одна команда-владелец. Совместное владение возвращает все проблемы монолита. Минимум синхронных зависимостей. Если команда не может выпустить свой раздел без согласования с другой, граница проведена неправильно. Размер, оправдывающий отдельный цикл выпуска. Микрофронтенд из пары экранов даёт инфраструктурную сложность без выгоды. Здесь работает закон Конвея, сформулированный Мелвином Конвеем в 1968 году: организации проектируют системы, повторяющие их структуру коммуникаций. Если команды устроены по-другому, чем задуманная архитектура, архитектура со временем подстроится под команды. Те же принципы разделения по доменам применяются на бэкенде — о них в статье «Microservices vs Monolith» . Коммуникация между частями URL Лучший общий источник состояния для навигации: выбранная категория, фильтры, идентификатор заказа. Микрофронтенды читают параметры адреса и не зависят друг от друга напрямую. Бонус — работающие ссылки и закладки. Браузерные события Для уведомлений между частями — «товар добавлен в корзину», «пользователь вышел» — подходят CustomEvent с пространством имён и описанным контрактом. // Контракт события, опубликованный в общем пакете типов export type CartItemAdded = { productId: string; quantity: number }; // Каталог публикует событие window.dispatchEvent( new CustomEvent CartItemAdded ('mfe:cart:item-added', { detail: { productId: 'sku-1024', quantity: 1 }, }) ); // Шапка оболочки подписывается window.addEventListener('mfe:cart:item-added', (event) = { const { quantity } = (event as CustomEvent CartItemAdded ).detail; cartBadge.increment(quantity); }); Контракт события — это публичный API. Изменение структуры без версии и уведомления других команд ломает продукт так же, как изменение REST-эндпоинта. Чего избегать Общий глобальный стор, к которому все части обращаются напрямую через window , создаёт скрытые зависимости хуже, чем в монолите. Если частям нужно много общего состояния, это сигнал, что граница проведена неудачно. Единый дизайн при разных командах Главный риск для пользователя — ощущение, что он переходит между разными сайтами: другие кнопки, отступы, поведение форм. Защита — дизайн-система как отдельный продукт. Дизайн-токены — цвета, типографика, отступы, радиусы в виде переменных, общих для дизайнеров и разработчиков. Библиотека компонентов с версионированием, которая подключается во все части как общая зависимость, а не копируется. Паттерны — формы, таблицы, пустые состояния, ошибки — с готовым поведением. Владелец и процесс: команда платформы, журнал изменений, правила обновления мажорных версий. Производительность Без дисциплины микрофронтенды замедляют продукт: несколько копий библиотек, каскад загрузок, разросшаяся оболочка. React и другие библиотеки с глобальным состоянием — только в одном экземпляре. Две копии React в одном документе ломают хуки с неочевидными ошибками, поэтому singleton и согласованная мажорная версия обязательны. Лёгкая оболочка. В ней навигация, авторизация и общие элементы, но не бизнес-логика разделов. Загрузка по маршрутам и предзагрузка вероятного следующего раздела в свободное время браузера. Кэширование: чанки с хешем в имени кэшируются надолго, манифест или точка входа — на короткий срок, чтобы оболочка быстро видела новые версии. Общий бюджет производительности. Метрики Core Web Vitals должны считаться для продукта целиком и по разделам. Как их измерять — в статье о производительности веб-приложений . Надёжность: что будет, если раздел не загрузился Удалённый модуль может быть недоступен: ошибка деплоя, проблема CDN, несовместимая версия. Оболочка должна показать понятное сообщение в пределах раздела, а не белый экран на весь продукт. // RemoteBoundary.tsx import { Component, type ReactNode } from 'react'; type Props = { name: string; children: ReactNode }; type State = { failed: boolean }; export class RemoteBoundary extends Component Props, State { state: State = { failed: false }; static getDerivedStateFromError(): State { return { failed: true }; } componentDidCatch(error: unknown) { reportError(error); // отправка в систему мониторинга } render() { if (this.state.failed) { return ( div role="alert" p Раздел «{this.props.name}» временно недоступен. /p button onClick={() = location.reload()} Обновить страницу /button /div ); } return this.props.children; } } Кроме того, нужны мониторинг ошибок с указанием микрофронтенда и версии, возможность быстро откатить конкретную часть и понятный процесс: кто дежурит, если сломался чужой раздел. Тестирование и выпуск Изолированные тесты каждой части: модульные и компонентные, в своём пайплайне. Контрактные тесты на события и общие интерфейсы: публикующая сторона проверяет формат, потребляющая — обработку. Для этого используют общие схемы типов или инструменты вроде Pa