Иконка подарка

Весенняя распродажа! Скидка 15% по промокоду

до 01.04.2026

Деплой Feature-Sliced Design проекта - практическое руководство

27 марта 2026
Автор

Олег Марков

Введение

Деплой проекта, построенного по подходу Feature-Sliced Design (FSD), на первый взгляд почти не отличается от развертывания обычного фронтенд-приложения. Но на практике архитектура FSD серьёзно влияет на то, как вы собираете проект, как прокидываете конфигурацию, как разделяете сборки по окружениям и как настраиваете CI/CD.

Давайте разберемся, какие особенности есть у деплоя FSD-проекта, на что стоит обратить внимание при настройке пайплайнов и конфигурации, и как избежать типичных проблем — от "сломанных" импортов слоёв до неправильных переменных окружения в проде.

В примерах я буду опираться на типичную связку:

  • React-приложение на FSD
  • Vite или Webpack как сборщик
  • Docker для контейнеризации (опционально)
  • GitHub Actions / GitLab CI как примеры CI/CD

Но общие идеи останутся полезными и в других связках.


Почему деплой FSD-проекта имеет особенности

Как архитектура влияет на процесс deployment

Feature-Sliced Design вводит несколько важных принципов:

  • разделение кода на слои (app, processes, pages, widgets, features, entities, shared);
  • строгая модульность и импорт только по разрешённым направлениям;
  • часто — использование алиасов (например @/shared, @/features).

На деплой это влияет так:

  1. Сборка должна уважать алиасы слоёв.
    Если при локальной разработке всё работает за счёт настроек Vite/Webpack/TS, на CI эти настройки должны быть полностью воспроизведены. Иначе при сборке вы получите ошибки "Module not found".

  2. Разные окружения могут включать/отключать фичи.
    Часто FSD-проект использует фиче-флаги или конфигурацию на уровне shared/config или app/config, и от окружения зависит, какие части включаются. Для этого нужны продуманные механизмы прокидывания переменных окружения.

  3. Несколько "входов" в приложение.
    В FSD может быть несколько entry-пойнтов (например, для админки и публичной части), или разные процессы в processes. Это может потребовать настройки множественных сборок или роутинга на уровне сервера.

  4. Чёткая разделимость слоёв упрощает деплой нескольких фронтов.
    Можно развернуть одну и ту же codebase с разной конфигурацией, отключая отдельные сущности или виджеты. Но тогда важно, чтобы процесс сборки был повторяемым и описан явно.

Теперь давайте пошагово посмотрим, как подготовить и реализовать deployment такого проекта.


Подготовка FSD-проекта к деплою

Базовая структура и точка входа

Обычно FSD-проект имеет структуру вида:

  • app
  • processes
  • pages
  • widgets
  • features
  • entities
  • shared

Смотрите, я покажу вам пример entry-файла, который часто лежит в src/app/index.tsx или src/app/main.tsx:

// src/app/index.tsx
import React from "react"
import { createRoot } from "react-dom/client"

// Импорт корневого компонента приложения из слоя app
import { App } from "./App"

// Импорт глобальных стилей из shared или app
import "@/shared/styles/global.css"

const container = document.getElementById("root")

// Проверяем, что контейнер найден
if (!container) {
  // В реальном проекте лучше кинуть ошибку или залогировать проблему
  throw new Error("Root container not found")
}

// Создаем корень React и монтируем приложение
const root = createRoot(container)
root.render(<App />)

Важно, чтобы в конфигурации сборщика именно этот файл выступал entry-пойнтом.

Настройка алиасов для слоёв

В FSD огромное значение имеет удобный импорт по алиасам: @/shared, @/entities и т.д.
Для deployment важно, чтобы:

  • сборщик понимал алиасы;
  • TypeScript тоже понимал те же пути;
  • тестовый раннер (Jest, Vitest) имел согласованную конфигурацию.

Пример настройки алиасов в Vite

Теперь давайте посмотрим на пример с Vite:

// vite.config.ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import path from "path"

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      // Здесь мы настраиваем пути для слоёв FSD
      "@": path.resolve(__dirname, "src"),
      "@app": path.resolve(__dirname, "src/app"),
      "@pages": path.resolve(__dirname, "src/pages"),
      "@widgets": path.resolve(__dirname, "src/widgets"),
      "@features": path.resolve(__dirname, "src/features"),
      "@entities": path.resolve(__dirname, "src/entities"),
      "@shared": path.resolve(__dirname, "src/shared"),
    },
  },
})

Комментарии в коде помогут вам вспомнить, что эти алиасы должны совпадать со структурой проекта.

Настройка путей в TypeScript

Чтобы TypeScript понимал те же алиасы, добавьте paths в tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@/*": ["*"],
      "@app/*": ["app/*"],
      "@pages/*": ["pages/*"],
      "@widgets/*": ["widgets/*"],
      "@features/*": ["features/*"],
      "@entities/*": ["entities/*"],
      "@shared/*": ["shared/*"]
    }
  }
}

Комментарии тут можно добавить в реальном файле, если вы используете tsconfig.base.json и отдельные конфиги для тестов.

Главное — следить за тем, чтобы конфигурации Vite/Webpack и TypeScript были синхронизированы. Иначе в CI сборка может проходить, а редактор — подсвечивать ошибки (или наоборот).


Конфигурация окружений для FSD-проекта

Где хранить конфигурацию в структуре FSD

Самый распространённый подход — хранить конфигурацию в shared/config или app/config.

Часто делают, например, так:

  • src/shared/config/env.ts — утилита для чтения переменных окружения;
  • src/shared/config/app-config.ts — агрегированная конфигурация приложения;
  • src/shared/config/feature-flags.ts — фиче-флаги.

Давайте разберемся на примере:

// src/shared/config/env.ts

// Здесь мы описываем тип окружения приложения
type AppEnv = "development" | "staging" | "production"

// Функция читает переменные окружения, которые подставит сборщик (Vite)
export const getEnv = () => {
  // Vite подставит значения из process.env при сборке
  const env = (import.meta.env.VITE_APP_ENV as AppEnv) || "development"

  return {
    env,
    apiUrl: import.meta.env.VITE_API_URL as string,
    // Можно добавить другие переменные по необходимости
    sentryDsn: import.meta.env.VITE_SENTRY_DSN as string | undefined,
  }
}

Комментарии показывают, как именно Vite подставит значения в момент сборки.

Использование конфигурации в слоях

Теперь давайте посмотрим, как использовать эту конфигурацию, например, в слое shared или entities:

// src/shared/api/base-api.ts
import axios from "axios"
import { getEnv } from "@/shared/config/env"

// Получаем конфигурацию окружения
const { apiUrl } = getEnv()

// Создаем базовый инстанс axios с нужным baseURL
export const apiClient = axios.create({
  baseURL: apiUrl,
  // Здесь же можно добавить таймауты, заголовки и т.д.
})

Такой подход помогает вам:

  • иметь одну точку входа для API-конфигурации;
  • легко переключать URL в зависимости от окружения;
  • не тянуть process.env напрямую в каждый модуль.

Сборка FSD-проекта под разные окружения

Сценарии сборки

Обычно в package.json FSD-проекта есть несколько скриптов:

  • сборка для продакшена;
  • сборка для staging;
  • сборка для dev (локальной разработки, но иногда отдельная сборка).

Пример:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:staging": "vite build --mode staging",
    "preview": "vite preview"
  }
}

Здесь ключевой момент — использование --mode, которое позволяет Vite подхватывать разные файлы переменных окружения (.env, .env.production, .env.staging и т.д.).

Файлы .env и их связь с Feature-Sliced Design

В FSD-архитектуре конфиг приложения обычно не размазан по коду, а централизован. Поэтому важно, чтобы .env корректно подхватывался и прокидывался именно в слой shared/config.

Структура может быть такой:

  • .env — базовые переменные для локальной разработки;
  • .env.production — переменные для продакшена;
  • .env.staging — для стейджинга.

Пример .env.production:

VITE_APP_ENV=production
VITE_API_URL=https://api.example.com
VITE_SENTRY_DSN=https://key@o0.ingest.sentry.io/0

Комментарии в .env вы тоже можете добавлять, чтобы объяснить коллегам, за что отвечает каждая переменная.


Контейнеризация FSD-проекта

Когда имеет смысл использовать Docker

Если вы деплоите фронтенд:

  • на Kubernetes;
  • в среду, где нужен единый образ;
  • или хотите стандартизировать окружение (Node, зависимости, build tools),

то контейнеризация FSD-проекта — удобный путь.

Архитектура FSD почти не меняет логику Dockerfile, но вы чаще будете:

  • использовать мультистейдж-сборку (отдельный этап build, отдельный — serve);
  • собирать проект однажды, а затем использовать артефакт в лёгком Nginx-образе.

Пример Dockerfile для FSD-приложения на Vite

Покажу вам, как это выглядит на практике:

# Этап 1 - сборка фронтенда
FROM node:20-alpine AS builder

# Устанавливаем рабочую директорию внутри контейнера
WORKDIR /app

# Копируем package.json и lock-файлы
COPY package*.json ./

# Устанавливаем зависимости
RUN npm ci

# Копируем весь исходный код проекта
COPY . .

# Устанавливаем переменную окружения для сборки
# Здесь можно использовать ARG и передавать значения при сборке
ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL}

# Запускаем сборку production-версии приложения
RUN npm run build

# Этап 2 - легковесный сервер для отдачи статики
FROM nginx:alpine

# Удаляем дефолтную конфигурацию nginx
RUN rm /etc/nginx/conf.d/default.conf

# Копируем нашу кастомную конфигурацию nginx
COPY ./.deploy/nginx.conf /etc/nginx/conf.d/default.conf

# Копируем собранные статические файлы из builder-образа
COPY --from=builder /app/dist /usr/share/nginx/html

# Указываем порт, который будет слушать контейнер
EXPOSE 80

# Стартуем nginx в форграунде
CMD ["nginx", "-g", "daemon off;"]

Комментарии в Dockerfile помогут вам понять, как каждый шаг связан с деплоем FSD-приложения.

Конфигурация nginx для SPA на FSD

Развертывание FSD-приложения в многодоменной архитектуре или с роутингом на стороне клиента обычно требует настройки fallback на index.html.

Вот пример простого конфига:

# .deploy/nginx.conf

server {
    listen       80;
    server_name  _;

    # Корень, куда мы скопировали статику
    root   /usr/share/nginx/html;
    index  index.html;

    # Отдача статических файлов
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Пример проброса запросов к API, если нужен прокси
    location /api/ {
        proxy_pass https://api.example.com;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Здесь FSD никак специально не "участвует", но важно настроить корректный try_files, чтобы роуты страниц (/profile, /dashboard) отдавали ваш SPA, а не 404.


Настройка CI/CD для FSD-проекта

Общая идея пайплайна

Типичный пайплайн деплоя для FSD-проекта:

  1. Проверка кода, линтеры, типы.
  2. Запуск тестов (юнит, e2e — по возможности).
  3. Сборка проекта (или Docker-образа).
  4. Деплой на выбранную платформу.

Строгость слоёв FSD и его структура упрощают автоматизацию: у вас обычно есть понятный src с фиксированными подпапками, а дополнительные проверки импортов (например, eslint-plugin для FSD) легко подключаются на шаге линтинга.

Пример GitHub Actions для сборки и деплоя

Смотрите, я покажу вам упрощённый пример workflow:

# .github/workflows/deploy.yml
name: Deploy FSD App

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    env:
      # Здесь мы прокидываем переменные окружения для сборки
      VITE_APP_ENV: production
      VITE_API_URL: https://api.example.com

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run linters
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build app
        run: npm run build

      # Здесь можно загрузить артефакты сборки
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist

      # Далее - шаг деплоя на ваш хостинг (S3, VPS, Kubernetes и т.д.)
      # Я укажу пример для условного провайдера
      - name: Deploy to hosting
        run: |
          # Здесь может быть скрипт загрузки dist на сервер
          # Например, через rsync или CLI провайдера
          echo "Deploy step goes here"

Комментарии показывают ключевые моменты:

  • окружение для сборки (env);
  • шаги проверки FSD-кода (линтеры, тесты);
  • сборка и выгрузка артефактов.

Работа с переменными окружения в CI

Самая частая ошибка — пытаться использовать .env на CI "как есть". Лучше:

  • хранить секретные значения (например, VITE_SENTRY_DSN) в секретах CI;
  • подставлять их через env в пайплайне;
  • не коммитить .env.production в репозиторий, если там чувствительные данные.

Пример с секретами GitHub Actions:

env:
  VITE_APP_ENV: production
  VITE_API_URL: ${{ secrets.VITE_API_URL }}
  VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}

Так вы не "зашиваете" реальные значения в коде, а FSD-конфиг (shared/config/env.ts) продолжает корректно работать, читая import.meta.env.


Deployment FSD-приложения на разные платформы

Статический хостинг (Netlify, Vercel, S3)

Для чисто фронтендового FSD-приложения чаще всего достаточно статики:

  1. Выполнить npm run build.
  2. Залить содержимое dist (или build) на хостинг.
  3. Настроить правила для SPA (fallback на index.html).

Особенности для FSD:

  • Структура проекта не меняет деплой как статики.
  • Но вам важно корректно настроить переменные окружения на самой платформе (например, в интерфейсе Netlify или Vercel).

Типичный сценарий для Netlify:

  • команда сборки: npm run build;
  • директория деплоя: dist;
  • переменные окружения: VITE_API_URL, VITE_APP_ENV и т.д.

Kubernetes и Docker-based платформа

Если вы используете Docker (как мы рассматривали выше), деплой часто выглядит так:

  1. Сборка Docker-образа с указанием аргументов:

    # Здесь мы передаем URL API и окружение
    docker build \
      --build-arg VITE_API_URL=https://api.example.com \
      -t registry.example.com/my-app:latest .
    
  2. Публикация образа в registry.
  3. Обновление deployment в Kubernetes, который тянет новый образ.

Особенность FSD здесь:

  • вы легко можете иметь несколько deployment-объектов, основанных на одном репозитории, но с разными build-arg (например, для разных клиентов или white-label-схемы).

Организация конфигурации под несколько окружений и клиентов

Мультибрендовый или мультиклиентский деплой

FSD удобен, когда нужно:

  • иметь единое ядро (shared, entities, features);
  • и несколько "обёрток" для разных клиентов (бренды, кастомные страницы, уникальные виджеты).

Например:

  • app/client-a;
  • app/client-b.

И каждая сборка может использовать свой entry-пойнт.

Пример мульти-entry в Vite

Теперь вы увидите, как это выглядит в конфиге:

// vite.config.ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import path from "path"

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
  build: {
    rollupOptions: {
      input: {
        // Разные entry-пойнты для разных клиентов
        clientA: path.resolve(__dirname, "src/app/client-a/index.html"),
        clientB: path.resolve(__dirname, "src/app/client-b/index.html"),
      },
    },
  },
})

Комментарии здесь объясняют, зачем несколько входов:

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

Такой подход часто сочетается с разными .env или переменными окружения в CI, чтобы для каждого клиента указать свои URL API и прочие настройки.


Типичные ошибки при деплое FSD-проекта и как их избежать

Ошибка 1. Несогласованные алиасы в сборщике и TypeScript

Симптомы:

  • локально всё работает, но при сборке на CI: "Module not found" для @/shared;
  • или наоборот — редактор не находит модуль, а сборка проходит.

Как исправить:

  1. Проверить, что vite.config.ts и tsconfig.json используют одинаковые алиасы и baseUrl.
  2. При изменении структуры слоёв (например, добавлении processes) обновлять оба файла.
  3. В CI не использовать кастомный tsconfig без нужных paths.

Ошибка 2. Прямое использование process.env в коде

В проектах на FSD с Vite или CRA часто допускают смешение:

  • где-то используют import.meta.env;
  • где-то — process.env.

В результат на деплое (особенно в Docker) значения могут не подставиться.

Как исправить:

  • определиться, какой подход вы используете (для Vite — import.meta.env);
  • сделать единый слой-конфиг (shared/config/env.ts) и использовать только его;
  • включить линтер, который запрещает прямой доступ к process.env вне этого слоя.

Ошибка 3. Захардкоженные URL и флаги по слоям

Иногда разработчики пишут:

// Плохой пример
const apiUrl = "https://api-dev.local"

Вместо чтения из конфигурации. В FSD-проекте это быстро становится проблемой, потому что:

  • логику и конфигурацию сложно переиспользовать;
  • деплой на несколько окружений требует ручного поиска "магических строк".

Как исправить:

  • вынести все такие значения в shared/config;
  • использовать переменные окружения только в одном месте;
  • при необходимости — добавить модуль в shared/config для feature-flags.

Ошибка 4. Отсутствие SPA fallback на боевом сервере

Симптомы:

  • главная страница открывается, но прямой переход на /profile даёт 404;
  • в dev-режиме всё работает.

Причина — сервер (nginx, Apache, CDN) не настроен корректно для Single Page Application.

Решение:

  • добавить правило try_files $uri $uri/ /index.html (для nginx);
  • или аналог для другого сервера;
  • убедиться, что оно применимо ко всем роутам, которые обслуживаются SPA.

Заключение

Деплой Feature-Sliced Design проекта с технической точки зрения очень похож на обычный деплой фронтенда. Разница в том, что архитектурные принципы FSD подталкивают вас к:

  • централизованной конфигурации (shared/config, app/config);
  • аккуратному использованию алиасов и правильной настройке сборщика;
  • более продуманной схеме окружений и фиче-флагов;
  • чёткому разделению точек входа (особенно при мультиклиентских деплоях).

Если вы изначально заложите:

  • единый модуль для чтения переменных окружения;
  • согласованную конфигурацию Vite/Webpack, TypeScript и тестов;
  • понятный CI-пайплайн с проверками и сборкой,

то дальнейший деплой FSD-проекта на любые платформы (от статического хостинга до Kubernetes) будет довольно прямолинейным. А сама архитектура FSD поможет масштабировать не только кодовую базу, но и вашу схему развертывания — под разные окружения, бренды, клиентов и фичи.


Частозадаваемые технические вопросы по теме и ответы

1. Как деплоить FSD-проект, если есть SSR или BFF-слой?

Обычно SSR/BFF выносится в отдельный пакет или директорию (/server), а фронтенд остаётся в FSD-структуре (/client/src). В этом случае:

  1. Соберите фронтенд как отдельный артефакт (например, npm run build в /client).
  2. На сервере (Node/Next/Nest) подключите статику из client/dist или аналогичной папки.
  3. Описывайте deployment как два контейнера (server и client) либо один мультистейдж-образ, где бандл фронта копируется внутрь серверного образа.

Важно не смешивать слои FSD с кодом сервера, чтобы не запутать imports и зависимость направлений.

2. Как организовать деплой, если нужно держать разные версии фич для разных пользователей?

Решение обычно строится на feature-flags:

  1. В shared/config/feature-flags.ts заводите флаги.
  2. Источник значений делаете внешним — API, конфиг-сервис, LaunchDarkly и т.п.
  3. При деплое вы не меняете код, а переключаете флаги через конфигурацию окружения или внешний сервис.
  4. В CI/CD различия между окружениями задаются в переменных окружения и конфигурации флагов, а не в бранчах кода.

3. Как деплоить FSD-проект в монорепозитории с бэкендом и мобильными приложениями?

Обычно используют:

  1. Monorepo-менеджер (Nx, Turborepo, pnpm workspace).
  2. Отдельный пакет web-app с FSD-структурой.
  3. В CI настраивают таргетированные пайплайны — если меняется только web-app, запускается только его сборка и деплой.
  4. Артефакты фронта деплоятся независимо от бэкенда, но могут использовать общие версии API-конрактов (например, через shared-пакеты типов).

4. Как поступить, если FSD-проект должен деплоиться и как SPA, и как встроенный виджет?

Практика такая:

  1. В app делаете два entry-пойнта — основной SPA и "микрофронт" (например, widget.tsx).
  2. В сборщике настраиваете два entry (как в примере с мульти-entry).
  3. В CI/CD собираете один артефакт, но деплоите:
    • SPA — как обычное приложение;
    • bundle виджета — в отдельное место или как NPM-пакет для встраивания.
  4. На стороне потребителя (другой сайт) подключается только нужный bundle виджета.

5. Как лучше организовать версионирование и откаты релизов FSD-приложения?

Подход не уникален для FSD, но хорошо с ним сочетается:

  1. Версии приложения (например, в package.json) метите тэгами в Git.
  2. В CI каждое тегированное создание релиза инициирует сборку и деплой.
  3. Для отката деплоя используете:
    • либо механизм версионирования артефактов (S3/CloudFront, Docker tags),
    • либо деплой предыдущего Git-тэга.
  4. FSD упрощает это тем, что изменения обычно локализованы по слоям, и легче отследить, какие фичи попали в конкретный релиз.
Стрелочка влевоЛенивая загрузка lazy loading - полное практическое руководствоЧастые ошибки при использовании функции append в GoСтрелочка вправо

Все гайды по Feature-sliced_design

Открыть базу знаний

Отправить комментарий