Deployment Blue-Green pada aplikasi monolit modern dengan Inertia.js sering menimbulkan tantangan unik yang tidak ditemukan pada API murni atau Multi-Page Application (MPA) tradisional. Inertia mengaburkan batas antara backend routing dan SPA (Single-Page Application). Ketika load balancer mengalihkan traffic secara instan (hard cutover) dari target Blue ke Green, browser klien yang masih menjalankan bundle JavaScript versi lama rentan mengalami runtime crash, request loop, dan lonjakan error HTTP 409.

Artikel ini membahas mitigasi komprehensif untuk deployment Blue-Green Inertia: mengelola versioning aset, merancang props yang backward-compatible, konfigurasi routing ingress, dan mengotomatiskan fast rollback.

Anatomi Masalah: X-Inertia-Version dan Desinkronisasi Props saat Cutover

Inertia.js menggunakan sistem asset versioning bawaan untuk mendeteksi perubahan bundle frontend. Setiap request navigasi Inertia menyertakan dua header HTTP penting:

X-Inertia: true
X-Inertia-Version: 4a2b1c8f9...

Ketika request dengan versi lama (Blue) mendarat di backend yang sudah diperbarui (Green), middleware HandleInertiaRequests mendeteksi ketidaksesuaian hash version. Backend secara default akan:

  1. Mengembalikan response HTTP 409 Conflict.
  2. Menyertakan header X-Inertia-Location: /target-url.
  3. Memaksa browser klien melakukan full page reload (hard reload) untuk mengunduh bundle aset Green terbaru.

Masalah muncul ketika dua kondisi ini terjadi:

  • Asset 404 / Cache Miss: Browser melakukan hard reload ke server yang belum memiliki aset Green terkompilasi, atau CDN belum menghangatkan cache untuk chunk baru.
  • Breaking Changes pada Skema Props: Jika payload data yang dikembalikan controller berubah drastis sebelum browser berhasil memuat bundle baru, komponen JavaScript yang aktif akan membaca skema baru menggunakan logika lama. Hasilnya adalah TypeError: Cannot read properties of undefined.

Postmortem Ringkas: Insiden Hard Cutover pada Jam Sibuk

Sebuah tim e-commerce melakukan deployment Green untuk merombak alur checkout. Perubahan backend mengubah struktur data shared props dari flat format menjadi nested object:

// Versi Blue (Lama)
{
  "customer_name": "Budi Santoso",
  "tier": "Gold"
}

// Versi Green (Baru)
{
  "customer": {
    "name": "Budi Santoso",
    "membership": {
      "tier": "Gold"
    }
  }
}

Traffic di-switch 100% pada level Ingress. Pengguna aktif yang sedang berada di halaman checkout melakukan interaksi navigasi partial reload. Komponen Vue/React versi Blue menerima payload Green. Evaluasi ekspresi props.customer_name.toUpperCase() langsung memicu uncaught exception pada ribuan browser klien.

Bersamaan dengan itu, request concurrent memicu badai HTTP 409. Server backend mengalami spike CPU karena ribuan browser melakukan reload halaman penuh secara simultan. Sentry menerima puluhan ribu event dalam 3 menit pertama sebelum deployment dibatalkan secara manual.

Strategi Pencegahan 1: Pola Expand-and-Contract pada Shared Props

Pencegahan breaking changes props mengharuskan penerapan pola Expand and Contract (Parallel Run). Backend Green wajib mendukung skema lama sekaligus skema baru selama jendela transisi deployment.

Implementasi di Middleware / Controller (Backend)

namespace App\Http\Middleware;

use Inertia\Middleware;
use Illuminate\Http\Request;

class HandleInertiaRequests extends Middleware
{
    public function version(Request $request): ?string
    {
        // Gunakan hash konsisten (misal commit SHA atau file hash manifest)
        return config('app.asset_version');
    }

    public function share(Request $request): array
    {
        $user = $request->user();

        return array_merge(parent::share($request), [
            'auth' => [
                // Skema Baru (Contract Phase)
                'customer' => $user ? [
                    'name' => $user->name,
                    'membership' => ['tier' => $user->tier],
                ] : null,

                // Skema Lama (Expand Phase - Deprecated tapi wajib ada)
                'customer_name' => $user?.->name,
                'tier' => $user?.->tier,
            ],
        ]);
    }
}

Defensif Konsumsi Props di Komponen Frontend

Komponen UI harus mengantisipasi transisi dengan membaca skema baru terlebih dahulu menggunakan optional chaining, lalu fallback ke skema lama:

<script setup>
import { computed } from 'vue';
import { usePage } from '@inertiajs/vue3';

const page = usePage();

// Aman terhadap bundle Blue maupun Green
const customerName = computed(() => {
  return page.props.auth?.customer?.name 
    ?? page.props.auth?.customer_name 
    ?? 'Guest';
});
</script>

Hapus field legacy pada sprint berikutnya setelah seluruh traffic berjalan stabil di environment Green.

Strategi Pencegahan 2: Affinity Routing & Asset Availability

Untuk meminimalkan badai HTTP 409, jangan hapus aset Blue segera setelah switch. Kedua bundle aset harus tetap dapat diakses via CDN atau reverse proxy selama durasi session TTL.

Gunakan cookie affinity sementara di layer load balancer (seperti NGINX) saat cutover berlangsung, sehingga sesi aktif menyelesaikan flow kerja mereka di Blue sebelum beralih ke Green:

upstream app_backend {
    ip_hash; # atau gunakan sticky cookie
    server 10.0.1.10:8000; # Blue
    server 10.0.1.11:8000 weight=0; # Green (diaktifkan bertahap)
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_set_header X-Inertia-Version $http_x_inertia_version;
    }

    # Pastikan build assets Blue dan Green tetap tersedia di public folder bersama
    location /build/ {
        alias /var/www/shared-assets/build/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Setup Observabilitas: Prometheus & Sentry

Deteksi dini kegagalan cutover membutuhkan pemantauan metrik pada layer reverse proxy dan monitoring unhandled exception pada browser.

Prometheus Metrics Alerting

Monitor rasio status HTTP 409 terhadap total HTTP request Inertia. Kenaikan drastis menandakan desinkronisasi deployment yang masif.

# Prometheus Alert Rule
groups:
  - name: inertia_cutover_alerts
    rules:
      - alert: InertiaVersionConflictSpike
        expr: sum(rate(http_requests_total{status="409"}[1m])) / sum(rate(http_requests_total[1m])) > 0.05
        for: 30s
        labels:
          severity: critical
        annotations:
          summary: "Lonjakan HTTP 409 Conflict terdeteksi (> 5% traffic)"
          description: "Inertia asset version mismatch tinggi antara target Blue dan Green."

Sentry Real-Time Tagging

Kirim metadata versi Inertia ke dalam scope context Sentry di frontend. Ini memisahkan error yang berasal dari aset lama versus aset baru:

import * as Sentry from '@sentry/vue';
import { router } from '@inertiajs/vue3';

router.on('navigate', (event) => {
  Sentry.setTag('inertia_version', event.detail.page.version);
  Sentry.setContext('page_props', {
    component: event.detail.page.component,
    url: event.detail.page.url,
  });
});

Automated Fast Rollback Script

Ketika cutover Green dilakukan, pipeline deployment harus menjalankan verifikasi kesehatan otomatis selama 60 detik pertama. Jika rate HTTP 409 atau 500 melampaui batas ambang batas (threshold), traffic harus segera dikembalikan ke Blue dalam hitungan detik.

#!/usr/bin/env bash
set -euo pipefail

PROMETHEUS_URL="http://prometheus:9090"
NGINX_ROUTER_IP="10.0.0.1"
THRESHOLD=0.03 # Max 3% error rate 409/5xx

echo "[INFO] Monitoring traffic post-switch ke Green..."
sleep 15

# Query rate HTTP 409 dan 5xx selama 1 menit terakhir
QUERY='sum(rate(http_requests_total{status=~"409|5.."}[1m]))/sum(rate(http_requests_total[1m]))'
RESPONSE=$(curl -sG --data-urlencode "query=${QUERY}" "${PROMETHEUS_URL}/api/v1/query")

ERROR_RATE=$(echo "${RESPONSE}" | jq -r '.data.result[0].value[1] // 0')

echo "[INFO] Current Error Rate: ${ERROR_RATE}"

# Bandingkan error rate dengan threshold menggunakan bc
IS_UNHEALTHY=$(echo "${ERROR_RATE} > ${THRESHOLD}" | bc -l)

if [ "${IS_UNHEALTHY}" -eq 1 ]; then
    echo "[CRITICAL] Error rate melampaui batas aman (${ERROR_RATE} > ${THRESHOLD}). Melakukan Rollback ke Blue!"
    
    # Kembalikan konfigurasi upstream ke Blue secara atomik
    ssh deploy@"${NGINX_ROUTER_IP}" 'sudo ln -sf /etc/nginx/upstreams/blue.conf /etc/nginx/conf.d/upstream.conf && sudo nginx -s reload'
    
    echo "[SUCCESS] Rollback selesai. Traffic diarahkan kembali ke Blue."
    exit 1
else
    echo "[SUCCESS] Deploy stabil. Health metric dalam batas toleransi."
    exit 0
fi

Checklist Deployment Blue-Green Aman

  • Asset Coexistence: Simpan build aset frontend Blue dan Green di direktori bersama atau S3 bucket yang sama agar request chunk lawas tidak menghasilkan HTTP 404.
  • Dual Schema Ready: Terapkan pola expand-and-contract pada shared props minimal 1 rilis sebelum skema lama dihapus total.
  • Synchronized Asset Version: Pastikan backend membaca source hash asset yang valid (misalnya manifest SHA) dan tidak berubah acak di tiap worker instance.
  • Automated Probe & Fast Rollback: Jangan mengandalkan manual check. Pantau metrik HTTP 409 dan runtime exceptions dengan script rollback otomatis.