Akar Masalah: Lag Konsensus Global dan State Desinkronisasi

Pada arsitektur edge-first, server-side rendering (SSR) dijalankan di lokasi terdekat dengan pengguna (Point of Presence/PoP). State aplikasi sering kali disimpan di distributed consensus store (seperti Cloudflare KV/D1 dengan Raft/Paxos atau engine serupa Meerkat). Sistem ini mengutamakan ketersediaan dan latensi rendah melalui model eventual consistency atau replikasi asinkron antar-region.

Hydration mismatch terjadi ketika alur mutasi dan pembacaan state melintasi node edge yang belum menerima update konsensus terbaru:

  1. Klien mengirim mutasi (misalnya POST /api/cart) yang dikomit oleh node leader atau region primer pada versi state $V_2$.
  2. Klien melakukan navigasi instan atau hard-reload. Permintaan SSR diarahkan oleh Anycast routing ke PoP terdekat pengguna.
  3. PoP tersebut membaca local read-replica yang mengalami lag replikasi (masih pada versi state $V_1$).
  4. Edge SSR merender pohon DOM berbasis snapshot $V_1$ dan mengirimkan HTML ke browser.
  5. Runtime JavaScript pada klien (React, Vue, atau Svelte) melakukan hidrasi menggunakan state lokal yang telah ter-cache ($V_2$) atau melakukan fetch ulang langsung ke leader. Perbedaan representasi tree antara SSR markup ($V_1$) dan VDOM klien ($V_2$) memicu hydration error fatal (misal: Text content does not match server-rendered HTML).

Arsitektur Monotonic Read Menggunakan Causal Version Token

Solusi arsitektural mendasar untuk menjamin read-your-writes consistency di edge adalah propagasi Causal Version Token. Setiap kali mutasi berhasil, server menuliskan penanda monotonic (LSN, commit timestamp, atau sequence ID) ke dalam Set-Cookie atau header respons.

Ketika request SSR berikutnya masuk membawa token tersebut, edge node membandingkan token versi klien dengan versi replika lokal:

  • Replika Up-to-Date (Versi Lokal ≥ Token): Edge SSR aman merender HTML menggunakan data lokal.
  • Replika Tertinggal (Versi Lokal < Token): Node memiliki dua jalur mitigasi: Read-through to Primary (meminta data konsisten langsung dari region otoritatif) atau SSR State Bypassing (merender placeholder netral dan mendelegasikan resolusi ke hidrasi klien).

Implementasi TypeScript: Edge SSR Handler

Contoh berikut mengimplementasikan edge handler berbasis Web Fetch API standar (kompatibel dengan Cloudflare Workers, Deno, atau Fastly Compute) untuk memvalidasi state version sebelum merender markup:

interface AppState {
  version: number;
  data: {
    userId: string;
    items: string[];
  };
}

interface ConsensusStore {
  getLocalSnapshot(): Promise<AppState>;
  getPrimarySnapshot(): Promise<AppState>;
}

export async function handleEdgeSSR(
  request: Request,
  store: ConsensusStore,
  renderTemplate: (state: AppState) => string
): Promise<Response> {
  const cookies = new Map(
    (request.headers.get("Cookie") || "")
      .split("; ")
      .filter(Boolean)
      .map((c) => c.split("=") as [string, string])
  );

  const clientVersion = parseInt(cookies.get("x-state-version") || "0", 10);
  let state = await store.getLocalSnapshot();

  // ponytail: Fallback to primary adds latency. Replace with targeted cache-invalidation push when edge bus is available.
  if (state.version < clientVersion) {
    // Replikasi lokal lag, paksa monotonic read ke leader/primary store
    state = await store.getPrimarySnapshot();
  }

  const html = renderTemplate(state);

  return new Response(html, {
    headers: {
      "Content-Type": "text/html; charset=utf-8",
      "x-rendered-version": state.version.toString(),
    },
  });
}

Strategi Frontend: Deferred Hydration dan Client Reconciliation

Jika memanggil primary store via edge menambah time-to-first-byte (TTFB) secara tidak dapat diterima, mitigasi dialihkan ke sisi frontend menggunakan Deferred Hydration.

1. Isomorphic State Injection

Edge handler harus menyematkan versi data yang digunakan saat render ke dalam payload dokumen HTML:

<script>
  window.__INITIAL_STATE__ = { version: 104, data: { ... } };
</script>

2. Graceful Reconciliation pada Komponen Reaktif

Di browser, komponen yang rentan desinkronisasi (seperti keranjang belanja, lencana notifikasi, atau status auth) menahan hidrasi tampilan sampai runtime memvalidasi kesesuaian versi klien dengan SSR snapshot.

import { useState, useEffect } from 'react';

export function MonotonicHydrationBoundary({ 
  children, 
  fallback 
}: { 
  children: React.ReactNode; 
  fallback: React.ReactNode; 
}) {
  const [isSynced, setIsSynced] = useState(false);

  useEffect(() => {
    const ssrVersion = window.__INITIAL_STATE__?.version ?? 0;
    const clientStorageVersion = parseInt(localStorage.getItem('app_version') || '0', 10);

    if (ssrVersion < clientStorageVersion) {
      // Inisiasi re-fetch data mutakhir atau render state lokal
      setIsSynced(true);
    } else {
      setIsSynced(true);
    }
  }, []);

  if (!isSynced) {
    // Kembalikan markup SSR identik untuk menghindari mismatch error
    return <>{fallback}</>;
  }

  return <>{children}</>;
}

Trade-off dan Evaluasi Performa

Setiap strategi penanganan consensus lag membawa konsekuensi performa berbeda:

  • Read-through to Primary: Menjamin 100% konsistensi SSR HTML dan zero mismatch. Latensi p95 TTFB melonjak karena koneksi lintas-region ke leader.
  • Causal Version Cookie + Edge Bypass: Menjaga TTFB edge tetap optimal (<50ms). Konsekuensinya, komponen reaktif harus merender skeleton/fallback, sehingga memindahkan beban render akhir ke CPU klien (Client-side Shift).
  • Monotonic Read Validation: Pendekatan paling seimbang. Hanya pengguna yang baru saja melakukan mutasi yang terkena penalti latensi read-through, sementara mayoritas request baca (read-only traffic) tetap dilayani instan dari local replica.