Module Federation и React: как делить frontend без хаоса

Практический разбор Module Federation в React: хост, удалённые модули, общие зависимости, болевые точки продакшена, производительность и сильный ответ на интервью.

28 апреля 2026 г.18 минLexicon Team

Введение

Тема Module Federation и React обычно всплывает не в учебных примерах, а в момент, когда один frontend перестает вписываться в ритм одной команды. Пока приложение можно развивать как модульный монолит, это почти всегда дешевле. Но когда каталог, биллинг, кабинет клиента и внутренняя админка начинают жить в разных циклах релизов, вопрос уже не только в структуре папок, а в способе независимой поставки кода.

Важно сразу развести два уровня разговора. Microfrontends отвечают на вопрос, зачем делить систему. Module Federation отвечает на вопрос, как технически подгружать и связывать эти части на runtime. Если нужен общий контекст об архитектурной стороне, сначала полезно прочитать материал про React microfrontends. Здесь сфокусируемся уже на механике: как работает host, что такое remotes, где ломаются shared-зависимости, как не уронить React-контекст и как объяснить это на интервью без общих фраз.

Больше вопросов в Telegram

Ежедневные разборы и реальные кейсы с интервью

Подписаться

Что такое Module Federation в React и какую проблему он решает

Module Federation позволяет одному приложению загружать модули другого во время выполнения. В React это обычно выглядит так: есть host или shell-приложение, а рядом существует несколько remote-модулей, которые экспортируют страницы, виджеты или крупные feature-блоки. Host знает, где их взять, и монтирует их в нужный маршрут или контейнер.

Практическая ценность не в самом факте динамического импорта. Динамический импорт и так есть. Ценность в другом:

  • remote может собираться и релизиться отдельно;
  • host не обязан пересобираться при каждом изменении remote;
  • зависимости можно делить через shared;
  • система может подключать модуль по URL без влияния на остальную часть приложения, если он недоступен.

Это особенно полезно в ландшафте, где frontend уже живет как платформа для нескольких продуктовых потоков. Но если команда просто не удерживает порядок внутри одного репозитория, Module Federation не исправляет архитектуру автоматически. В таком случае сначала полезнее укрепить границы внутри одного приложения, например через подходы из архитектуры больших React-приложений.

Архитектурный разбор: как устроена рабочая схема

Основные роли

Минимальная схема обычно состоит из четырех частей:

  1. host или shell, который поднимает приложение;
  2. remote-модули с отдельными доменными зонами;
  3. контракт интеграции: что remote экспортирует и какой контекст получает;
  4. политика shared-зависимостей, чтобы не размножать React и служебные библиотеки.

Поток загрузки

Рабочий путь запроса выглядит так:

  1. Пользователь открывает маршрут.
  2. Host определяет, какой remote нужен для этого экрана.
  3. Загружается remoteEntry.js или его аналог.
  4. Runtime договаривается о shared-зависимостях.
  5. Host запрашивает конкретный экспорт remote.
  6. React монтирует модуль внутри boundary и fallback-логики.

Критичная часть здесь не "умение подключить remote", а границы ответственности. Host должен владеть маршрутизацией верхнего уровня, авторизацией, feature flags, telemetry, error boundaries и базовым layout. Remote должен владеть своей доменной областью и не требовать прямого доступа к внутренностям соседних модулей.

Точка отказа и деградация

Если remote не загрузился, правильное поведение не равно "сломался весь экран". Нужны как минимум:

  • fallback на уровне маршрута или виджета;
  • логирование ошибки с указанием имени remote и версии;
  • возможность выключить модуль флагом;
  • понятный сценарий rollback только для проблемного remote.

Без этого Module Federation быстро превращается из механизма автономии в распределенную точку отказа.

Код-пример: конфигурация host для React

Ниже упрощенный пример host-конфигурации на Webpack:

// webpack.config.ts
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",
      remotes: {
        catalog: "catalog@https://cdn.example.com/catalog/remoteEntry.js",
        billing: "billing@https://cdn.example.com/billing/remoteEntry.js",
      },
      shared: {
        react: {
          singleton: true,
          requiredVersion: "^19.0.0",
        },
        "react-dom": {
          singleton: true,
          requiredVersion: "^19.0.0",
        },
        "react-router-dom": {
          singleton: true,
        },
      },
    }),
  ],
};

Здесь самое опасное место не URL remote, а shared-политика. Если react или react-dom не совпадают по ожиданиям и runtime не может договориться о единственном экземпляре, вы получите не просто красивую архитектурную ошибку, а хаотичные проблемы: сломанный контекст, странное поведение хуков, дублирование чанков и трудную отладку. Эта тема тесно связана и с оптимизацией бандла в React, потому что ошибка видна не только в стабильности, но и в весе клиентского JavaScript.

Код-пример: remote с явным публичным API

Remote лучше экспортировать не как россыпь внутренних файлов, а как небольшой публичный контракт:

// apps/catalog/webpack.config.ts
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "catalog",
      filename: "remoteEntry.js",
      exposes: {
        "./CatalogPage": "./src/pages/CatalogPage",
        "./mountCatalogWidget": "./src/bootstrap/mountCatalogWidget",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^19.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^19.0.0" },
      },
    }),
  ],
};

Хорошее правило простое: экспортируйте только то, что реально нужно host. Если remote начинает раскрывать внутренние hooks, store-модули, типы транспортных объектов и служебные адаптеры, интеграция теряет устойчивость. Любое локальное изменение превращается в скрытый breaking change.

Код-пример: безопасная загрузка remote в React

Обычно проблема не в успешном сценарии, а в том, как система ведет себя при сбое:

import { Suspense, lazy } from "react";

const CatalogPage = lazy(() => import("catalog/CatalogPage"));

export function CatalogRoute() {
  return (
    <RemoteBoundary remoteName="catalog">
      <Suspense fallback={<CatalogSkeleton />}>
        <CatalogPage />
      </Suspense>
    </RemoteBoundary>
  );
}

И boundary:

import { Component, ReactNode } from "react";

type Props = {
  remoteName: string;
  children: ReactNode;
};

type State = {
  hasError: boolean;
};

export class RemoteBoundary extends Component<Props, State> {
  state: State = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: unknown) {
    reportRemoteLoadFailure({
      remote: this.props.remoteName,
      error,
    });
  }

  render() {
    if (this.state.hasError) {
      return <RemoteFallback remoteName={this.props.remoteName} />;
    }

    return this.props.children;
  }
}

Такой код не делает архитектуру хорошей сам по себе, но убирает самый критичный класс отказов: падение одного remote не должно уничтожать весь пользовательский путь. Для более широкой темы диагностики React-ошибок полезен и материал про error boundaries в React.

Сравнение подходов: когда Module Federation лучше, а когда нет

КритерийModule FederationМодульный монолитiframe / жесткая изоляцияКогда выбирать
Независимость релизовВысокая при зрелом процессеНижеОчень высокаяFederation, если командам нужен свой темп поставки
Единый UX и shared stateРеализуемо, но требует дисциплиныПроще удерживатьСложнееМонолит, если главное - цельность интерфейса
Цена интеграцииВышеНижеОчень высокая по UXFederation, если автономия окупает интеграцию
Работа с React-зависимостямиНужна строгая политика sharedПрощеИзоляция снимает часть конфликтовМонолит, если нет боли с релизами
Деградация одного модуляВозможна локальноЧасто большой общий радиус отказаОбычно изолирована, но UX хужеFederation для управляемой частичной деградации
Локальная разработкаСложнее координация модулейПрощеРазрозненноМонолит для одной команды, Federation для платформенного ландшафта
ПроизводительностьЗависит от числа remotes и стратегии загрузкиПредсказуемееДороже по сети и памятиFederation только при ясной архитектурной выгоде

Главная мысль таблицы: Module Federation не "современная замена монолиту", а инструмент для другого класса задач. Он оправдан, когда стоимость общей кодовой базы и синхронных релизов уже выше стоимости интеграции.

Прокачай React за 7 дней

20 вопросов и разборов по React Hooks

Начать

Сложные моменты продакшена: где команды чаще всего ломают систему

1. Две копии React или критичных shared-пакетов

Симптомы:

  • растет размер чанков;
  • React DevTools показывает странное дерево;
  • контекст или router работают непредсказуемо;
  • ошибка воспроизводится только в интеграции, но не локально в remote.

Причина обычно в несовместимых версиях или неудачной настройке singleton. Проверять это нужно до релиза через анализ бандла и интеграционные стенды, а не после инцидента.

2. Host знает внутренности remote

Если host импортирует внутренние типы, лезет в сторы remote или начинает собирать UI из приватных модулей, формально Federation есть, а фактически автономии нет. Такое решение редко переживает несколько циклов изменений.

3. Нет стратегии недоступного remote

Самая неприятная production-ошибка здесь не падение само по себе, а отсутствие плана:

  • чем заменяется модуль;
  • какая метрика загорается;
  • кто получает алерт;
  • как быстро откатить только этот remote.

4. Разная observability в каждом модуле

Если shell пишет ошибки в одну систему, а remotes в три других, то сквозной путь пользователя распадается на куски. Минимум, который нужен почти всегда: единый traceId, единая схема ошибок и общий словарь бизнес-событий.

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

С Module Federation легко увлечься красивой схемой и недооценить runtime-цену. Основные издержки лежат в трех местах.

Сетевые накладные расходы

Каждый remote добавляет свои entry-файлы, manifest, чанки и вероятность лишнего round trip. На хорошем десктопном интернете это может быть незаметно. На мобильной сети или при холодном кеше цена уже чувствуется. Именно поэтому Federation плохо сочетается с избыточным дроблением на слишком мелкие remotes. Если граница модуля меньше, чем доменный выигрыш от автономии, вы платите сетевой задержкой за декоративную архитектуру.

Инициализация runtime и shared negotiation

Host и remote должны договориться о зависимостях, и это не бесплатная операция. Если продукт и так хорошо делится по маршрутам, иногда обычный code splitting и lazy loading в React дает почти ту же выгоду по загрузке при меньшей сложности интеграции.

Дублирование служебного кода

Даже при хорошей политике shared часть инфраструктуры все равно повторяется: bootstrap, адаптеры, telemetry hooks, локальные UI-обвязки. Это не всегда критично, но на крупных продуктах без контроля может раздувать память и объём передаваемых данных.

Когда оптимизация оправдана:

  • есть независимые продуктовые зоны;
  • remotes можно кешировать отдельно;
  • p95 загрузки измеряется и контролируется;
  • shell остается легким и не превращается во второй монолит.

Когда оптимизация преждевременна:

  • проблема команды не в релизах, а в плохих границах кода;
  • никто не меряет RUM, размер чанков и время интерактивности;
  • хочется "сделать как у больших компаний", но нет своей платформенной дисциплины.

Для поиска реального узкого места полезно держать рядом и React performance profiling: иначе легко начать оптимизировать архитектуру, когда лаг сидит в таблице, сети или тяжелом вычислении.

Практики, которые делают Module Federation управляемым

Держите host тонким

Host должен быть платформенным слоем, а не местом, куда стягивается бизнес-логика всех модулей. Его зона ответственности: router верхнего уровня, auth, layout, telemetry, feature flags, error handling.

Формализуйте публичный контракт remote

Хороший remote экспортирует несколько стабильных entry points, а не половину кодовой базы. Чем меньше публичная поверхность модуля, тем ниже вероятность тихих breaking changes.

Делайте shared-политику явной

Нельзя надеяться, что runtime сам "как-нибудь договорится". Для react, react-dom, router, UI-kit и инфраструктурных пакетов нужны явные правила версий, ownership и интеграционного тестирования.

Тестируйте не только remote, но и реальную сборку host + remote

Одна из частых ловушек в том, что каждый remote локально работает идеально, а вместе они ломаются. Нужен хотя бы один интеграционный контур, где проверяются:

  • загрузка remote;
  • работа shared-пакетов;
  • навигация;
  • деградация при ошибке;
  • сквозная телеметрия.

Планируйте rollout и rollback заранее

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

Частые ошибки

  • Выбирать Module Federation как лекарство от плохой модульности внутри одного приложения.
  • Делить систему по техническим слоям вместо доменных зон и зон ответственности.
  • Разрешать каждому remote тащить свою версию критичных библиотек.
  • Делать shell слишком умным и собирать в нем бизнес-оркестрацию всех модулей.
  • Не проверять поведение при недоступном remote.
  • Оценивать пользу только по скорости независимого релиза и не считать сетевую цену runtime.
  • Считать, что Vite, Webpack или Rspack сами по себе решают архитектурный вопрос.

Как отвечать на интервью

Сильный ответ по теме Module Federation и React обычно строится так:

  1. Сначала развести понятия: microfrontends - это архитектурный подход, Module Federation - механизм интеграции.
  2. Затем описать схему: есть host, есть remotes, есть shared-зависимости и публичные контракты модулей.
  3. После этого назвать компромиссы: сложнее версии зависимостей, наблюдаемость, локальная разработка, деградация и производительность.
  4. Добавить критерий применимости: подход окупается, когда нескольким командам реально нужна автономная поставка.
  5. Завершить сценарием отказа: что произойдет, если remote не загрузился или отдал несовместимый контракт.

Короткая рабочая формулировка может звучать так: "Я рассматриваю Module Federation в React как инструмент независимой поставки, а не как обязательную архитектуру для любого большого frontend. Он оправдан, когда есть host, четкие доменные remotes, строгая политика shared dependencies и понятная деградация при сбое. Если этих условий нет, чаще дешевле модульный монолит с хорошими границами и route-based splitting".

Такой ответ звучит сильнее, чем простое перечисление host, remoteEntry.js и singleton, потому что показывает понимание цены решения, а не просто знание терминов.

Потренируйте React-архитектуру и сборку на реальных интервью-кейсах

Платформа помогает разбирать Module Federation, microfrontends, bundling, производительность и архитектурные компромиссы так, как это обсуждают на технических собеседованиях и в production-командах

Начать практику

FAQ

Module Federation и microfrontends это одно и то же?

Нет. Microfrontends описывают способ делить продукт на автономные части. Module Federation описывает способ технически загружать и связывать эти части на runtime.

Можно ли использовать Module Federation без нескольких репозиториев?

Да. Многие команды используют его и в монорепозитории. Ключевой признак не число репозиториев, а независимость сборки, поставки и ownership.

Обязательно ли делить React как singleton?

В подавляющем большинстве практических сценариев да. Иначе вы рискуете получить дублирование runtime, проблемы с контекстом и трудные для диагностики ошибки интеграции.

Чем Module Federation отличается от обычного dynamic import?

Обычный dynamic import загружает код из того же build-графа. Module Federation позволяет подгружать модуль из отдельной сборки с собственным жизненным циклом и договариваться о разделяемых зависимостях.

Когда лучше не использовать Module Federation?

Когда у вас одна команда, общий цикл релизов, неустойчивые доменные границы и проблемы, которые пока можно решить нормальной модульностью, code splitting и договоренностями внутри одного приложения.

Итоги

Module Federation в React полезен не потому, что позволяет эффектно подключить remote по URL, а потому, что дает контролируемую независимую поставку в системах, где один frontend уже обслуживает несколько команд и доменных потоков. Его сила - в автономии. Его цена - в контрактах, shared-зависимостях, наблюдаемости и производительности runtime.

Если проекту не нужна эта автономия, почти всегда выгоднее оставить модульный монолит и усилить архитектурные границы внутри него. Если же релизная и организационная боль уже стала системной, Module Federation может быть очень рациональным инструментом, но только при строгой инженерной дисциплине вокруг host, remotes и деградации.

Больше вопросов в Telegram

Ежедневные разборы и реальные кейсы с интервью

Подписаться

Автор

Lexicon Team

Читайте также