惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
W
WeLiveSecurity
Stack Overflow Blog
Stack Overflow Blog
L
LangChain Blog
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
Google DeepMind News
Google DeepMind News
F
Full Disclosure
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Register - Security
The Register - Security
G
Google Developers Blog
C
Check Point Blog
GbyAI
GbyAI
A
About on SuperTechFans
V
Vulnerabilities – Threatpost
T
The Blog of Author Tim Ferriss
T
Tor Project blog
AWS News Blog
AWS News Blog
Cyberwarzone
Cyberwarzone
C
CERT Recently Published Vulnerability Notes
MongoDB | Blog
MongoDB | Blog
Latest news
Latest news
aimingoo的专栏
aimingoo的专栏
U
Unit 42
Y
Y Combinator Blog
P
Privacy International News Feed
Cisco Talos Blog
Cisco Talos Blog
S
Securelist
S
Schneier on Security
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
C
Cisco Blogs
Webroot Blog
Webroot Blog
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
月光博客
月光博客
P
Privacy & Cybersecurity Law Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
罗磊的独立博客
Cloudbric
Cloudbric
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News: Ask HN
Hacker News: Ask HN
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 司徒正美
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Security Blog
Microsoft Security Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Build-time микрофронтенды, или делай проще
Александр Богданов · 2026-05-13 · via Все публикации подряд на Хабре

Средний

11 мин

11K

Привет, меня зовут Александр Богданов, я ведущий фронтенд-разработчик стрима «Программы лояльности» в MWS. Наша команда отвечает за развитие и поддержку продуктов МТС Premium и МТС Cashback. 

На 2025 год у нас было три активных пользовательских витрины, которые использовали разный стек, разные дизайн-системы, по-разному взаимодействовали с бэкенд-сервисами и имели еще целый набор мелких отличий. Из-за этого разработке приходилось поддерживать зоопарк решений и технологий, а также многократно повторять одни и те же действия на разных стеках. Высокие time-to-market и стоимость разработки одной фичи закономерно не устраивали бизнес, поэтому перед нами поставили задачу — перейти на архитектуру микрофронтендов.

В посте расскажу, как мы это сделали с помощью npm-пакетов и какие результаты получили.

Какие варианты реализации микрофронтендов мы рассматривали

После того как было принято решение о переходе на микрофронтенды, мы начали поиск вариантов, как будем его реализовывать. Нам было важно сохранить поддержку SSR и SEO и важно, чтобы разработка решения не заняла слишком много времени. Еще хотелось внедрить микрофронтенды в существующие витрины постепенно, а не ждать масштабного переезда на новое решение.

Сначала рассматривали Module Federation — о нем много статей, выступлений на конференциях, гайдов на YouTube и так далее. Но мы быстро поняли, что решение нам не подходит: возникли технические проблемы с SSR и SEO. Готовые решения работали плохо или не работали вообще, и стало понятно, что проблему нужно будет решать самостоятельно. А еще из доступных материалов мы поняли, что потребуется слишком много ресурсов: большинство выступлений коллег из других компаний начинались с рассказа об организации инфраструктурных команд. 

Поэтому начали смотреть решения микрофронтендов с поддержкой SSR. Так вышли на Single SPA, но в этом случае потребовался бы переезд на новый фреймворк. В наших внутренних и внешних проектах мы используем Next.js и не планировали отказываться от него ради микрофронтендов. Также в случае переезда мы не смогли бы внедрять микрофронтенды в витрины постепенно. Поэтому вариант с Single SPA тоже отвергли.

Build-time микрофронтенды 

В поисках решения мы наткнулись на термин build-time микрофронтенды — это ​​когда независимые части frontend интегрируются в единое приложение еще до его развертывания. Интеграция выполняется с помощью бандлера на этапе сборки приложения. В этом случае микрофронтендом выступает обычный npm-пакет, который реализует в себе отдельную фичу или страницу веб-приложения. 

Решение нам показалось простым в разработке, ведь мы продолжаем работать с привычными нам технологиями, отличие лишь в том, что теперь компоненты нужно будут разрабатывать в отдельных репозиториях, а не в одном. Сохраняется поддержка SSR и SEO, так как npm-пакеты не накладывают на это никаких ограничений. Не нужно менять CI/CD витрин, достаточно добавить сборку и публикацию npm-пакетов. А еще нет влияния на инструменты и мониторинги, используемые на проекте, например Sentry.

Об ограничениях мы тоже подумали. Сразу очевидно, что независимых релизов микрофронтендов мы не получим: для релиза новой версии npm-пакета всегда нужен будет релиз витрины. По этой же причине останутся и зависимости между командами при релизах витрины. В нашем случае релизы на проектах были не частыми — меньше одного в сутки, так что риски мы приняли и окончательно остановились на решении build-time.

Архитектура решения

Рассмотрим архитектуру решения, начнем с основных моментов. Различные файлы конфигурации и линтеров собраны в npm-пакеты, они составляют базу для конфигурации остальных элементов решения.

Затем идет блок инфраструктуры. В него входят UI-kit, реализованный на React и TailwindCSS, npm-пакет взаимодействия с backend на основе Apollo Client и шина данных для общения между микрофронтендами и root-приложением.

Теперь сами микрофронтенды, которые мы в команде называем виджетами. Виджет в своей реализации полагается на инфраструктуру и конфигурацию и использует npm-пакет widget-builder, который хранит в себе конфигурации для сборки и разработки виджета. Для удобства создания виджета был разработан пакет-утилита widget-creator.

Готовые виджеты интегрируются в root-приложение. Root-приложение также использует инфраструктуру решения и конфигурации в качестве базовых настроек, которые расширяются настройками фреймворка Next.js.

Теперь рассмотрим элементы архитектуры подробнее.

TailwindCSS

Для реализации стилей в UI-kit, виджетах и root-приложениях был выбран TailwindCSS. Мы в команде рассмотрели все варианты реализации стилей и в итоге сошлись на TailwindCSS, и вот почему. 

TailwindCSS отлично подходит именно для решения build-time микрофронтендов, так как финальная сборка стилей будет происходить именно в root-приложении. Фреймворк при сборке опирается на стили, которые имеются в исходном коде root-приложения и виджетов и учитывает tree-shaking. Результатом такой сборки будет набор CSS классов без дублирования и без мертвого кода — только то, что нужно для стилизации витрины.

import path from "path"

export default {
	presets: [require("@cashback/tailwind-config/base")],
	content: [
		path.join(path.dirname(require.resolve("@cashback/indigo-ui")), "**/*.js"),
		path.join(path.dirname(require.resolve("@cashback/balance")), "**/*.js"),
		path.join(path.dirname(require.resolve("@cashback/user-settings-widget")), "**/*.js"),
		path.join(path.dirname(require.resolve("@cashback/registration-widget")), "**/*.js"),
		path.join(path.dirname(require.resolve("@cashback/banner-widget")), "**/*.js"),
		path.join(path.dirname(require.resolve("@cashback/mgts-widget")), "**/*.js"),
		path.join(path.dirname(require.resolve("@cashback/ondemand-widget")), "**/*.js"),
		"./src/**/*.{ts,tsx}",
	],
}

Также конфигурацию стилей TailwindCSS можно разместить в отдельном npm-пакете и шэрить между UI-kit, виджетами и root-приложениями. Конфигурации также подхватываются автокомплитом (требуются расширения) и eslint-плагинами.

Шина данных

В идеальном мире виджеты должны быть полностью независимы друг от друга, но на практике этого зачастую невозможно добиться и между ними возникает связность. Для общения между виджетом и виджетом или виджетом и root-приложением используется шина данных.

В качестве шины данных можно использовать любую библиотеку, реализующую паттерн «издатель — подписчик» (англ. publisher-subscriber). Мы выбрали библиотеку Postal.js, так как в ней имеется дополнительное свойство channel у события, что делает взаимодействие более гибким. Пришлось сделать форк, так как библиотека больше не поддерживается создателем и не имела поддержки Typescript, которая нам была нужна. 

Формат взаимодействия по шине данных выглядит так: в свойстве channel указываем имя виджета, который отправляет или принимает событие или имя служебного канала. Свойство topic содержит уникальное для виджета имя события, из-за наличия channel можно не бояться, что будут пересечения. При отправке события можно передать произвольные данные в поле data, для которого добавлена возможность типизации.

postal.publish<CityChangePayload>({
    channel: "user-settings",
    topic: "city.change",
    data: {
        city: {
          cityId: 1,
          cityName: "Москва"
        },
    },
})

По соглашению в команде каждый виджет обязательно должен реализовывать два события. События готовности виджета, виджет инициализирован и готов к работе и событие ошибки, работа или отображение виджета невозможна. 

К служебным каналам относятся каналы для отправки событий в МТС Аналитику, для событий в мониторинг Sentry и другие каналы общих механик.

Взаимодействие с бэкенд

Для взаимодействия нашего фронтенда с бэкенд-сервисами мы используем язык запросов GraphQL. На момент разработки виджетов на витринах уже использовалась библиотека Apollo Client, и мы решили сохранить это решение. Для унификации был создан npm-пакет, который содержит в себе функции создания экземпляра Apollo Client для виджетов и root-приложения.

Благодаря тому что Apollo Client существует внутри root-приложения в единственном экземпляре, запросы GraphQL кешируются и позволяют реализовать реактивный state, который используется как shared state. Например, если какой-то виджет получил актуальный баланс кешбэка пользователя, баланс обновится во всех виджетах.

Следует отметить, что использование shared state в архитектуре микрофронтендов имеет свои плюсы и минусы, так как есть опасность рассинхронизации данных в shared state. В нашем случае в shared state хранятся только сырые данные от backend, которые могут только актуализироваться и жестко связаны с схемой GraphQL, так что конфликты данных не возникают.

Создание и сборка виджета

Для быстрого старта разработки нового виджета, был сделан npm-пакет widget-creator, который устанавливается глобально на локальное окружение разработчика. Пакет позволяет создать шаблон виджета в заданной директории, который готов к запуску и разработке. Шаблон содержит в себе конфигурации, Hello World компонент и песочницу для разработки и тестирования.

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

Вся сборка происходит автоматически, реализован процесс CI/CD с набором необходимых quality-check. С локального окружения ничего не публикуем.

Работа с зависимостями

Внешние зависимости

Каждый виджет имеет продакшн-зависимости на инфраструктуру, общие UI-библиотеки и так далее. Если npm-пакеты просто собрать без дополнительных настроек и интегрировать в root-приложение, мы не просто увеличим вес продакшн-сборки, а получим нерабочее приложение, так как некоторые библиотеки не работают в нескольких экземплярах.

Для того чтобы этого избежать, нужно использовать внешние (external) зависимости при сборке виджета. 

const externals = [
    'react',
    'react-dom',
    'react-dom/client',
    'react/jsx-runtime',
    '@cashback/apollo-wrapper',
    '@cashback/indigo-ui',
    '@cashback/postal-premium',
    '@cashback/centrifuge-premium',
    '@apollo/client',
    'swiper/react',
    'class-variance-authority',
    'react-hook-form',
]

module.exports = { externals }

Во внешние зависимости добавляются React, элементы инфраструктуры, часто используемые UI-библиотеки. Для случаев, если один виджет имеет зависимость на другой, он также указывается в списке внешних зависимостей.

Одноранговые зависимости

В процессе разработки возникают ситуации, когда для виджета требуется доработка UI-kit. При интеграции такого виджета в root-приложение, требуется, чтобы в root-приложении стояла версия UI-kit, содержащая новые доработки, так как UI-kit — это внешняя зависимость. Для этого используются одноранговые (peer) зависимости в package.json, с их помощью можно указать, какие версии npm-пакетов инфраструктуры требуются виджету в root-приложении: 

{
  "peerDependencies": {
		"@apollo/client": "^3.12.4",
		"@cashback/apollo-wrapper": "^4.0.22",
		"@cashback/indigo-ui": "^3.0.264",
		"@cashback/postal-premium": "^2.0.24",
		"react": "^19.0.0",
		"react-dom": "^19.0.0"
	},
}

При установке виджета в root-приложение, если версия npm-пакета инфраструктуры не будет соответствовать нужной, пакетный менеджер выведет предупреждение:

Нужно отметить, что не существует аналога одноранговых зависимостей для dev-зависимостей. Если указать такую зависимость в списке одноранговых, то они могут попадать в директорию node_modules в продакшн-сборке root-приложения. Это не влияет на конечного пользователя, но увеличивает размер финального образа и может приводить к проблемам, если у вас настроены проверки на наличие CVE-уязвимостей в npm-пакетах.

Локальные зависимости

Для локальной разработки можно использовать версии зависимостей, которые развернуты локально. Можно подключать локальные изменения инфраструктуры в виджет или доработки виджета в root-приложение. 

Например, создать символическую ссылку c npm-пакета в node_modules на локально развернутую версию. Это можно сделать с помощью пакетного менеджера, которые поддерживают команду link.

npm link
yarn link
pnpm link

Существуют решения для организации локального репозитория npm-пакетов, например yalc. Они аналогично реализуют механизм символических ссылок, но имеют расширенный функционал управления ими.

Если все-таки нужно развернуть на тестовый стенд версию npm-пакета, который все еще в разработке, CI/CD реализует возможность сборки dev-версии из любой feature-ветки в Gitlab.

Песочница виджета

У каждого виджета есть своя песочница. Она может быть запущена локально и развернута на тестовый стенд. Песочница состоит из рабочей области, в которой размещаются компоненты виджета и debug-панели. 

Разработчик должен предусмотреть в debug-панели функции, которые позволяют воспроизвести все пользовательские пути виджета. Часть функций поставляется с шаблоном, часть дорабатывается под конкретный виджет в процессе разработки.

Мы используем песочницу виджета для разработки, тестирования, проведения дизайн-ревью и планируем запускать на ней end-2-end тесты.

Внедрение виджета

Процесс внедрения виджета выглядит следующим образом:

  1. Устанавливает npm-пакет виджета в root-приложение, по необходимости поднимает версии peer-зависимостей.

  2. Настраиваем конфигурацию: 

    • tailwind.config.js — чтобы учитывали стили виджета;

    • next.config.js — для сборки приложения;

    • в Apollo Client указываем url backend-сервисов, которые использует виджет.

  3. Внедряем виджет в витрину. Размещаем React-компонент в нужном месте в интерфейсе, выполняем связку событий шины данных и при необходимости настраиваем поддержку SSR.

import { MgtsWidget as Mgts, POSTAL_CONFIG } from "@cashback/mgts-widget"
import postal from "@cashback/postal-premium"
import { useEffect } from "react"
import { useObserveModal } from "@/modules/router"
import { refetchTransactionsPreviewData } from "@/widgets/transactions-preview-widget/helpers"

const MGTS_WIDGET_TEST_ID = "mgts-widget"
const MODAL_NAME = "mgts"

export const MgtsWidget = () => {
    // хук связывающий searchParams и открытие модального окна
	useObserveModal({
		modalName: MODAL_NAME,
		onOpen: () => {
			postal.publish({
				channel: POSTAL_CONFIG.channel,
				topic: POSTAL_CONFIG.topics.MGTS_OPEN,
			})
		},
	})

    // обновление данных при успешном пополнении счёта
	useEffect(() => {
		const onPaymentSuccess = postal.subscribe({
			channel: POSTAL_CONFIG.channel,
			topic: POSTAL_CONFIG.topics.payment_success,
			callback: () => {
				refetchTransactionsPreviewData()
			},
		})

		return () => {
			onPaymentSuccess.unsubscribe()
		}
	}, [])

	return <Mgts testId={MGTS_WIDGET_TEST_ID} />
}

Поддержка SSR реализуется так: виджет экспортирует функцию для выполнения GraphQL запроса на сервере. Функция хранится в npm-пакете виджета, чтобы не дублировать функционал на витрине и предотвратить случаи расхождения набора полей в запросе виджета и root-приложения. 

import { ApolloClient, NormalizedCacheObject } from "@apollo/client"
import { SOURCE_GRAPH_OBJ } from "@cashback/apollo-wrapper"
import { BannersDocument, BannersQueryResult, BannersQueryVariables } from "@graphql/banner/schema"

export const bannersServerQuery = (
	apollo: ApolloClient<NormalizedCacheObject>,
	variables?: BannersQueryVariables
) => {
	return apollo.query<BannersQueryResult>({
		query: BannersDocument,
		variables,
		context: { sourceGraph: SOURCE_GRAPH_OBJ.stories },
	})
}

Вызов запроса размещаем в функции getServerSideProps фреймворка Next.js и затем используем реализацию SSR-библиотеки Apollo Client. Результат выполненных на сервере запросов агрегатируется и передается на клиент, где при инициализации экземпляра используется Apollo Client, который и выполняет гидратацию. 

Проблемы, с которыми столкнулись

Основная проблема, с которой мы столкнулись, — это цепочки зависимостей. Рассмотрим пример, когда нам нужно добавить новый цвет для кнопки и использовать в виджете. Приходится делать следующее: добавить новый цвет в конфигурацию TailwindCSS, затем применить конфигурацию к UI-kit и доработать кнопку, применить новую версию UI-kit в виджете и в конце проинтегрировать обновленный виджет в root-приложение. Это технологическое ограничение работы с зависимостями, и от него, к сожалению, никак не уйти.

Другую проблему мы создали сами. Мы не уделили должного внимания шаблону виджета и кинулись в разработку, так как уже хотелось попробовать концепцию в действии. В процессе выяснилось, что мы не учли различные моменты реализации. Пришлось добавить или исправить их в шаблоне, но эти изменения не распространялись на уже сделанные виджеты. Из этого сделали вывод, что важно уделять больше внимания шаблонам проектов, ведь потраченное на них время в будущем сэкономит время на разработку.

Результаты

В декабре 2025 года мы выпустили в продакшн витрину экосистемного модуля МТС Cashback, которая была целиком выполнена на виджетах. Проект заменил собой старое решение на Svelte. По новой концепции багов найдено не было, процессы остались такими же, новая витрина просто встала на место старой.

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

Сейчас мы работаем над витриной МТС Cashback, которую решили переработать из-за большого количества legacy: берем виджеты экосистемного модуля и из них собираем новую витрину.

Концепция виджетов позволила решить существующие проблемы. Теперь:

  • бизнес рад, что сократился time-to-market;

  • разработчики унифицируют стек и подходы к разработке, поэтапно избавляются от legacy;

  • команды перестали сталкиваться между собой при разработке фичей и инфраструктура не отнимает много времени на поддержку. 

Так что у нас получилось рабочее решение на npm-пакетах, которое можно использовать в продакшне.