Ketika aplikasi Single Page Application (SPA) berbasis Inertia.js di-deploy ke produksi, browser klien yang masih membuka tab lama akan memegang referensi ke asset chunk yang berpotensi sudah dihapus dari server. Upaya navigasi berikutnya akan menghasilkan ChunkLoadError dan merusak alur pengguna. Inertia menyelesaikan masalah ini lewat mekanisme asset versioning otomatis berbasis header HTTP.

Mekanisme Protokol Asset Versioning Inertia

Inertia mendeteksi perbedaan versi antara klien dan server dengan mengevaluasi header pada setiap request internal:

  • Pengiriman Versi: Klien menyertakan header X-Inertia-Version yang berisi hash aset frontend saat aplikasi pertama kali dimuat.
  • Validasi Middleware: Middleware HandleInertiaRequests di backend membandingkan nilai header tersebut dengan hash manifest saat ini (misalnya hasil dari Vite::manifestHash()).
  • Respons 409 Conflict: Jika hash tidak identik pada request GET, server membatalkan render komponen dan mengembalikan status HTTP 409 Conflict disertai header X-Inertia-Location: [target_url].
  • Hard Reload: Klien Inertia menangkap status 409 tersebut, lalu memaksa browser melakukan hard navigation (window.location.href) ke URL target guna mengunduh HTML dan bundle JavaScript terbaru.

Integrasi Test Backend: Memverifikasi 409 Conflict

Pengujian backend memastikan middleware bereaksi tepat terhadap versi usang dan tidak meloloskan payload JSON usang ke klien. Implementasi menggunakan Pest PHP di Laravel:

<?php

use Inertia\Inertia;

it('mengembalikan 409 conflict dan header X-Inertia-Location saat versi aset mismatch', function () {
    // ponytail: Mock versi statis untuk isolasi tes unit middleware
    Inertia::version('manifest-hash-v2');

    $response = $this->withHeaders([
        'X-Inertia' => 'true',
        'X-Inertia-Version' => 'manifest-hash-v1-outdated',
    ])->get('/dashboard');

    $response->assertStatus(409);
    $response->assertHeader('X-Inertia-Location', url('/dashboard'));
});

it('mengembalikan 200 ok saat versi aset sinkron', function () {
    Inertia::version('manifest-hash-v2');

    $response = $this->withHeaders([
        'X-Inertia' => 'true',
        'X-Inertia-Version' => 'manifest-hash-v2',
    ])->get('/dashboard');

    $response->assertOk();
    $response->assertHeaderMissing('X-Inertia-Location');
});

Pengujian di atas membuktikan bahwa request berstatus usang langsung dipotong sebelum masuk ke rendering controller.

Mitigasi Request Non-GET saat Version Mismatch Terjadi

Protokol default Inertia memperlakukan request non-GET (seperti POST, PUT, DELETE) secara berbeda saat version mismatch terjadi:

Inertia tidak langsung mengembalikan respons 409 Conflict pada request non-GET untuk mencegah eksekusi ulang aksi mutasi data secara tidak sengaja (non-idempotent action). Klien diarahkan untuk mengeksekusi request ulang via GET ke URL sebelumnya.

Untuk menghindari hilangnya data input form ketika mismatch terjadi di tengah submit transaksi:

  1. State Preservation: Gunakan form helper bawaan Inertia (useForm) dengan opsi remember: true agar form state tersimpan di browser storage.
  2. Client-Side Interception: Tangkap event global router.on('invalid', callback) untuk mendeteksi mismatch sebelum request mutasi dijalankan jika hash aplikasi diketahui sudah kedaluwarsa.

Verifikasi E2E Menggunakan Playwright

Uji integrasi browser memastikan klien Inertia merespons 409 dengan melakukan reload fisik tepat satu kali dan tidak terjebak dalam infinite reload loop akibat kegagalan sinkronisasi hash.

import { test, expect } from '@playwright/test';

test('klien mengeksekusi hard reload saat menerima 409 conflict', async ({ page }) => {
  let reloadCount = 0;

  page.on('framenavigated', (frame) => {
    if (frame === page.mainFrame()) {
      reloadCount++;
    }
  });

  await page.goto('/dashboard');
  expect(reloadCount).toBe(1);

  // Simulasikan endpoint backend merespons 409 saat navigasi client-side
  await page.route('**/settings', async (route) => {
    await route.fulfill({
      status: 409,
      headers: {
        'X-Inertia-Location': 'http://localhost/settings',
      },
    });
  });

  // Trigger navigasi Inertia
  await page.click('a[href="/settings"]');

  // Pastikan browser menjalankan navigasi penuh ke URL tujuan
  await page.waitForURL('**/settings');
  
  // Reload total harus tepat 2 (navigasi awal + 1 hard reload)
  expect(reloadCount).toBe(2);
});

Otomasi Validasi Hash di Pipeline CI

Di lingkungan CI/CD, verifikasi bahwa backend dapat membaca hash hasil build Vite tanpa kegagalan parsing manifest. Tambahkan langkah verifikasi eksplisit pada GitHub Actions:

name: CI Test Suite

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Build Frontend Assets
        run: |
          npm ci
          npm run build

      - name: Validate Manifest Generation
        run: |
          test -f public/build/manifest.json || { echo "Vite manifest missing"; exit 1; }

      - name: Run Pest Tests (with Real Manifest)
        run: |
          php artisan test --filter=InertiaVersionTest

Pipeline ini mencegah rilis kode backend yang tidak sinkron dengan lokasi output direktori build frontend, memastikan proteksi asset mismatch selalu aktif di level runtime produksi.