Розробка мікрофронтендної архітектури веб-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка мікрофронтендної архітектури веб-застосунку
Складна
від 2 тижнів до 3 місяців
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка архітектури мікрофронтенд веб-додатків

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

Це виправдано, коли над одною програмою працюють 4+ команди, коли монолітна система стала вузьким місцем розгортання, або коли різні частини програми розвиваються у принципово різних темпах.

Що входить до роботи

Аналіз предметної області та кордонів між командами, вибір підходу інтеграції, проектування архітектури shell, налаштування спільних залежностей, дизайн-система, комунікація між мікрофронтендами, CI/CD-стратегія, документація.

Підходи інтеграції — порівняння

Підхід Build-time Run-time Ізоляція Складність
NPM packages Так Ні Ні Низька
Module Federation Ні Так Часткова Середня
iframes Ні Так Повна Низька
Web Components Ні Так CSS Середня
Single-SPA Ні Так Часткова Висока

Для більшості проектів оптимальні Module Federation (якщо все на React/Vue) або Single-SPA (якщо команди використовують різні фреймворки).

Крок 1 — Аналіз кордонів домену

Кордон мікрофронтенду — це кордон bounded context у бізнес-домені, а не технічна зручність:

E-commerce платформа:
  ├── Catalog Team     → /products, /categories, /search
  ├── Cart Team        → /cart, mini-cart widget
  ├── Checkout Team    → /checkout, /payment
  ├── Account Team     → /profile, /orders, /addresses
  └── Platform Team    → shell, auth, design system, analytics

Погане розділення: за технічним стеком (header/sidebar/content), за UI-компонентами, за шарами (API/state/view).

Крок 2 — Shell-додаток

Shell — тонка оболонка без бізнес-логіки. Відповідає лише за:

  • маршрутизацію верхнього рівня
  • завантаження мікрофронтендів
  • спільні сервіси (auth, analytics)
  • навігацію та layout
// apps/shell/src/App.tsx
import React, { Suspense, lazy } from 'react'
import { Routes, Route, Navigate } from 'react-router-dom'
import { Shell } from './components/Shell'
import { AuthGuard } from './guards/AuthGuard'

// lazy — завантажуємо тільки те, що потрібно
const CatalogApp = lazy(() => import('catalog/App'))
const CheckoutApp = lazy(() => import('checkout/App'))
const AccountApp = lazy(() => import('account/App'))

export function App() {
  return (
    <Shell>
      <Routes>
        <Route path="/" element={<Navigate to="/products" replace />} />
        <Route
          path="/products/*"
          element={
            <Suspense fallback={<AppSkeleton name="Каталог" />}>
              <CatalogApp />
            </Suspense>
          }
        />
        <Route
          path="/checkout/*"
          element={
            <AuthGuard>
              <Suspense fallback={<AppSkeleton name="Оформлення" />}>
                <CheckoutApp />
              </Suspense>
            </AuthGuard>
          }
        />
        <Route
          path="/account/*"
          element={
            <AuthGuard>
              <Suspense fallback={<AppSkeleton name="Особистий кабінет" />}>
                <AccountApp />
              </Suspense>
            </AuthGuard>
          }
        />
      </Routes>
    </Shell>
  )
}

Крок 3 — Спільні контракти

Мікрофронтенди спілкуються через контракти. Контракт потрібно версіонувати та підтримувати зворотну сумісність:

// packages/contracts/src/events.ts
// Типізовані события — публікуються як npm-пакет

export interface CartEvents {
  'cart:item-added': { productId: string; quantity: number; price: number }
  'cart:item-removed': { productId: string }
  'cart:cleared': Record<string, never>
  'cart:checkout-started': { cartTotal: number; itemCount: number }
}

export interface AuthEvents {
  'auth:login': { userId: string; roles: string[] }
  'auth:logout': Record<string, never>
  'auth:token-refreshed': { expiresAt: number }
}

// Типізований event bus
type AllEvents = CartEvents & AuthEvents

export class EventBus {
  private emitter = new EventTarget()

  emit<K extends keyof AllEvents>(event: K, detail: AllEvents[K]) {
    this.emitter.dispatchEvent(new CustomEvent(event as string, { detail }))
  }

  on<K extends keyof AllEvents>(event: K, handler: (detail: AllEvents[K]) => void) {
    const listener = (e: Event) => handler((e as CustomEvent).detail)
    this.emitter.addEventListener(event as string, listener)
    return () => this.emitter.removeEventListener(event as string, listener)
  }
}

export const eventBus = new EventBus()

Крок 4 — Спільні залежності та дизайн-система

Дизайн-система — окремий пакет, який все мікрофронтенди отримують як npm-залежність:

packages/
  ui/                   — компоненти, токени, іконки
    src/
      components/
      tokens/
      icons/
    package.json
  contracts/            — типи подій та інтерфейсів
  shared-config/        — eslint, tsconfig, prettier базові конфіги

У Module Federation UI-пакет робимо singleton: true — все мікрофронтенди використовують одну версію:

// у кожному webpack.config.js / vite.config.ts
shared: {
  '@company/ui': { singleton: true, requiredVersion: '^2.0.0' },
  react: { singleton: true },
  'react-dom': { singleton: true },
}

Крок 5 — Стратегія маршрутизації

Два підходи до маршрутизації:

Централізований (shell володіє маршрутами верхнього рівня):

shell:    /products → завантажує CatalogApp
catalog:  /products/:id, /products?search=

Делегований (кожен мікрофронтенд володіє своїми маршрутами):

shell:    /* → передає в CatalogApp або CheckoutApp
catalog:  обробляє /products/*, /categories/*

При використанні React Router рекомендую централізований: shell визначає верхній /products/*, всередині CatalogApp — свої вкладені Routes.

Крок 6 — Аутентифікація

Auth-логіка — в shell або в окремому auth-мікрофронтенді. Решта отримують тільки факт авторизації:

// packages/auth-client/src/index.ts
export interface AuthContext {
  user: User | null
  token: string | null
  isAuthenticated: boolean
  login: (credentials: Credentials) => Promise<void>
  logout: () => void
  hasPermission: (permission: string) => boolean
}

// AuthProvider в shell
export function AuthProvider({ children }: { children: React.ReactNode }) {
  // логіка зберігання токена, refresh, logout при 401
  const auth = useAuthState()

  return (
    <AuthContext.Provider value={auth}>
      {children}
    </AuthContext.Provider>
  )
}

// У мікрофронтенді — тільки useAuth()
// import { useAuth } from '@company/auth-client'
// const { user, hasPermission } = useAuth()

Крок 7 — Обробка деградації

Remote може бути недоступний. Shell повинен деградувати gracefully:

function withRemoteFallback<P extends object>(
  remoteLoader: () => Promise<{ default: React.ComponentType<P> }>,
  FallbackComponent: React.ComponentType
) {
  const Remote = lazy(remoteLoader)

  return function RemoteWithFallback(props: P) {
    return (
      <ErrorBoundary
        onError={(error) => {
          monitoring.captureException(error, { tags: { type: 'remote_load_failure' } })
        }}
        fallback={<FallbackComponent />}
      >
        <Suspense fallback={<Spinner />}>
          <Remote {...props} />
        </Suspense>
      </ErrorBoundary>
    )
  }
}

const CatalogApp = withRemoteFallback(
  () => import('catalog/App'),
  () => <ServiceUnavailable name="Каталог" />
)

Крок 8 — CI/CD стратегія

monorepo (або polyrepo):
  apps/catalog/     → pipeline → CDN catalog.example.com
  apps/checkout/    → pipeline → CDN checkout.example.com
  apps/shell/       → pipeline → CDN example.com

shell зберігає remoteEntry URL у конфігу, отриманому з сервера:
  /api/mf-config → { catalog: "https://catalog.example.com/...", ... }
# .github/workflows/catalog-deploy.yml
name: Catalog Deploy
on:
  push:
    branches: [main]
    paths: ['apps/catalog/**', 'packages/ui/**']

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cd apps/catalog && npm ci && npm test && npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to S3
        run: aws s3 sync apps/catalog/dist s3://$CATALOG_BUCKET --delete

      - name: Notify shell of new version
        run: |
          curl -X POST $SHELL_API/mf-config \
            -H "Authorization: Bearer $DEPLOY_TOKEN" \
            -d '{"catalog": "https://catalog.example.com/assets/remoteEntry.js"}'

Моніторинг мікрофронтендів

// Відстежуємо завантаження кожного remote
window.addEventListener('unhandledrejection', (e) => {
  if (e.reason?.message?.includes('Loading chunk')) {
    monitoring.track('remote_load_failure', {
      error: e.reason.message,
      remote: detectRemoteFromStack(e.reason.stack),
    })
  }
})

// Web Vitals за кожним мікрофронтендом
import { onLCP, onFID, onCLS } from 'web-vitals'
onLCP((metric) => analytics.track('LCP', { value: metric.value, remote: 'catalog' }))

Типові помилки при переході

Занадто дрібне розділення — мікрофронтенд з 3 компонентів не виправданий операційним навантаженням.

Немає контрактів — команди починають залежати від внутрішньої реалізації один одного.

Несинхронізовані версії спільних залежностей — два React на сторінці → два VDOM, баги з hooks, розпухлий bundle.

Прямі імпорти між мікрофронтендами — порушує ізоляцію та робить незалежний розпуск неможливим.

Що ми робимо

Проводимо аналіз кордонів домену разом з product/engineering командами, вибираємо підхід інтеграції, проектуємо shell та пакет контрактів, налаштовуємо Module Federation або Single-SPA, організовуємо CI/CD з незалежним розгортанням, налаштовуємо моніторинг remote-завантаження.

Терміни: 10–20 днів — архітектура, реалізація shell, налаштування 2–3 мікрофронтендів, CI/CD. Подальший перенос логіки — в рамках окремих завдань.