Оглавление
Когда я только начинал работать с фронтендом, micro-frontend звучал как buzzword из мира гигантов уровня Amazon или Netflix. Казалось: «Ну это же для тех, у кого тысячи разработчиков». Но после пары лет в команде, которая развивала продукт с десятками страниц и десятками разработчиков, я понял — micro-frontend реально решает проблемы, когда проект и команда растут.
В этой статье я разберу, когда MFE нужен, когда нет, и покажу реальные примеры на React с Module Federation и роутингом.
Почему вообще появляется интерес к micro-frontend?
Представьте: у вас есть огромный SPA на React. Команда выросла с 5 до 30 человек. Каждую неделю кто-то:
— меняет общие компоненты;
— обновляет библиотеки;
— выкатывает новые страницы.
В итоге:
— CI/CD стал занимать по 30–40 минут, потому что всё пересобирается;
— баг в корзине может «положить» весь сайт;
— даже маленькое изменение в личном кабинете требует релиза всей системы.
Знакомо? Именно в этот момент бизнес и разработчики начинают задумываться: «А может разделить приложение на независимые части, как это делают бэкендеры с микросервисами?»
Что такое micro-frontend?
Micro-frontend — это архитектурный подход, при котором одно большое приложение разбивается на несколько независимых фронтендов. Каждый из них:
— может иметь свой собственный стек (React, Vue, Angular);
— разрабатывается своей командой;
— деплоится независимо от остальных.
Module Federation: стандарт де-факто
До Webpack 5 мы мучились с iframe или динамическими import() из CDN. Сейчас почти все используют Module Federation.
Пример: Host + Remote
Host (основное приложение) — webpack.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
dashboard: 'dashboard@http://localhost:3001/remoteEntry.js',
cart: 'cart@http://localhost:3002/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
};
Remote (Dashboard) — webpack.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'dashboard',
filename: 'remoteEntry.js',
exposes: {
'./Dashboard': './src/Dashboard',
},
shared: ['react', 'react-dom'],
}),
],
};
Remote (Cart) — webpack.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'cart',
filename: 'remoteEntry.js',
exposes: {
'./Cart': './src/Cart',
},
shared: ['react', 'react-dom'],
}),
],
};
Пример React-роутинга для micro-frontend
Host (основное приложение)
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
const Dashboard = lazy(() => import('dashboard/Dashboard'));
const Cart = lazy(() => import('cart/Cart'));
function App() {
return (
<Router>
<nav>
<Link to="/">Главная</Link>
<Link to="/dashboard">Dashboard</Link>
<Link to="/cart">Cart</Link>
</nav>
<Suspense fallback={<div>Загрузка модуля...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard/*" element={<Dashboard />} />
<Route path="/cart/*" element={<Cart />} />
</Routes>
</Suspense>
</Router>
);
}
function Home() {
return <h1>Главная страница</h1>;
}
export default App;
Remote (Dashboard)
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
export default function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<nav>
<Link to="analytics">Analytics</Link>
<Link to="settings">Settings</Link>
</nav>
<Routes>
<Route path="analytics" element={<Analytics />} />
<Route path="settings" element={<Settings />} />
</Routes>
</div>
);
}
function Analytics() {
return <div>Страница Analytics</div>;
}
function Settings() {
return <div>Страница Settings</div>;
}
Remote (Cart)
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
export default function Cart() {
return (
<div>
<h2>Cart</h2>
<nav>
<Link to="items">Items</Link>
<Link to="checkout">Checkout</Link>
</nav>
<Routes>
<Route path="items" element={<Items />} />
<Route path="checkout" element={<Checkout />} />
</Routes>
</div>
);
}
function Items() {
return <div>Список товаров</div>;
}
function Checkout() {
return <div>Страница оформления заказа</div>;
}
Итог: у каждого remote модуля свой собственный роутинг, а host управляет только корневыми маршрутами (/dashboard и /cart), не зная деталей внутренней структуры модулей.
Когда micro-frontend реально нужен
Мой опыт показывает, что micro-frontend оправдан:
Большая команда (5+ отдельных команд)
У нас был проект, где одна команда делала CRM, другая — биллинг, третья — портал клиентов. Обновления в одной части не должны ломать другие.
Разные технологии
Например, вы переписываете Angular-приложение на React, но нужно продолжать поддерживать старый код. MFE позволяет плавно мигрировать.
Сложные домены
Когда у приложения несколько разных бизнес-направлений, которые живут своей жизнью.
Разный релизный цикл
У нас был модуль «Оплата», который обновлялся раз в месяц (жёсткие регуляции), и модуль «Маркетинг», который менялся чуть ли не каждый день. MFE позволил не блокировать друг друга.
Когда micro-frontend — это лишнее?
Micro-frontend — не серебряная пуля. Если:
— у вас одна команда;
— приложение небольшое (до 50–70k строк кода);
— нет планов использовать разные технологии
то MFE принесёт больше боли:
— сложнее локальная разработка (поднять несколько приложений одновременно);
— сложнее DevOps (нужна инфраструктура для сборки и деплоя модулей);
— увеличивается размер бандла, потому что каждая часть тянет свои зависимости.
Пример из практики
Я видел команду из 4 человек, которые пытались запустить MFE, чтобы «быть как в больших компаниях». В итоге:
— они тратили по часу на запуск локальной разработки;
— появились баги с версионированием зависимостей;
— процесс деплоя стал в 3 раза сложнее.
Через полгода они отказались от MFE и вернулись к классическому SPA.
Пример структуры проекта (React + Module Federation)
/host-app
├── src/
│ └── App.jsx
└── webpack.config.js
/dashboard-app
├── src/
│ └── Dashboard.jsx
└── webpack.config.js
/cart-app
├── src/
│ └── Cart.jsx
└── webpack.config.js
Каждый модуль запускается на своём порту:
— Host: localhost:3000
— Dashboard: localhost:3001
— Cart: localhost:3002
Host динамически подключает Dashboard и Cart как независимые модули.
Вывод
Micro-frontend архитектура — это не про модули ради модулей. Это про:
— независимые команды;
— независимые релизы;
— гибкость в технологиях.
Если ваш проект маленький или у вас одна команда — оставайтесь на обычном SPA.
Если же вы растёте и упираетесь в монолит — MFE может дать нужную гибкость, но вместе с этим придётся вкладываться в инфраструктуру и DevOps.

Комментарии: