Мікрофронтенди: переваги, підводні камені та практичні рішення

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Привіт, спільното, я Катя — розробниця в міжнародній IT-компанії ELEKS, де ми щодня працюємо над реальними викликами для реального бізнесу. Уже понад 13 років я в розробці — починала з фронтенду ще в ті часи, коли jQuery був на піку, а responsive дизайн тільки набирав популярності. З того часу змінилось багато: технології, підходи, та і я сама.

Зараз більшість моїх проєктів — це складні вебзастосунки, де я не лише відповідаю за фронт, а й дедалі частіше занурююся в бекенд. І, чесно кажучи, мені це навіть подобається: є щось захопливе в тому, щоб розуміти, як усе працює під капотом.

І сьогодні я хочу поговорити про мікрофронтенди — архітектуру, яка стала майже незамінною на великих проєктах, але водночас приносить чимало викликів.

Чому мікрофронтенди?

Мікрофронтенди — один зі шляхів створення великих вебзастосунків, де увесь застосунок розбивається на окремі логічні модулі (мікрофронтенди), де кожна частина відповідає за свій інтерфейс користувача.

Таким чином кожна команда працює незалежно. У них свій код, свої релізи, свій темп. І якщо структура проєкту чітко продумана, це реально працює: команди не заважають одна одній, швидше впроваджують нові фічі, і загалом розробка йде значно плавніше. Саме тому мікрофронтенди ідеально підходять для великих проєктів, де працює багато команд і де важливо зберігати гнучкість у розробці.

Але, як і з будь-яким іншими підходами, є й проблеми. Зокрема, при роботі з мікрофронтендами я стикнулася з такими викликами, як дублювання залежностей, що збільшує розмір бандла, наявність кількох лоадерів під час завантаження сторінки та труднощі з управлінням версіями бібліотек.

Проблеми при роботі з мікрофронтендами

Забагато лоадерів — забагато шуму

Одна з речей, яка може дратувати в мікрофронтендах, — це кілька лоадерів підряд. Уявіть: ви заходите на сторінку, чекаєте, поки все завантажиться... і от нарешті бачите перший лоадер. Потім — другий. А потім ще один. Кожен мікрофронтенд тягне за собою власні ресурси, показує свій індикатор завантаження — навіть якщо використовуються ті самі бібліотеки, як-от React. Один може бути на React 17, інший — на React 18, і замість того, щоб розділити ресурс, вони вантажать кожен своє. З боку користувача це виглядає як якесь вічне очікування або погано злагоджена система. Коли одночасно з’являється кілька різних спінерів — це збиває з пантелику, псує враження від продукту і створює відчуття, ніби кожна частинка сайту живе своїм окремим життям. І часто — на жаль — так воно і є.

Добра новина — рішення є. Навіть кілька.

Webpack Module Federation — поділись із сусідом. Щоб не вантажити одну й ту саму бібліотеку по кілька разів (наприклад, два різні React’и), можна використати Webpack Module Federation. Це такий спосіб «подружити» мікрофронтенди між собою і дозволити їм ділитися спільними залежностями. Тобто якщо один мікрофронтенд уже завантажив React, інші можуть ним скористатися, замість того щоб тягнути свою копію. Менше дублювання — швидше завантаження — щасливіший користувач.

Один лоадер на всіх — менше хаосу. Замість того, щоб кожен мікрофронтенд показував свій власний лоадер (а іноді й по кілька), можна винести лоадер на рівень контейнера чи основної оболонки. Тобто на сторінці буде лише один лоадер, який відповідає за завантаження всіх мікрофронтендів. Це менше дратує, виглядає краще й зрозуміліше. Плюс, якщо правильно налаштувати Webpack (наприклад, об’єднати лоадери JS, CSS, зображень), можна зменшити кількість запитів до сервера і зробити завантаження ще ефективнішим.

Lazy Loading + Code Splitting — тільки тоді, коли треба. Ще один класний підхід — не вантажити одразу весь світ. Lazy loading дозволяє підвантажувати компоненти тільки тоді, коли вони реально потрібні. У React це, наприклад, React.lazy() у парі з Suspense, який показує лоадер саме в той момент, коли компонент завантажується. А code splitting розбиває великий бандл на менші чанки, які теж підтягуються за потребою. Це дуже допомагає уникати перевантаження при першому відкритті сторінки.

Проблеми з управлінням версіями бібліотек у мікрофронтендах

Ще один головний біль у мікрофронтендах — це версії бібліотек. Одна команда оновила пекедж, а інша ні, а ще інша команда може мати свою улюблену (трішки застарілу) версію однієї й тієї ж бібліотеки. І коли все це збирається докупи в одному застосунку — починаються проблеми.

Що саме йде не так?

  • Конфлікти версій. Наприклад, один мікрофронтенд працює на React 17, а інший — уже на 18. І хоча на перший погляд все виглядає нормально, під капотом можуть бути несумісності — змінене API, інші підходи до рендерингу тощо. Це може вилізти несподівано і боляче.
  • Зайвий розмір бандлу. Якщо кожен мікрофронтенд тягне свою версію однієї й тієї ж бібліотеки — вітаю, у тебе зростає розмір бандлу, а разом з ним і час завантаження сторінки. А ще користувачі витрачають більше трафіку — не дуже приємно, особливо на мобільному.
  • Оновлення стає болючим. Якщо одна частина застосунку оновила залежність, а інша — ні, це може спричинити збої або непередбачувану поведінку. І що більше мікрофронтендів — то складніше все це координувати.

Як із цим впоратись?

  • Шеринг бібліотек через Module Federation. Один із найкращих варіантів — використання Webpack Module Federation. Це дозволяє не дублювати загальні залежності, а шарити їх між мікрофронтендами. Наприклад, React можна оголосити як «singleton», і він буде підвантажуватись тільки один раз. Це і економія трафіку, і мінімізація ризику конфліктів.
  • CDN для сторонніх бібліотек. Якщо йдеться про щось легке, як lodash чи dayjs — краще винести ці бібліотеки на CDN. Це спрощує кешування, пришвидшує завантаження і не вимагає зусиль з боку кожного окремого мікрофронтенда.
  • Регулярні оновлення + тести. Уникнути проблем із сумісністю можна, якщо оновлювати залежності регулярно, а не раз на рік перед релізом. І що важливо — мати покриття автоматизованими тестами, щоб одразу бачити, де щось «посипалось» після оновлення.
  • Ізоляція через контейнеризацію. Якщо зовсім не виходить домовитись про спільні версії — можна ізолювати мікрофронтенди, щоб вони не заважали один одному. Але тут важливо розуміти: це радше компроміс, а не ідеальне рішення, бо бандли все одно ростуть.

Архітектурна невизначеність — або коли все починає сипатися

Не всі проблеми в мікрофронтендах — це про технології. Дуже часто корінь усіх бід криється в архітектурі. Точніше — в її відсутності або нечіткості. Якщо з самого початку не визначити, хто за що відповідає, які частини UI розділяються, як вони мають взаємодіяти — далі буде хаос.

Коли кожна команда тягне у свою сторону, з часом виникає плутанина: компоненти починають дублюватися, з’являються приховані залежності, інтеграція стає болючою, а оновлення — ризикованим квестом.

Як цього уникнути?

  • Продумати архітектуру наперед. Це звучить банально, але насправді дуже важливо. Ще на етапі планування потрібно чітко розподілити, які зони інтерфейсу будуть окремими мікрофронтендами, які залежності будуть спільними, як проходитимуть оновлення, і хто за що відповідає. Добре працює створення архітектурного маніфесту або документа з правилами гри.
  • Налагодити комунікацію між мікрофронтендами. І не лише між командами, а й між самими частинами застосунку. Важливо мати єдиний механізм, через який мікрофронтенди можуть «спілкуватися» один з одним. Це можуть бути кастомні події (CustomEvent), глобальні state-менеджери, як-от Redux або Zustand, або навіть спільні API (наприклад, через message bus). Головне — уникати жорсткого зв’язування між модулями.

UI — коли кожен «малює» по-своєму

Ще одна проблема, яку часто можна зустріти у мікрофронтендах — це не технічна, а дизайнерська. Точніше — результат того, що кожна команда малює по-своєму. Бо «так гарніше», «у нас UX-експерт сказав так», або просто «ми не знали, що вже є готове рішення». У підсумку виходить, що одна таба виглядає так, інша — трошки по-іншому. Кнопки схожі, але не ідентичні. Відступи гуляють. Анімація — то є, то нема. І користувач, який переходить між частинами інтерфейсу, відчуває цю «дрібну несумісність» на рівні підсвідомості. І все — наче й не критично. Але загальне враження від продукту страждає. Виглядає так, ніби все зліплено з різних шматків, без єдиного стилю.

Як не перетворити застосунок на Frankenstein UI?

  • Дизайн-система — маст-хев. В ідеалі — спільна компонентна бібліотека. Щоб усі таби, кнопки, форми, спінери — були єдині, перевірені й повторно використовувані. Це може бути як внутрішній UI-kit, так і щось типу Storybook, де всі компоненти живуть і тестуються.
  • Обов’язкові гайдлайни. Недостатньо просто «домовитися». Треба мати чітко задокументовані правила: які кольори, які відступи, які шрифти, де і як використовуються. І бажано — з прикладами.
  • Дизайн-рев’ю між командами. Якщо є координація — менше шансів, що хтось знову «винаходить кнопку». Або хоча б це буде усвідомлений вибір, а не випадковість.

Отже, що ж допомагає тримати все під контролем у мікрофронтенд-проєкті?

Shared Dependencies — діліться, бо дублювання болить

Одна з найбільш поширених проблем — дублювання бібліотек. У кожного мікрофронтенда свої залежності, і якщо їх не шарити — все вантажиться по кілька разів. Рішення просте: централізовано керувати бібліотеками (наприклад, через Webpack Module Federation або monorepo). Це не тільки зменшує бандл, а й спрощує оновлення.

Module Federation — головний гравець у мікрофронтендах

Це вже майже стандарт. Module Federation дозволяє динамічно підключати мікрофронтенди та спільні залежності з інших частин системи або навіть з інших серверів. Плюс — менше дублювання, менше лоадерів, краща продуктивність.

Lazy Loading — вантажимо тільки те, що треба прямо зараз

Не треба на старті тягнути весь застосунок. Завдяки lazy loading мікрофронтенди підвантажуються лише тоді, коли користувач з ними взаємодіє. Це зменшує час початкового завантаження й дає відчуття «швидкості», навіть якщо застосунок великий.

Автоматизоване керування версіями — не вручну ж усе перевіряти

Інструменти типу npm workspaces, Yarn Workspaces або pnpm дають змогу централізовано оновлювати бібліотеки та уникати несумісностей між мікрофронтендами. Синхронізувати версії можна автоматично, без щотижневого «dependency day».

Tree Shaking + Code Splitting — тільки потрібний код у потрібний час

Щоб не вантажити зайвого, варто налаштувати tree shaking (видалення неактивного коду) і code splitting (розбиття на чанки). В результаті сторінка завантажується швидше, і користувач не чекає, поки скачається весь код світу.

Моніторинг та логування — бо все ламається

Жодна система не ідеальна. А в мікрофронтендах це особливо важливо: треба знати, яка саме частина дала збій. Тому APM (наприклад, Datadog, Sentry, New Relic) і логування з розбивкою по мікрофронтендах — це маст-хев, а не nice-to-have.

Єдина дизайн-система — щоб додаток не виглядав як колаж

Мікрофронтенди — це різні команди, різні підходи, і без єдиного дизайну все це починає виглядати... дивно. Якщо ви хочете, щоб продукт виглядав як одне ціле — створіть спільну дизайн-систему. Компоненти, шрифти, кольори, відступи — усе має бути узгоджено. Це сильно підтягує візуальну якість і економить час.

Висновок

Мікрофронтенди — це не магія, яка автоматично розв’язує всі проблеми масштабування. Це інструмент, і як будь-який інструмент, він вимагає продуманого підходу. Без чіткої архітектури, спільних правил, узгоджених залежностей і єдиного підходу до дизайну — замість гнучкої системи можна отримати хаотичну мозаїку.

Але якщо все зробити правильно — з продуманим шарингом, централізованим оновленням, лейзі лоадингом, моніторингом і спільною дизайн-системою — мікрофронтенди реально дають можливість масштабувати команди, пришвидшувати розробку і працювати незалежно, не заважаючи одне одному.

Ключ — у балансі свободи й узгодженості. І саме в цьому вся краса добре реалізованої мікрофронтенд-архітектури.

👍ПодобаєтьсяСподобалось19
До обраногоВ обраному5
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Shared Dependencies — діліться, бо дублювання болить

І більшість цих костилів це через те, що за 15 років в браузер так і не завезли нормальну модульну систему з інтеграцією npm як логічне продовження концепції cdn і пакетного менеджера. Це при тому що зараз на будь-якій лопаті в кармані 8 ядер та сотня гігабайт що виділити 1-2 гб на загальний модульний кеш не проблема, в який би запросто поміщалися прогрітими топ 25к пакетів та десятки їх версій.

Якщо без прив’язки до конкретики вебзастосунків -
то зараз схоже може розпочинатися зворотня тенденція — бо language package managers принесли стільки хаосу в тих кілотонах залежностей і як/де вони збираються, що напевно після того системна packaging system виглядає не такою вже й поганою в порівнянні.

p.s. і зустрічається доволі цікава аргументація — чому package managers (включно з building) інтегровані з мовою програмування — то не сама краща ідея в багатьох випадках.

І більшість цих костилів це через те, що за 15 років в браузер так і не завезли нормальну модульну систему з інтеграцією npm як логічне продовження концепції cdn і пакетного менеджера.

Вони там нафіг не потрібні. Вся проблема в тому, що сучасний розробник підходить до фронта як до JS-аплікухи, де треба писати код, а воно там якось саме складеться. Саме це є проблемою номер один, підхід code first.

Але інтерфейс — це не про запит даних по API та їх відображення. Це про компоненти, їхню поведінку, структуру та взаємодію між собою. Де беруться дані при цьому — не сильно важливо.

Катерино, мені написане цілком ок, нема до чого прискіпуватись. Проте не вистачає прикладів з кодом чи конфігами. Тобто наводячи той же випадок з React 17/18 показати «підключаємо один реакт ось так, імпортуємо ось тут». Або про самі мікрофронтенди: «на сторінці у нас дві аппки, ось тут карта з готелями, ось тут калькулятор цін оренди яхти, разом співіснує ось так»

Дякую, дуже влучне зауваження 🙌 Я свідомо не ділилась конкретними конфігами — проєкт комерційний, і мені не дуже ок публічно шарити такі речі. Хотілося радше передати загальну картину, як це працює на продакшні, а не робити «копіпаст». Дякую за комент! 🙏

Що скажете, має сенс з таким стеком стартувати новий проект? Я як архітектор порадив замовникам подивитись на підхід на веб компонентах, бо вебпак це щось з давно минулого як на мене, не підтримує збірку в ESM модулі і не буде.

Як на мене то краще рішення це те, де спільнота вже набила синці))
Так простіше буде як і вирішувати будь-які виклики на проєкті, так і наймати нових розробників. Плюс ніхто не відміняє допомогу AI і той самий Junior буде доволі ефективний на проєкті.

Веб компоненти чудовий варіант, але складний з боку стейт менеджменту.
Якщо казати за Build-time Integration, то це Nx. Дуже популярне і класне рішення.
З Server-side Template Composition не працював. Важко щось сказати без практичних кейсів. Але це дійсно Astro. Дуже багато чув про нього за останній рік. Але спробувати не було нагоди.

Все залежить від проєкту. Якщо це якесь MVP і є розуміння що клієнт піде заробляти гроші з ним і ще не дуже скоро повернеться, якщо взагалі повернеться, то я б ризикував і пробував щось нове))
Якщо це більш стабільний клієнт з довгостроковим проєктом, то найкращій вибір там, де вже існує дуже багато рішень і відомі всі мінуси цього підходу

Дякую за відповідь.

в міжнародній IT-компанії ELEKS, де ми щодня працюємо над реальними викликами для реального бізнесу.

Початок багатообіцяючий😁

Цікава стаття, дякую що поділилась досвідом!

Дякую, що прочитав! Рада, що було цікаво 😊

Хороша стаття, але дивно, що нічого не сказано про nx / lerna / rush / що там ще придумали для мікрофронтендів

Та, згодна — про NX/Lerna/Rush не згадала, бо фокус був на кейсі з ізольованими мікрофронтендами, без монорепо. Але так, у багатьох проєктах це дуже допомагає — особливо NX із кешуванням і генераціями. Можливо, зроблю окремий пост про монорепо + мікрофронтенди

Module Federation — головний гравець у мікрофронтендах

Які є ще ?

1) Server-side Template Composition — цей підхід передбачає, що сервер об’єднує різні мікрофронтенди в один HTML-документ перед відправкою його клієнту;
2) Build-time Integration — мікрофронтенди об’єднуються під час збірки додатку;
3) Run-time Integration via iframes — використання iframe для завантаження мікрофронтендів;
4) Run-time Integration via JavaScript — мікрофронтенди завантажуються та інтегруються за допомогою JavaScript під час виконання;
5) Web Components — використання веб-компонентів для створення ізольованих та повторно використовуваних частин інтерфейсу;
6) Module Federation — дозволяє динамічно завантажувати модулі з інших додатків під час виконання;

Мабуть ще у Вас спитаю, з чим з цього варто стартувати новий проект? Я вебкомпоненти порадив, але з іншими варіантами не працював. Можете навести посилання на якісь фреймворки/збирачі куди копнути наприклад для Build time чи Server side? Server side то мабуть Astro, він різні компоненти підтримує і island architecture в принципі добре для lazy load мікрофронтендів підходить

Давайте відповім вам у комменті вище, щоб там був тред з цією темою)

Підкажіть як ви організували шарінг даних необхідних для кожного мікро фронтенда ? Параметри користувача наприклад? Що користуєте для головного фронтенду( той що обʼєднує всі інші під собою ). Дякую

Ми зберігаємо дані користувача в shell-апліку (контейнері) — після логіну зчитуємо їх і передаємо мікрофронтендам через custom events або через shared state (типу Zustand). Інколи прокидуємо через props, якщо інтеграція тісна. Головне — shell ініціалізує все, і мікрофронтенди не ходять самі в auth.

гаразд, контейнер, тобто shell-аплік виконує логін або логаут користувача? Чи дивились готові рішення по типу single spa? І чи дозволяє конфігурація виконувати розробку окремо одного з мікрофронтедів? Як деплой менеджите?

Так, логін/логаут у нас відбуваються в shell-апліку(контейнері) — він зберігає токени, ініціалізує сесію і передає все мікрофронтендам. single-spa дивились, непогано підходить, коли треба міксувати різні фреймворки. Кожен мікрофронтенд можна запускати окремо на своєму девсервері, це зручно. Деплой автоматизований — у кожного мікрофронтенда свій пайплайн.

Гаразд а я у тоді відбувається запуск мікро фронта для розробки локально якщо все керується з shell?

Так, можна запускати мікрофронт локально навіть якщо все збирається через shell. Ми використовуємо import-map-overrides — це тулза, яка дозволяє підмінити окремий модуль на локальний. Просто запускаєш свій мікрофронтенд, обираєш його в UI цієї тулзи — і він підставляється на твій локальний. Працюєш із ним як з окремим додатком, все інше лишається як є. Дуже зручно і просто.

Ага
А як у Вас відбувається збірка модулів ( мікро фронтів ) під клієнта (юзера)? Чи є якась система підключення мікрофронта а по необхідності / якщо є права доступу ?

Після логіну ми отримуємо доступи користувача, і вже на їх основі вирішується, які мікрофронтенди підключати. Якщо прав немає — модуль взагалі не завантажується. Все працює через dynamic import, тож підтягується тільки те, що потрібно саме цьому юзеру.

Можете показати ? Цікавенько

На жаль, не можу показати — у нас усе досить строго з безпекою. Але можу порадити: спробуй зробити свій маленький pet-проєкт із мікрофронтендами — це реально найкращий спосіб зрозуміти, як все працює. Коли сам проходиш шлях від інтеграції до деплою, багато речей стають очевидними.

все одно не можу вточити для чого

import-map-overrides

? Мікрофронт повинен мати можливість бути запущеним як stand-alone додаток. Прокоментуй будь ласка

Так, абсолютно, мікрофронт повинен мати можливість працювати самостійно — і це в нас є. А import-map-overrides — це просто зручний спосіб вбудувати локально запущений модуль у shell, щоб перевірити взаємодію з іншими частинами. Це не заміна stand-alone режиму, а доповнення.

чому не vite? Webpack це слоняра. Збирає проект за трильярд секунд. А коли мені треба запустити 3 модулі поспіль — це біль. Люблю вайт але є відчуття що мозилла його не дуже розуміє, тому що на кожну сторінку видає нетворк ерор.

Бо у Vite ще не дуже підтримка Module Federation. Тому Webpack поки кращій вибір для мікрофронтенда. На жаль

Так і є — чекаємо, поки доросте. У Vite є плагін для Module Federation, але з великими проєктами він поки поводиться нестабільно. Loading....

В чому прояв цієї нестабільності ? Рік тому реалізували мікрофронтенд фреймворк з vite-plugin-federation, все працює.
За статтю дякую, згадав свій шлях при роботі з мікрофронтендами)

Дякую! 😊 У нас основні глюки починались на масштабі: різні фреймворки, React 17/18, shared залежності, які не завжди шарились коректно, і ще hot reload не завжди підхоплював зміни у dev-режимі. Плюс були нюанси з внутрішніми пакетами, типу UI-компонентів — іноді злітала синхронізація або конфліктували стилі. Тобто не те щоб «не працює», а радше нестабільно в edge-кейсах.

А ще не «вайт», а «віт». В них це на першій сторінці доки написано. Це слово з французької. Але то таке. Це я вже прискіпуюсь)))

О, та я теж так само спочатку казала «вайт», поки друг не підказав, що правильно «віт». Тепер вже звикла, хоч і звучить трохи непривично, зате правильно!

Ми перейшли на rspack замість webpack, фактично те ж апі, але все набагато швидше.

І як з ним?
Бо я якось збирав на ньому учбовий проєкт для студентів. І він мені дозволив в const obj присвоїти новий об’єкт без усяких помилок. Зробив теж саме на Webpack — одразу показало помилку, як і повинно бути. Тому поки на такому легкому прикладі в мене вже не дуже враження від Rspack

Спочатку зробили на ньому дев мод, хот релоад став швидшим десь в 5 разів (~15s > ~3s, проект дуже великий).
Прод білд на днях ввімкнули, збирається десь так само в 5 разів швидше, 40 секунд замість 180-200 локально. Для всіх основних плагінів є або сумісність або альтернативи. Загалом якщо задача мігрувати на щось швидше за вебпак без надзусиль і не переписувати весь конфіг на щось повністю інше типу parcel / vite то супер.
Єдине що в дев моді якийсь меморі лік є і він оперативу поступово підʼїдає, треба десь раз-два на робочий день вбивати процес і запускати заново. Цікаво, що коли він був в беті, такого не було, і почалось вже на версії 1.0+.

А вебпак це взагалі адіще, були на версії 5.85, апгрейдив сентрі на 8 версію — не збирається. Бампнув версію вебпака до останньої на той момент — виявилось, починаючи з 5.87 не збирається через іншу залежність, яку ми не могли теж бампнути, бо там в наступній мажорній версії критичний для нас баг. Так ми і були залочені на 5.86 бо це була єдина версія на якій все збиралось :) а з рспаком подібних проблем поки не спостерігалось

Так, я теж придивляюсь до Rspack — виглядає перспективно, особливо якщо врахувати швидкість збірки та підтримку Module Federation. Якщо API сумісне, то перехід має бути доволі painless. Думаю, спробую на одному з мікрофронтендів як тест.

Так, Webpack уже втомив :) Але проєкт стартував давно, і тоді це був дефолт. Через module federation міграція не така проста. Vite дуже подобається, ми вже пробуємо його в нових проєктах.

Та і у нас продукт досить великий, з купою залежностей і складною архітектурою. Ми дивились у бік Vite + MF, але поки що він не тягне такі масштаби — надто багато edge-кейсів. Тому залишились на Webpack, він хоч і повільніший, але стабільно працює в нашому випадку.

Підписатись на коментарі