Feature flags — переключатели, которые позволяют включать и выключать функциональность в проде без передеплоя. Запустили новую корзину, что-то сломалось — два клика, и все пользователи на старой. Это превращает страшный релиз в нестрашный, и для растущих продуктов это must-have.
Зачем нужны feature flags
| Сценарий | Что делает флаг |
|---|---|
| Постепенный rollout | сначала 1% пользователей, потом 10%, потом 100% |
| Отключение функции в случае проблем | мгновенно выключить, не передеплоивая |
| A/B-тесты | две версии одной фичи, метрики какая лучше |
| Раздел по сегментам | новая функция только для enterprise-плана |
| Включение по бета-программе | конкретные emails видят то, чего другие не видят |
| Trunk-based development | merge в main без боязни сломать прод |
Без флагов
Релиз → баг в проде → откат → расследование → fix → релиз. Между «обнаружили» и «починили» — час, иногда сутки. Пользователи злы.
С флагом: релиз → баг → выключили флаг (5 секунд) → пользователи на старой версии → расследование без давления.
Локальные флаги
В простейшем случае — .env:
NEXT_PUBLIC_FEATURE_NEW_CHECKOUT=true
NEXT_PUBLIC_FEATURE_AI_RECOMMENDATIONS=false
if (process.env.NEXT_PUBLIC_FEATURE_NEW_CHECKOUT === "true") {
return <NewCheckout />;
}
return <OldCheckout />;
Минусы: чтобы включить/выключить — нужен передеплой. Нет сегментации (или вкл всем, или выкл всем). Не подходит для растущих команд.
Сравнение flag-сервисов
| Сервис | Open source | Self-hosted | Бесплатный план | Сложность |
|---|---|---|---|---|
| GrowthBook | да (MIT) | да | до 1 000 ивентов/мес | средняя |
| Unleash | да (Apache 2.0) | да | community edition бесплатно | средняя |
| Flagsmith | да (BSD) | да | до 50 000 запросов/мес | низкая |
| LaunchDarkly | нет | нет | $0 — нет | низкая (богатый UI) |
| ConfigCat | нет (но есть free tier) | нет | до 10 флагов | низкая |
| PostHog Feature Flags | да | да | до 1 млн ивентов/мес | низкая |
Для российского проекта самое разумное — GrowthBook или Unleash self-hosted (полностью бесплатно), либо PostHog если уже используете для аналитики.
GrowthBook — рекомендуем
Open source, фокус на A/B-тестах с интеграцией в аналитику.
docker run -d -p 3100:3000 -p 4100:3100 \
-e MONGODB_URI=... growthbook/growthbook
import { GrowthBook } from "@growthbook/growthbook-react";
const gb = new GrowthBook({
apiHost: "https://flags.example.ru",
clientKey: "sdk-xxxxx",
attributes: {
id: user.id,
plan: user.plan,
country: "RU",
},
});
await gb.loadFeatures();
import { useFeatureIsOn } from "@growthbook/growthbook-react";
function Checkout() {
const newFlow = useFeatureIsOn("new-checkout");
return newFlow ? <NewCheckout /> : <OldCheckout />;
}
В UI GrowthBook задаёте правила:
- 50% пользователей → ON.
- Юзеры с
plan = "enterprise"→ ON. - Юзеры из
country = "RU"→ OFF. - A/B-тест: 50% видят вариант A, 50% — B; меряем conversion.
Unleash
Похож на GrowthBook, фокус на feature toggles без A/B. Хорошо подходит для команд, где нужно много прав-управления (кто может включать какие флаги).
import { initialize } from "unleash-client";
const unleash = initialize({
url: "https://unleash.example.ru/api/",
appName: "my-site",
customHeaders: { Authorization: "..." },
});
unleash.on("ready", () => {
if (unleash.isEnabled("new-checkout", { userId })) {
/* ... */
}
});
Минус: меньше готовых функций для эксперимента, чем у GrowthBook.
Flagsmith
Самый простой UI, удобен для не-разработчиков (продакт-менеджер сам может включать).
import flagsmith from "flagsmith";
await flagsmith.init({ environmentID: "xxx" });
flagsmith.identify(user.id, { plan: user.plan });
if (flagsmith.hasFeature("new-checkout")) { /* ... */ }
SSR и флаги
Главная боль: на SSR нужно знать состояние флага до рендера, иначе будет flash контента (промигнёт старая версия, потом перерендерится новая).
В Next.js 15 — серверная инициализация:
// app/page.tsx (server component)
import { GrowthBook } from "@growthbook/growthbook";
async function getGrowthBook(userId: string) {
const gb = new GrowthBook({ /* ... */ });
await gb.loadFeatures();
gb.setAttributes({ id: userId });
return gb;
}
export default async function Page() {
const gb = await getGrowthBook(getUserId());
return gb.isOn("new-checkout") ? <NewCheckout /> : <OldCheckout />;
}
Для client components — гидрация с тем же стейтом, чтобы не мигало.
A/B-тесты правильно
Пара правил, без которых эксперимент бесполезен:
- Sample size — заранее посчитайте, сколько пользователей нужно для статистической значимости. Калькуляторы есть в GrowthBook.
- Один тест за раз — иначе не понятно, какая фича дала эффект.
- Минимум 2 недели — недельный цикл (выходные ≠ будни) должен пройти полностью.
- Метрика выбрана заранее — не подгоняйте, не ищите «какая метрика выиграла».
- Ноль = тоже результат — если фича не дала эффекта, нужно её откатить, не «всё равно зальём».
Risk management
Каждый флаг — потенциальный долг. Через месяц-два их становится 30, никто не помнит, что они делают, в коде ветвление на ветвление.
Правила гигиены:
- Каждый флаг имеет owner и дата планируемого удаления.
- Раз в спринт — ревью списка, удаление протухших.
- Тесты пишем на обе ветки (
if flagиelse), чтобы при удалении флага не рассыпалось. - Не более 5-10 активных флагов на сервис одновременно.
Killswitch для критичных функций
Помимо обычных флагов имеет смысл выделить «аварийные выключатели» для критики:
disable-checkout— отключить оформление заказа при сбое платёжного шлюза.disable-search— выключить поиск, если упал Elasticsearch.read-only-mode— режим только-чтение при миграции БД.
Эти флаги не для экспериментов, а для инциден-response. Должны включаться за секунды, без согласований.
Стоимость
| Решение | Месяц для 100 тыс. пользователей |
|---|---|
| GrowthBook self-hosted | 500-1500 ₽ (VPS) |
| Unleash community | то же |
| PostHog self-hosted | 2-5 тыс. ₽ |
| LaunchDarkly | от $1000 |
| ConfigCat | $99-799 |
Российским проектам — однозначно self-hosted, оплата зарубежных SaaS затруднена.
Итого
Feature flags — стандарт зрелого продукта. Без них релиз каждой фичи — нервный, с ними — рутина. Ставьте GrowthBook или Unleash, заведите дисциплину «каждый флаг с владельцем и датой смерти», и через полгода вы не вспомните, как жили без них.
Частые вопросы
Сколько флагов нормально иметь в проекте?
Активных — 5-15 на сервис в любой момент. Если становится больше — что-то идёт не так, превратили продукт в лоскутное одеяло. Каждый флаг должен иметь дату удаления (через 1-3 месяца после релиза). Долгоживущие флаги (например, разделение по тарифам) — это не feature flag, а конфигурация, выносите в отдельную таблицу.
Feature flags замедляют сайт?
Запросы к flag-серверу — да, каждый сетевой round-trip это latency. Решение: SDK кеширует флаги локально (5-30 секунд), плюс используется server-side rendering для первого запроса. На клиенте — periodic refetch в фоне. На правильно сконфигурированном GrowthBook overhead исчезающе мал — единицы миллисекунд.
Как тестировать фичу под флагом локально?
Override через query param или localStorage: ?gb=new-checkout принудительно включает флаг для текущего юзера. SDK должен это поддерживать (GrowthBook через forceFeatures). На staging — заводят отдельное окружение в flag-сервере с предсказуемыми правилами для QA.
A/B-тесты обязательно в flag-сервисе или можно самим?
Технически можно: рандомное распределение в коде + аналитика отдельно. Но статистический анализ (значимость, sample size, sequential testing) — это месяцы работы. Лучше взять GrowthBook или PostHog Experiments — там это решено и есть калькуляторы. Самописное A/B обычно даёт ложные результаты из-за статистических ошибок.
Как избежать flash контента при загрузке?
Серверный рендер — флаги загружаются на бэке до отдачи HTML, разметка приходит уже с правильной версией. На клиенте — hydration с тем же state. Если без SSR (SPA) — показывайте skeleton до загрузки флагов и только потом делайте решение, что показать. Не делайте мгновенный default fallback, иначе будет мигать.
Flag-сервис лежит — что показывать?
SDK всегда отдаёт fallback-значение, заданное в коде (обычно «функция выключена»). Поэтому даже если GrowthBook упал — продукт работает по последней закешированной конфигурации или с фолбэком. Главное — не делать критическую функциональность зависимой от ответа сервиса в реальном времени.
Можно ли использовать feature flags для permission/access control?
Нет. Это разные задачи. Permissions — это «может ли этот юзер видеть страницу X», управляется через RBAC и проверки на бэке. Feature flag — «включена ли вообще эта фича в проде». Использовать flag для прав — путь к утечке данных, потому что флаги легко обходятся клиентскими манипуляциями (override через query param). Для прав — отдельный механизм с проверкой на сервере.