У сучасній інженерній практиці неперервної доставки (Continuous Delivery) фундаментальним принципом є чітке архітектурне розмежування двох процесів життєвого циклу:
Пряме зв'язування релізу з викаткою коду (git commit -> збірка в CI -> розгортання контейнерів -> негайне відкриття на 100% аудиторії) несе максимальний рівень ризику: виявлення дефекту під навантаженням змушує виконувати екстрені відкати або в паніці збирати хотфікси впродовж 10–30 хвилин, під час яких сервіс зазнає фінансових та репутаційних втрат.
Для повної ізоляції ризиків використовується патерн Feature Flags (Feature Toggles). У Next.js 15 інтеграція провайдерів керування прапорцями (PostHog, LaunchDarkly) вимагає врахування механізмів React Server Components (RSC):
useEffect) або асинхронні HTTP-запити з браузера неминуче спричиняє візуальне зміщення макета (Cumulative Layout Shift, CLS) та мерехтіння інтерфейсу (Flicker). Користувач спочатку бачить старий елемент UI, який через 150–300 мс скачкоподібно замінюється на новий.middleware.ts на основі токена сесії чи ідентифікатора користувача (distinctId). Сервер рендерить консистентний HTML без зсувів верстки та вбудовує обчислені прапорці в початковий стан клієнта за допомогою механізму Bootstrapping.Паралельно з цим на рівні інфраструктури налаштовується система Automated Rollbacks (Автоматичний відкат). Оркестратори прогресивної доставки (такі як Argo Rollouts або зв'язка Prometheus Alertmanager з вебхуками) безперервно відстежують Service Level Indicators (SLI). Якщо канареєчний реліз викликає сплеск помилок HTTP 5xx вище порогу (наприклад, >1.5%) або затримка p99 зростає більше ніж на 50%, контролер негайно зупиняє розгортання та повертає трафік на стабільну попередню версію за лічені секунди без втручання людини.
Взаємодія систем Feature Flags та автоматичного відкату під час аварійної ситуації представлена на схемі:
[ Вхідний клієнтський HTTP-трафік ]
│
▼
[ Ingress / Argo Rollouts Traffic Split ]
│ (90% Stable) │ (10% Canary)
▼ ▼
[ Next.js v1 ] [ Next.js v2 ]
│
┌──────────┴──────────┐
▼ ▼
[ Feature Flag: ON ] [ Feature Flag: OFF ]
(PostHog Target 5%) (Базовий шлях)
│
▼
[ Дефект у v2: Сплеск 500! ]
│
┌────────────────┴────────────────┐
▼ ▼
[ Sentry: Поріг алерту ] [ Prometheus: Запит PromQL ]
(Помилок > 20 на хвилину) (rate(5xx) > 1.5%)
│ │
▼ ▼
[ PostHog API: Kill Switch ] [ Argo Rollouts: Abort ]
(Вимкнення за 1 секунду) (Відкат v2 -> v1 за 5с)Надійна система прогресивної доставки об'єднує два контури: серверне обчислення прапорців у PostHog із клієнтським bootstrapping у середовищі Next.js 15 RSC та декларативний маніфест Argo Rollouts для моніторингу метрик у Prometheus.
Для усунення мерехтіння інтерфейсу прапорці обчислюються на сервері в Server Components і передаються вниз за деревом компонентів:
import { PostHog } from 'posthog-node';
let posthogInstance: PostHog | null = null;
/**
* Синглтон-клієнт PostHog для серверного середовища Node.js
*/
export function getPostHogServerClient(): PostHog {
if (!posthogInstance) {
posthogInstance = new PostHog(process.env.POSTHOG_API_KEY || '', {
host: process.env.POSTHOG_HOST || 'https://app.posthog.com',
// Пакетизація подій для мінімізації навантаження на Event Loop
flushAt: 20,
flushInterval: 5000,
});
}
return posthogInstance;
}
export type SupportedFeatureFlag =
| 'checkout-v2-redesign'
| 'ai-recommendation-engine'
| 'instant-payout-integration';
/**
* Оцінка прапорця на сервері безпосередньо у Node.js.
* Виконується синхронно всередині Server Components без додаткових запитів із браузера.
*/
export async function isFeatureEnabledOnServer(
distinctId: string,
flagKey: SupportedFeatureFlag,
userProperties?: Record<string, string | number | boolean>
): Promise<boolean> {
const client = getPostHogServerClient();
try {
const isEnabled = await client.isFeatureEnabled(flagKey, distinctId, {
personProperties: userProperties,
});
return Boolean(isEnabled);
} catch (error) {
console.error(`[PostHog Server] Помилка під час оцінки прапорця "${flagKey}":`, error);
// Безпечне значення за замовчуванням у разі мережевого збою
return false;
}
}
/**
* Отримання всіх обчислених прапорців для передачі в клієнтський Bootstrapping
*/
export async function getBootstrapFeatureFlags(
distinctId: string
): Promise<Record<string, boolean | string>> {
const client = getPostHogServerClient();
try {
return await client.getAllFlags(distinctId);
} catch (err) {
console.error('[PostHog Server] Не вдалося отримати прапорці для bootstrapping:', err);
return {};
}
}import { cookies } from 'next/headers';
import { isFeatureEnabledOnServer, getBootstrapFeatureFlags } from '@/lib/feature-flags/posthog-server';
import { CheckoutLegacyForm } from '@/components/checkout/CheckoutLegacyForm';
import { CheckoutV2Interactive } from '@/components/checkout/CheckoutV2Interactive';
import { FeatureFlagClientProvider } from '@/components/providers/FeatureFlagClientProvider';
export default async function CheckoutPage() {
const cookieStore = await cookies();
const distinctId = cookieStore.get('session_user_id')?.value || 'anonymous';
// 1. Оцінка прапорця на сервері до початку генерації HTML
const isCheckoutV2Enabled = await isFeatureEnabledOnServer(
distinctId,
'checkout-v2-redesign'
);
// 2. Отримання попередньо обчислених прапорців для клієнтської гідратації
const bootstrapFlags = await getBootstrapFeatureFlags(distinctId);
return (
<FeatureFlagClientProvider distinctId={distinctId} bootstrapFlags={bootstrapFlags}>
<main className="max-w-4xl mx-auto py-8">
{isCheckoutV2Enabled ? (
<CheckoutV2Interactive />
) : (
<CheckoutLegacyForm />
)}
</main>
</FeatureFlagClientProvider>
);
}Контролер створює 10% подів кандидатської версії v2 та спрямовує на них частину трафіку.
Prometheus що 15 секунд перевіряє співвідношення помилок 5xx на нових подах.
Рівень помилок 5xx сягає 3.2% за встановленого ліміту 1.5%. Аналіз переходить у статус Failed.
Argo Rollouts повертає 100% трафіку на стабільну ревізію v1 та зупиняє поди v2.
У Slack надходить сповіщення з посиланням на дашборд Grafana та звіт про інцидент у Sentry.
Нижче наведено маніфест Kubernetes корпоративного рівня, що налаштовує канареєчний деплой з аналізом PromQL-метрик та автоматичним відкатом:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: prometheus-error-rate-and-latency
namespace: production
spec:
metrics:
# 1. Перевірка допустимого відсотка HTTP 5xx помилок
- name: success-rate-check
interval: 15s
# Дозволяємо максимум 1 невдалу пробу перед ініціацією відкату
failureLimit: 1
count: 6
provider:
prometheus:
address: http://prometheus-k8s.monitoring.svc.cluster.local:9090
query: |
(
sum(rate(nextjs_http_requests_total{track="canary",status_code=~"5.."}[1m]))
/
sum(rate(nextjs_http_requests_total{track="canary"}[1m]))
) * 100 < 1.5
# 2. Перевірка порогу затримки p99 (повинна бути нижче 800 мс)
- name: latency-p99-check
interval: 30s
failureLimit: 2
count: 3
provider:
prometheus:
address: http://prometheus-k8s.monitoring.svc.cluster.local:9090
query: |
histogram_quantile(0.99, sum(rate(nextjs_http_request_duration_seconds_bucket{track="canary"}[1m])) by (le)) < 0.8
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: nextjs-web-app
namespace: production
spec:
replicas: 10
strategy:
canary:
analysis:
templates:
- templateName: prometheus-error-rate-and-latency
args:
- name: service-name
value: nextjs-web-app
steps:
# Крок 1: Спрямовуємо 10% трафіку на нову версію і проводимо аналіз 3 хвилини
- setWeight: 10
- pause: { duration: 3m }
# Крок 2: Збільшуємо до 25% трафіку і аналізуємо ще 5 хвилин
- setWeight: 25
- pause: { duration: 5m }
# Крок 3: Підвищуємо до 50%
- setWeight: 50
- pause: { duration: 5m }
# Фінальний крок: Повне перемикання на 100% за успішного проходження перевірок
revisionHistoryLimit: 5
selector:
matchLabels:
app: nextjs-web-app
template:
metadata:
labels:
app: nextjs-web-app
spec:
terminationGracePeriodSeconds: 30
containers:
- name: nextjs
image: ghcr.io/enterprise/nextjs-web:v2.1.0
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
ports:
- containerPort: 3000
name: http
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2000m"
memory: "1536Mi"
readinessProbe:
httpGet:
path: /api/healthz
port: 3000
periodSeconds: 5Використання хуків на зразок useFeatureFlagEnabled усередині клієнтських компонентів без серверного bootstrapping. Компонент монтується зі значенням за замовчуванням (false), надсилає запит до PostHog і через 200 мс оновлюється зі значенням true. Це спричиняє відчутний зсув макета (CLS), дратує користувачів і погіршує показники пошукової оптимізації. Оцінюйте прапорці виключно на сервері в RSC.
Залишення в коді конструкцій if (isFeatureEnabled('old-flag')) через кілька місяців після 100% релізу фічі. Десятки старих прапорців призводять до комбінаторного вибуху гілок виконання, які неможливо якісно покрити тестами. Встановлюйте чіткий SLA на видалення прапорців (не пізніше 14 днів після повного релізу) та фіксуйте завдання з очищення в спринті.
Проведення міграцій, що видаляють або перейменовують стовпці PostgreSQL одночасно з деплоєм версії v2. Якщо Argo Rollouts зафіксує збій і відкотить поди до v1, стабільні поди аварійно завершаться під час старту, оскільки їхній код очікує старий стовпець. Завжди дотримуйтесь патерну Expand-Contract: база даних має забезпечувати сумісність із попередньою версією коду.
Відкат образу контейнера без одночасного очищення кешу CDN та тегів revalidateTag у Next.js Data Cache. Користувачі продовжують отримувати з кешу збійні сторінки або пошкоджені файли стилів. Автоматичний скрипт відкату зобов'язаний скидати кеш на рівні Cloudflare/Fastly та очищати внутрішнє кеш-сховище сервера.
Ізоляція нової функціональності за серверними прапорцями з вимкненим станом за замовчуванням.
Оцінка прапорців на сервері Node.js для усунення візуальних зсувів макета на клієнті.
Підготовка виразів PromQL для відстеження відсотка помилок 5xx та затримок p99 на canary-подах.
Створення маніфесту AnalysisTemplate з налаштуванням лімітів збоїв та покрокового збільшення трафіку.
Очищення застарілих умовних гілок у коді впродовж двох тижнів після повного розгортання.