Олег Марков
Микрофронтенды в Feature-Sliced Design - архитектура интеграция и практические примеры
Введение
Микрофронтенды и Feature-Sliced Design (FSD) решают схожую задачу — управлять сложностью больших фронтенд‑систем. Но делают это на разных уровнях.
- Микрофронтенды — про разделение приложения на независимые вертикальные модули.
- Feature-Sliced Design — про структурирование кода внутри одного фронтенда по слоям и фичам.
Смотрите, мы разберем, как объединить эти подходы: использовать FSD внутри каждого микрофронтенда и при этом выстроить понятные границы между ними. В итоге у вас будет архитектура, в которой:
- крупные части фронтенда разворачиваются и версионируются независимо;
- внутренняя структура каждой части остается предсказуемой и одинаковой;
- команды могут работать параллельно, не мешая друг другу.
Теперь давайте шаг за шагом разберем, как это устроено.
Что такое микрофронтенды и как они сочетаются с FSD
Кратко о микрофронтендах
Микрофронтенды — это подход, когда одно большое фронтенд‑приложение разбивается на набор более мелких, относительно независимых приложений. Чаще всего:
- у каждого микрофронтенда свой репозиторий;
- у него свой цикл релизов;
- он отвечает за свою часть пользовательского пути (например, каталог, корзина, профиль).
Главная идея — не держать весь фронтенд как монолит, а дать командам владеть своими областями.
Кратко о Feature-Sliced Design
Feature-Sliced Design — это методология структурирования фронтенда внутри одного приложения. Она делит код по слоям и слайсам:
- Слои: app, processes, pages, widgets, features, entities, shared.
- Слайсы: обычно по предметной области (user, cart, product, order и т.д.).
Например, структура на FSD в рамках одного приложения может выглядеть так:
- app
- pages
- widgets
- features
- entities
- shared
Внутри каждого слоя код разбит по слайсам. Идея в том, чтобы вы всегда понимали, куда положить новый код и где искать существующий.
Как совместить микрофронтенды и FSD
На высоком уровне картина такая:
- весь фронтенд разбит на несколько микрофронтендов (например, Shell, Catalog, Checkout);
- каждый микрофронтенд внутри построен по правилам Feature-Sliced Design;
- между микрофронтендами есть четкие контракты (API, события, маршруты).
Давайте посмотрим на возможную схему:
- shell (host) — отвечает за layout, маршрутизацию верхнего уровня, общие стили, авторизацию;
- mf-catalog — отвечает за просмотр каталога, фильтры, карточки товаров;
- mf-checkout — отвечает за корзину и оформление заказа;
- mf-profile — отвечает за личный кабинет.
И каждый из них внутри выглядит как обычное FSD‑приложение.
Гранулярность: что считать микрофронтендом, а что — фичей
Вертикальные микрофронтенды против горизонтальных слоев
Вам важно не путать уровни:
- Микрофронтенд — вертикальный модуль, который покрывает целый кусок пользовательского сценария.
- FSD‑фича — горизонтальный строительный блок внутри микрофронтенда.
Неподходящий вариант:
- делать микрофронтенд только под один маленький виджет;
- дублировать микрофронтенды под похожие задачи, которые лучше выразить через фичи.
Более удачный ориентир: один микрофронтенд — один крупный бизнес‑домэн или набор тесно связанных сценариев. Например:
- каталог и карточка товара могут жить в одном микрофронтенде;
- корзина и оформление заказа — в другом;
- профиль, заказы, настройки — в третьем.
Роли микрофронтендов и FSD
Смотрите, роли можно описать так:
Микрофронтенд отвечает за:
- набор страниц и маршрутов внутри своей зоны;
- собственный API и зависимость от backend;
- формат данных и контракты на границах с другими зонами.
FSD внутри микрофронтенда отвечает за:
- то, как код организован по слоям;
- как разделены UI, бизнес‑логика, состояние;
- как переиспользуются фичи и сущности.
Это помогает не путать, на каком уровне вы принимаете архитектурные решения.
Архитектура: Shell и независимые микрофронтенды
Shell (host) как корневое приложение
Чаще всего в схеме микрофронтендов есть Shell — корневое приложение, которое:
- отвечает за общую маршрутизацию;
- подгружает микрофронтенды;
- создает общий layout (шапка, подвал, side‑бар);
- может управлять авторизацией и глобальными настройками.
Внутри Shell мы тоже можем использовать FSD. Здесь уже свои слои:
- app — инициализация, корневые провайдеры;
- processes — например, процесс авторизации;
- pages — корневые страницы, каждая из которых встраивает микрофронтенд;
- widgets — общие части layout;
- shared — общие компоненты, стили, утилиты.
Пример структуры Shell‑приложения:
- app
- providers
- routing
- processes
- auth
- pages
- root
- 404
- widgets
- layout
- header
- sidebar
- shared
- ui
- lib
- config
Структура дочернего микрофронтенда в терминах FSD
Допустим, есть микрофронтенд каталога товаров. Теперь вы увидите, как это выглядит в коде на уровне структуры:
- app
- index.tsx // вход в микрофронтенд
- providers // локальные провайдеры
- pages
- catalog
- product-details
- widgets
- catalog-filters
- product-list
- product-recommendations
- features
- add-to-cart
- add-to-favorites
- product-compare
- entities
- product
- category
- shared
- ui
- lib
- api
- config
Здесь FSD помогает внутри микрофронтенда избегать хаоса. А сам микрофронтенд — всего лишь один из “участников” общей системы.
Варианты реализации микрофронтендов и их влияние на FSD
Основные варианты интеграции микрофронтендов
Есть несколько популярных способов сшить микрофронтенды:
- Module Federation (Webpack 5)
- Build-time интеграция (подмешивание бандлов на этапе сборки)
- Runtime загрузка через iframe
- Runtime загрузка через отдельные JS‑бандлы (например, через SystemJS или собственный загрузчик)
Чаще всего в SPA используют Module Federation, поэтому дальше будем опираться на него.
Module Federation и FSD
Module Federation позволяет одному Webpack‑приложению динамически подгружать модули из другого. Для FSD это удобно, потому что вы можете:
- экспонировать строго определенные entry‑модули поверх FSD‑структуры;
- не раскрывать внутренние детали слоев наружу;
- задать стандартизованный контракт: что именно видит Shell или другой микрофронтенд.
Здесь важно: не отдавать наружу произвольные сущности и фичи. Лучше экспонировать ровно то, что нужно для интеграции: страницы, виджеты, возможно — единичные функции.
Пример: Shell + микрофронтенд каталога через Module Federation
Конфигурация Shell (host)
Давайте разберемся на примере конфигурации Webpack для Shell.
webpack.config.js для Shell
// webpack.config.js в Shell (host)
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
// ...другая конфигурация
plugins: [
new ModuleFederationPlugin({
name: "shell", // имя текущего контейнера
remotes: {
// alias: "globalName@url/remoteEntry.js"
catalog: "catalogApp@https://cdn.example.com/catalog/remoteEntry.js",
},
shared: {
react: { singleton: true, eager: false },
"react-dom": { singleton: true, eager: false },
"react-router-dom": { singleton: true, eager: false },
},
}),
],
};
Комментарии:
- remotes.catalog — это удаленный микрофронтенд каталога;
- shared — общие зависимости, которые не нужно дублировать в каждом микрофронтенде.
Конфигурация микрофронтенда каталога (remote)
Теперь посмотрим на каталог.
// webpack.config.js в микрофронтенде каталога
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const path = require("path");
module.exports = {
// ...базовая конфигурация
output: {
publicPath: "https://cdn.example.com/catalog/", // базовый URL для статических файлов микрофронтенда
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
},
plugins: [
new ModuleFederationPlugin({
name: "catalogApp", // глобальное имя контейнера
filename: "remoteEntry.js", // входной файл для Module Federation
exposes: {
// Экспортируем страницы и корневой компонент маршрутов
"./routes": "./src/app/routes", // основной роутер микрофронтенда
"./CatalogPage": "./src/pages/catalog", // конкретная страница
},
shared: {
react: { singleton: true, eager: false },
"react-dom": { singleton: true, eager: false },
"react-router-dom": { singleton: true, eager: false },
},
}),
],
};
Комментарии:
- в exposes мы явно указываем, какие entry‑точки доступны извне;
- при этом внутренняя FSD‑структура (features, entities и т.д.) не “торчит” наружу.
Организация маршрутизации с учетом FSD
Теперь давайте посмотрим, как Shell подключает маршруты из микрофронтенда.
Импорт роутов из удаленного микрофронтенда
// src/app/providers/router.tsx в Shell
// Здесь мы подключаем роуты из микрофронтенда каталога
import React, { Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
// Динамический импорт роутера из удаленного микрофронтенда
const CatalogRoutes = React.lazy(
() => import("catalog/routes") // "catalog" — alias из remotes в Module Federation
);
export function AppRouter() {
return (
<BrowserRouter>
<Suspense fallback={<div>Загрузка...</div>}>
<Routes>
{/* Роуты Shell */}
<Route path="/" element={<div>Главная Shell</div>} />
{/* Встраиваем роуты каталога */}
<Route path="/catalog/*" element={<CatalogRoutes />} />
{/* Страница 404 */}
<Route path="*" element={<div>Страница не найдена</div>} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Комментарии:
- CatalogRoutes — это компонент, который определяет внутреннюю маршрутизацию микрофронтенда;
- Shell знает только о входной точке (routes), но не о внутренней структуре.
Реализация routes внутри микрофронтенда по FSD
Теперь вы увидите, как это выглядит в коде каталога.
// src/app/routes/index.tsx в микрофронтенде каталога
import React from "react";
import { Routes, Route } from "react-router-dom";
import { CatalogPage } from "@/pages/catalog";
import { ProductDetailsPage } from "@/pages/product-details";
// Здесь мы описываем только локальные маршруты микрофронтенда
export const CatalogRoutes = () => {
return (
<Routes>
<Route path="/" element={<CatalogPage />} />
<Route path=":productId" element={<ProductDetailsPage />} />
</Routes>
);
};
export default CatalogRoutes;
Комментарии:
- CatalogRoutes — внутренний роутер микрофронтенда, который встраивается в маршруты Shell;
- внутри мы используем обычные страницы, оформленные по слоям FSD.
Как FSD помогает держать границы микрофронтендов
Жесткие границы между микрофронтендами
Вы можете считать каждый микрофронтенд отдельным bounded context. FSD внутри помогает:
- не допускать “утечки” низкоуровневых сущностей наружу;
- формировать публичное API микрофронтенда поверх слоев.
Подход:
Внутри микрофронтенда:
- features, entities, shared — доступны только локально;
- app/pages/widgets используют их через понятные публичные интерфейсы.
Снаружи:
- Shell или другие микрофронтенды видят только то, что явно экспонировано (routes, отдельные компоненты).
Как выбрать, что именно экспонировать
Смотрите, есть несколько устойчивых вариантов:
- Экспортировать роуты микрофронтенда (как в примере выше).
- Экспортировать отдельные виджеты (например, мини‑корзину в шапке).
- Экспортировать функции для интеграции (например, инициализацию процесса авторизации).
Плохая идея:
- экспортировать “сырые” фичи или entities, которые не подготовлены как стабильный контракт;
- опираться на внутреннюю структуру (например, import "catalog/features/add-to-cart").
Лучше:
- описать отдельный публичный слой или модуль, который выступает фасадом для экспорта.
Например:
- external
- ui
- api
И уже из этого слоя экспонировать маршруты, виджеты и функции в Module Federation.
Общие библиотеки и shared‑слой: локальный и глобальный
Общий shared‑слой внутри микрофронтенда
Во‑первых, у каждого микрофронтенда есть свой shared‑слой, где живет:
- локальный UI‑кит (кнопки, поля ввода, модальные окна);
- утилиты, хелперы, форматирование;
- конфигурация (endpoints, фиче‑флаги);
- локальные адаптеры к API.
Пример структуры:
- shared
- ui
- button
- input
- modal
- lib
- date
- number
- api
- client.ts
- config
- index.ts
- ui
Этот shared не должен утекать наружу. Он служит только внутренним слоям микрофронтенда.
Глобальный shared‑пакет для всей системы
Во‑вторых, вы можете ввести глобальный shared‑пакет, который живет как отдельный npm‑пакет или репозиторий. В нем хранят:
- дизайн‑систему (универсальный UI‑кит);
- общие типы (например, User, Currency);
- общие утилиты, которые действительно универсальны.
Важно отличать:
- что действительно общее для всех микрофронтендов (и может жить в npm‑пакете);
- что специфично для конкретной доменной области, и должно оставаться внутри одного микрофронтенда.
FSD в этом помогает: все, что относится к конкретной предметной области, логично разместить в entities / features этого микрофронтенда, а не в глобальном shared.
Согласование навигации и состояний между микрофронтендами
Навигация: кто управляет URL
Практически всегда Shell должен быть владельцем глобальной навигации:
- он инициализирует BrowserRouter или другую систему роутинга;
- он определяет, какой микрофронтенд будет активен на каком префиксе пути;
- он может передавать параметры вниз.
Как видите, в примере мы использовали:
- Route path="/catalog/*" element={
}
Это говорит: любые пути, которые начинаются с /catalog, обрабатываются роутером каталога.
Передача параметров и контекста
Если Shell отвечает за авторизацию, настройки и т.п., то ему нужно передавать эти данные микрофронтендам. Есть несколько способов:
- Через props корневого компонента, который вы импортируете из микрофронтенда.
- Через контексты (React Context) с использованием shared зависимостей.
- Через глобальные события (custom events) или message bus.
Простой пример через props:
// Shell - пример передачи данных микрофронтенду
const CatalogRoutes = React.lazy(
() => import("catalog/routes")
);
export function AppRouter() {
const user = useCurrentUser(); // хук из Shell, который отдает текущего пользователя
return (
<BrowserRouter>
<Suspense fallback={<div>Загрузка...</div>}>
<Routes>
<Route
path="/catalog/*"
element={<CatalogRoutes currentUser={user} />}
/>
</Routes>
</Suspense>
</BrowserRouter>
);
}
И внутри каталога вы уже решаете, как использовать текущего пользователя.
Пример внутренней FSD‑структуры в микрофронтенде
Теперь давайте посмотрим, как в микрофронтенде каталога может быть устроена одна из страниц по FSD.
Entities: Product
// src/entities/product/model/types.ts
// Здесь мы описываем доменную сущность Product
export interface Product {
id: string;
title: string;
price: number;
imageUrl: string;
rating: number;
isFavorite: boolean;
}
// src/entities/product/api/getProducts.ts
// Здесь мы определяем запрос к API для получения списка продуктов
import { Product } from "../model/types";
import { apiClient } from "@/shared/api/client";
export async function getProducts(): Promise<Product[]> {
const response = await apiClient.get("/products");
return response.data as Product[];
}
// src/entities/product/ui/ProductCard.tsx
// Компонент карточки товара как часть слоя entities
import React from "react";
import { Product } from "../model/types";
import { Button } from "@/shared/ui/button";
interface ProductCardProps {
product: Product;
onAddToCart?: (product: Product) => void;
}
export const ProductCard: React.FC<ProductCardProps> = ({
product,
onAddToCart,
}) => {
const handleAddToCart = () => {
// Мы вызываем колбэк, если он передан
onAddToCart?.(product);
};
return (
<div>
<img src={product.imageUrl} alt={product.title} />
<div>{product.title}</div>
<div>{product.price} ₽</div>
<Button onClick={handleAddToCart}>В корзину</Button>
</div>
);
};
Комментарии:
- entities/product хранит типы, API и базовые UI‑компоненты, которые не знают о бизнес‑логике фич.
Features: добавление в корзину
// src/features/add-to-cart/model/store.ts
// Здесь мы описываем бизнес-логику добавления товара в корзину
import { createEvent, createStore } from "effector";
import { Product } from "@/entities/product/model/types";
export const addProductToCart = createEvent<Product>();
export const removeProductFromCart = createEvent<string>();
interface CartItem {
product: Product;
quantity: number;
}
interface CartState {
items: CartItem[];
}
const initialState: CartState = { items: [] };
export const $cart = createStore<CartState>(initialState)
.on(addProductToCart, (state, product) => {
// Здесь мы обновляем состояние корзины при добавлении товара
const existing = state.items.find(
(item) => item.product.id === product.id
);
if (existing) {
return {
...state,
items: state.items.map((item) =>
item.product.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
),
};
}
return {
...state,
items: [...state.items, { product, quantity: 1 }],
};
})
.on(removeProductFromCart, (state, productId) => ({
...state,
items: state.items.filter((item) => item.product.id !== productId),
}));
// src/features/add-to-cart/ui/AddToCartButton.tsx
// Кнопка добавления в корзину как часть фичи
import React from "react";
import { Product } from "@/entities/product/model/types";
import { Button } from "@/shared/ui/button";
import { addProductToCart } from "../model/store";
interface AddToCartButtonProps {
product: Product;
}
export const AddToCartButton: React.FC<AddToCartButtonProps> = ({
product,
}) => {
const handleClick = () => {
// Здесь мы диспатчим событие добавления товара в корзину
addProductToCart(product);
};
return <Button onClick={handleClick}>В корзину</Button>;
};
Комментарии:
- фича инкапсулирует бизнес‑логику и состояние;
- entities/product — чистые сущности;
- shared/ui — переиспользуемые компоненты.
Widgets и pages
// src/widgets/product-list/ui/ProductList.tsx
// Виджет списка продуктов
import React, { useEffect, useState } from "react";
import { Product } from "@/entities/product/model/types";
import { getProducts } from "@/entities/product/api/getProducts";
import { ProductCard } from "@/entities/product/ui/ProductCard";
import { AddToCartButton } from "@/features/add-to-cart/ui/AddToCartButton";
export const ProductList: React.FC = () => {
const [products, setProducts] = useState<Product[]>([]);
useEffect(() => {
// Здесь мы загружаем список продуктов при монтировании компонента
getProducts().then(setProducts);
}, []);
return (
<div>
{products.map((product) => (
<ProductCard
key={product.id}
product={product}
onAddToCart={() => {}}
>
{/* Вариант 1: можно композиционно подключать AddToCartButton */}
</ProductCard>
))}
</div>
);
};
// src/pages/catalog/ui/CatalogPage.tsx
// Страница каталога, собирающая виджеты
import React from "react";
import { ProductList } from "@/widgets/product-list/ui/ProductList";
export const CatalogPage: React.FC = () => {
return (
<div>
<h1>Каталог</h1>
{/* Здесь мы размещаем список товаров */}
<ProductList />
</div>
);
};
Комментарии:
- page собирает widgets;
- widgets собирают entities и features;
- все это живет внутри микрофронтенда каталога и не торчит наружу напрямую.
Интеграция нескольких микрофронтендов в общий UX
Общий layout и единый стиль
Одна из ключевых проблем микрофронтендов — “зашитые” внутри стили. FSD здесь помогает в комбинации с общей дизайн‑системой:
- выносите основу дизайн‑системы в отдельный shared‑пакет;
- подключаете его во всех микрофронтендах;
- в Shell создаете общие оболочки (header, sidebar) как widgets.
Например, в Shell:
- widgets
- layout
- MainLayout.tsx
- header
- footer
- layout
И уже в MainLayout вы встраиваете children‑контент из разных микрофронтендов.
Согласование состояния между микрофронтендами
Бывает, что корзина живет в одном микрофронтенде, а иконка мини‑корзины — в шапке Shell. Как им синхронизироваться?
Варианты:
- Вынести корзину в отдельный микрофронтенд, и Shell просто встраивает его виджет.
- Организовать общий event‑bus (например, на базе browser events).
- Использовать global store в shared‑пакете (осторожно, чтобы не вернуться к монолиту).
Более согласуется с идеями микрофронтендов: первый вариант — вынос отдельным микрофронтендом и встраивание его виджетов через Module Federation. Тогда:
- микрофронтенд Cart экспортирует MiniCartWidget;
- Shell импортирует его и размещает в шапке.
Организация команд и репозиториев
Один микрофронтенд — одна команда
Микрофронтенды логично связать с командами:
- каждая команда владеет одним или несколькими микрофронтендами;
- внутри придерживается FSD;
- сама решает, как эволюционировать внутри своей доменной области.
Плюсы:
- меньше конфликтов в коде;
- автономные релизы;
- понятные области ответственности.
Mono‑repo или multi‑repo
Есть два классических варианта:
- Multi‑repo: каждый микрофронтенд — отдельный репозиторий.
- Mono‑repo: все микрофронтенды и shared‑пакеты живут в одном репозитории.
FSD не привязан к способу: он про внутреннюю структуру. Но практические моменты:
- multi‑repo облегчает полную независимость, но усложняет синхронизацию общих зависимостей;
- mono‑repo упрощает единые версии пакетов, но требует строгой дисциплины и настроек CI/CD.
Типичные ошибки при сочетании микрофронтендов и FSD
Излишняя “микро‑гранулярность”
Ошибка:
- делать микрофронтенды слишком мелкими: отдельный микрофронтенд под одну страницу или виджет.
Лучше:
- использовать микрофронтенды для крупных доменных блоков;
- внутри уже дробить код по FSD.
Размытые границы ответственности
Ошибка:
- несколько микрофронтендов изменяют один и тот же кусок пользовательского сценария;
- логика заказов размазана по каталогу и чекауту.
Лучше:
- четко определить, кто владеет каким доменом;
- использовать FSD‑entities, чтобы показать принадлежность сущности к домену.
Случайное дублирование shared‑логики
Ошибка:
- каждая команда собирает свой “мини shared” с очень похожими компонентами;
- дизайн‑система начинает разъезжаться.
Решение:
- вынести общие элементы в отдельный shared‑пакет;
- сохранять минимальность этого пакета и четко формулировать критерии, что туда можно добавлять.
Заключение
Микрофронтенды и Feature-Sliced Design решают разные, но хорошо дополняющие друг друга задачи:
- микрофронтенды управляют границами между крупными частями системы и командами;
- FSD управляет внутренней структурой каждого микрофронтенда, упрощая навигацию по коду и развитие фич.
При грамотном сочетании вы получаете:
- независимые релизы и масштабирование по командам;
- предсказуемую структуру кода во всех микрофронтендах;
- понятные публичные контракты между частями системы.
Ключевые моменты:
- задавать границы микрофронтендов по доменам и пользовательским сценариям;
- использовать FSD‑слои и слайсы внутри каждого микрофронтенда;
- экспортировать наружу только тщательно продуманные entry‑точки (routes, виджеты, фасады API);
- централизовать действительно общие вещи в отдельном shared‑пакете.
Такой подход помогает выдерживать рост проекта, не превращая его ни в хрупкий монолит, ни в набор несвязанных “островков”.
Частозадаваемые технические вопросы
Как организовать тестирование FSD‑структуры в микрофронтендах отдельно от Shell
Используйте отдельный тестовый стенд для каждого микрофронтенда:
- В микрофронтенде поднимайте dev‑сервер, который не зависит от Shell, а использует локальный BrowserRouter.
- Пишите unit‑ и component‑тесты на уровне features, entities и shared.
- Для интеграционных тестов можно использовать отдельный “standalone” entry (например, app/standalone.tsx), который монтирует локальные страницы без Module Federation.
Как версионировать контракты между Shell и микрофронтендами
Создайте отдельный npm‑пакет с типами и интерфейсами контрактов:
- В пакете описывайте типы данных, события и протоколы взаимодействия.
- Shell и микрофронтенды зависят от этого пакета по конкретным версиям.
- При изменении контракта повышайте мажорную или минорную версию и планируйте миграцию.
Как поступать с глобальным состоянием авторизации если его нужно во всех микрофронтендах
Реализуйте авторизацию в Shell и передавайте данные вниз:
- В Shell храните токен и профиль пользователя.
- Прокидывайте user и функции обновления через props или React Context.
- В микрофронтендах используйте эти данные только как read‑only, не дублируя логику авторизации.
Как обеспечить изоляцию CSS между микрофронтендами
Используйте один из подходов:
- CSS‑modules или CSS‑in‑JS библиотеки (Emotion, styled‑components) для автоматического неймспейса.
- Префиксы классов для каждого микрофронтенда (например, .catalog‑btn).
- Общую дизайн‑систему для базовых компонентов и локальные стили только для специфики.
Как мигрировать существующий монолит на микрофронтенды с FSD
Двигайтесь поэтапно:
- Наведите порядок внутри монолита с помощью FSD — разделите слои и слайсы.
- Выделите один доменный блок (например, каталог) и оформите его как отдельное приложение, но пока в одном репозитории.
- Включите Module Federation, сделайте Shell и вынесите каталог как remote.
- Повторяйте для следующих доменов, постепенно опорожняя монолит.