Penyebab Utama Hydration Mismatch pada Prolly Tree

Hydration error pada visualisasi riwayat diff berbasis Prolly Tree (Probabilistic B-Tree) terjadi ketika pohon DOM yang dihasilkan server berbeda bit demi bit dengan hasil evaluasi browser pada fase rehidrasi React. Prolly Tree membagi node menggunakan fungsi content-defined chunking berbasis rolling hash (misalnya Buzhash atau Gear hash). Dua penyebab utama kegagalan determinisme antara Node.js (SSR) dan V8 Browser adalah:

  • Inkonsistensi Floating-Point dan Seed Entropy: Perhitungan ambang batas probabilitas boundary yang melibatkan operasi Math.random(), floating-point precision mismatch, atau inisialisasi tabel look-up hash dengan seed non-deterministik antar environment.
  • State Boundary Serialization: Serialisasi BigInt hash 64-bit ke JSON standar yang terpotong menjadi IEEE 754 float sebelum sampai di client.

Kasus Komponen Bermasalah

Komponen di bawah ini memicu Hydration failed because the initial UI does not match what was rendered on the server karena menggunakan generator boundary acak non-deterministik pada proses chunking di komponen render:

// components/ProllyDiffViewer.tsx (Bermasalah)
import React from 'react';

interface ProllyNode {
  key: string;
  val: string;
  isBoundary?: boolean;
}

function calculateBadBoundary(key: string): boolean {
  // Bug: Math.random() merusak determinisme SSR vs Client
  const hash = Array.from(key).reduce((acc, c) => acc + c.charCodeAt(0), 0);
  return (hash ^ Math.floor(Math.random() * 100)) % 4 === 0;
}

export function ProllyDiffViewer({ items }: { items: { key: string; val: string }[] }) {
  const nodes: ProllyNode[] = items.map((item) => ({
    ...item,
    isBoundary: calculateBadBoundary(item.key),
  }));

  return (
    <div className="prolly-tree">
      {nodes.map((node) => (
        <span key={node.key} className={node.isBoundary ? 'boundary-node' : 'leaf-node'}>
          {node.key}:{node.val}
        </span>
      ))}
    </div>
  );
}

Solusi 1: Rolling Hash Deterministik untuk Node Boundary

Perhitungan boundary wajib deterministik murni terhadap isi byte data tanpa ketergantungan pada runtime engine. Gunakan Gear hash atau truncated rolling hash 32-bit integer murni:

// lib/prolly-boundary.ts
const MASK = 0x0000000f; // Target chunk size rata-rata: 16 item

export function isProllyBoundary(data: Uint8Array): boolean {
  let hash = 0x811c9dc5; // FNV-1a 32-bit offset basis
  for (let i = 0; i < data.length; i++) {
    hash ^= data[i];
    hash = Math.imul(hash, 0x01000193); // Perkalian integer 32-bit murni
  }
  return (hash & MASK) === 0;
}

export function evaluateChunkBoundary(key: string, val: string): boolean {
  const encoder = new TextEncoder();
  const bytes = encoder.encode(key + ":" + val);
  return isProllyBoundary(bytes);
}

Solusi 2: Two-Pass Rendering dan Dynamic Client Boundary

Jika diff Prolly Tree harus dihitung secara lokal di client dari indexedDB atau WebAssembly worker yang tidak tersedia di server, isolasi visualisasi menggunakan client boundary atau pola two-pass rendering dengan placeholder skeleton saat SSR.

// components/SafeProllyDiffViewer.tsx
'use client';

import React, { useState, useEffect } from 'react';
import { evaluateChunkBoundary } from '@/lib/prolly-boundary';

interface DiffItem {
  key: string;
  val: string;
}

export default function SafeProllyDiffViewer({ items }: { items: DiffItem[] }) {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) {
    // Render skeleton identik pada server dan initial client render
    return <div className="skeleton">Memuat visualisasi struktur tree...</div>;
  }

  return (
    <div className="prolly-diff-grid">
      {items.map((item) => {
        const isBoundary = evaluateChunkBoundary(item.key, item.val);
        return (
          <div
            key={item.key}
            className={`node ${isBoundary ? 'border-red-500 font-bold' : 'border-gray-200'}`}
          >
            <span>{item.key}</span>
            <span>{item.val}</span>
          </div>
        );
      })}
    </div>
  );
}

Alternatif Lebih Sederhana

Hindari menghitung boundary Prolly Tree di dalam layer presentasi React. Hitung status chunking di backend/API route, lalu kirim struktur data statis ke frontend via Server Component (RSC). Dengan begitu, React hanya merender plain JSON tanpa mengeksekusi logika hash di browser.