Anatomi Masalah: Polusi Cache Edge pada Rilis Produksi

Pengguna mendapati tampilan antarmuka web rusak pasca-deployment: browser langsung merender respons mentah (raw JSON) seperti {"component":"Dashboard","props":{...},"url":"/dashboard","version":"..."} alih-alih antarmuka berbasis HTML lengkap. Masalah ini terjadi secara sporadis pada pengguna direct visit atau saat melakukan hard refresh (F5), sementara navigasi berbasis Single Page Application (SPA) internal tetap berjalan normal.

Root Cause: Cache Key Collision Tanpa Header 'Vary: X-Inertia'

Inertia.js menggunakan arsitektur dual-response pada URI yang identik. Perilaku respons dikendalikan oleh header permintaan klien:

  • Direct Visit: Browser mengirimkan permintaan GET biasa tanpa header khusus. Backend merespons dengan status 200 OK, Content-Type: text/html, dan membungkus data aplikasi di dalam atribut root data-page.
  • Inertia Visit: Router sisi klien mengirimkan permintaan AJAX/Fetch dengan menyertakan header HTTP X-Inertia: true. Backend merespons dengan status 200 OK, Content-Type: application/json, dan payload data halaman saja.

Ketika CDN dikonfigurasi untuk melakukan caching terhadap rute web dinamis (misalnya untuk menghemat beban origin server), cache key default CDN hanya mengevaluasi Host + Path + Query String. CDN mengabaikan header HTTP request.

Urutan kegagalan terjadi sebagai berikut:

  1. Klien melakukan navigasi SPA via Inertia ke rute /dashboard. Permintaan menyertakan header X-Inertia: true.
  2. Origin server mengembalikan payload JSON dengan header Cache-Control: public, max-age=3600, tetapi tanpa menyertakan Vary: X-Inertia.
  3. Edge CDN menyimpan payload JSON tersebut dengan cache key example.com/dashboard.
  4. Klien lain membuka https://example.com/dashboard secara langsung melalui address bar browser. Permintaan ini meminta dokumen HTML, bukan JSON.
  5. Edge CDN mendapati cache hit untuk key example.com/dashboard dan langsung mengembalikan payload JSON mentah ke browser. Browser merender representasi MIME application/json tanpa mengeksekusi runtime JavaScript frontend.

Observabilitas: Deteksi Dini Anomali Edge & Klien

Pencegahan degradasi layanan bergantung pada metrik edge log dan telemetri klien.

1. Edge Log Monitoring

Jalankan query analisis log CDN (Cloudflare Logpush, CloudWatch, atau Datadog) untuk mendeteksi mismatch antara Accept header/User-Agent dengan Content-Type respons:

SELECT 
  client_ip, 
  request_uri, 
  request_headers['user-agent'] AS user_agent,
  response_headers['content-type'] AS content_type
FROM cdn_access_logs
WHERE request_headers['x-inertia'] IS NULL
  AND response_headers['content-type'] LIKE '%application/json%'
  AND request_uri NOT LIKE '/api/%'
  AND status_code = 200;

2. Alerting Mismatch Rasio Konten

Pasang alert dengan kondisi: lonjakan metrik edge.response.content_type.json melebihi 5% dari total lalu lintas pada rute non-API tanpa keberadaan header permintaan X-Inertia selama interval 2 menit.

3. Sentry / RUM Client-Side Tracking

Ketika raw JSON termuat di browser, berkas bundle JavaScript frontend tidak dieksekusi. Metrik RUM akan menunjukkan drop drastis pada interaksi Largest Contentful Paint (LCP) atau peningkatan error pada tracking error gateway/proxy fallback.

Mitigasi Cepat & Prosedur Rollback Darurat

Saat insiden terjadi, eksekusi pembersihan cache secara instan sebelum melakukan deploy perbaikan kode origin.

Langkah 1: Fast Purge via CDN API

Gunakan API CDN untuk menginvalidasi cache global pada rute-rute aplikasi Inertia:

curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
     -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
     -H "Content-Type: application/json" \
     --data '{"prefixes": ["example.com/dashboard", "example.com/profile"]}'

Jika skala URL terlalu luas, eksekusi purge total sementara ({"purge_everything": true}) sambil memantau kapasitas origin server terhadap thundering herd.

Langkah 2: Edge Rule Bypass Sementara

Tambahkan Transform Rule atau Cache Rule di level edge untuk mem-bypass cache secara eksplisit jika header X-Inertia ada atau jika rute mencakup antarmuka dinamis:

# Cloudflare Cache Rule Expression:
(http.request.uri.path not matches "^/(assets|build|static)/" and http.request.headers["x-inertia"][0] eq "true")
-> Action: Bypass Cache

Pencegahan Permanen

1. Konfigurasi Backend Middleware (Header Vary)

Origin server wajib menginstruksikan reverse proxy bahwa respons bervariasi bergantung pada header X-Inertia. Pada Laravel, pasang logic ini pada middleware aplikasi:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureVaryInertiaHeader
{
    public function handle(Request $request, Closure $next): Response
    {
        /** @var Response $response */
        $response = $next($request);

        // Tambahkan Vary: X-Inertia ke seluruh respons berbasis Inertia atau web routes
        $response->headers->set('Vary', 'X-Inertia, Accept', false);

        // Pastikan halaman privat tidak tersimpan di public edge
        if (! $request->is('public-routes/*')) {
            $response->headers->set('Cache-Control', 'no-store, private');
        }

        return $response;
    }
}

2. Konfigurasi Cache Key Edge CDN

Jika CDN mendukung custom cache key (misalnya Cloudflare Enterprise Custom Cache Keys atau Fastly VCL), sertakan header X-Inertia ke dalam key:

# Konfigurasi Fastly VCL (vcl_hash)
sub vcl_hash {
  set req.hash += req.url;
  set req.hash += req.http.host;
  if (req.http.X-Inertia) {
    set req.hash += req.http.X-Inertia;
  }
  return (hash);
}

3. Otomasi Purge pada Pipeline CI/CD

Invalidasi cache CDN wajib terikat langsung pada pipeline deployment setelah artefak baru aktif di server origin. Tambahkan task pada pipeline deployment (contoh GitHub Actions):

- name: Purge Edge Cache
  env:
    CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    CF_ZONE: ${{ secrets.CLOUDFLARE_ZONE_ID }}
  run: |
    curl -s -f -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE/purge_cache" \
      -H "Authorization: Bearer $CF_TOKEN" \
      -H "Content-Type: application/json" \
      --data '{"tags": ["inertia-pages"]}'
    echo "Edge cache purged successfully for new release."

4. Automated Smoke Test di Pipeline CI/CD

Verifikasi isolasi respons via curl sebelum mengarahkan lalu lintas produksi seutuhnya:

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

TARGET_URL="https://staging.example.com/dashboard"

# 1. Uji Navigasi Direct Visit (Harus mengembalikan HTML)
HTML_CONTENT_TYPE=$(curl -s -o /dev/null -I -w "%{content_type}" "$TARGET_URL")
if [[ "$HTML_CONTENT_TYPE" != *"text/html"* ]]; then
  echo "FAILED: Direct visit did not yield text/html, got: $HTML_CONTENT_TYPE"
  exit 1
fi

# 2. Uji Navigasi AJAX Inertia (Harus mengembalikan JSON)
JSON_CONTENT_TYPE=$(curl -s -o /dev/null -I -w "%{content_type}" \
  -H "X-Inertia: true" \
  -H "Accept: text/html, application/xhtml+xml" \
  "$TARGET_URL")

if [[ "$JSON_CONTENT_TYPE" != *"application/json"* ]]; then
  echo "FAILED: Inertia visit did not yield application/json, got: $JSON_CONTENT_TYPE"
  exit 1
fi

# 3. Verifikasi Keberadaan Header Vary
VARY_HEADER=$(curl -s -I -H "X-Inertia: true" "$TARGET_URL" | tr -d '\r' | grep -i '^vary:' || true)
if [[ "$VARY_HEADER" != *"X-Inertia"* ]]; then
  echo "FAILED: Response missing 'Vary: X-Inertia' header. Edge cache collision imminent!"
  exit 1
fi

echo "All edge cache verification tests passed."

Kesimpulan

Caching respons dinamis pada arsitektur hybrid seperti Inertia.js menuntut pemisahan status state yang ketat. Mengandalkan path URL sebagai cache key tunggal tanpa mengevaluasi header X-Inertia melalui instruksi Vary akan selalu memicu polusi data cache. Otomasi invalidasi edge dan smoke test berbasis protokol HTTP pada CI/CD merupakan lapisan pertahanan wajib guna mencegah kebocoran raw JSON ke sisi pengguna akhir.