Hydration error pada aplikasi penampil dokumen kontrak interaktif berbasis Server-Side Rendering (SSR) terjadi ketika struktur DOM yang dihasilkan Node.js tidak identik dengan pohon DOM awal di browser. Mismatch ini memicu peringatan runtime di framework seperti React atau Vue, merusak event listener klausul hukum, hingga memicu render ulang seluruh tree yang mematikan performa.
Root Cause: Perbedaan Sanitasi dan Parsing DOM SSR vs Client
Penyebab utama inkonsistensi rendering dokumen legal berbasis rich-text/Markdown terbagi menjadi tiga faktor struktural:
- Parser Normalization Mismatch: Pustaka sanitasi server-side berbasis JSDOM atau regex parser menghasilkan entitas teks yang berbeda dibandingkan API bawaan browser (
DOMParser). Contohnya, karakter void elements seperti tag<br>atau penutupan otomatis pada tag paragraf bersarang (<p><div>...</div></p>) akan dinormalisasi secara berbeda oleh browser engine. - Entitas Non-Breaking Space (
vs\u00A0): Parser backend sering merender entitas teks literal , sedangkan browser parser langsung mengonversinya menjadi byte\u00A0dalam DOM text node, menghasilkan text mismatch saat rekonsiliasi. - Interferensi Ekstensi Browser: Alat penerjemah, pemeriksa tata bahasa, atau password manager sering menyuntikkan node seperti
<font>, class tambahan, atau atributdata-*ke dalam elemen teks hukum sebelum proses hidrasi selesai.
Sanitasi Deterministik dan Isolasi dangerouslySetInnerHTML
Untuk menghindari perbedaan output, sanitasi string kontrak harus mengacu pada satu konfigurasi deterministik. Gunakan isomorphic sanitizer (seperti isomorphic-dompurify) dan hilangkan entitas karakter whitespace yang berpotensi membelah token.
import DOMPurify from 'isomorphic-dompurify';
export function sanitizeContract(rawHtml: string): string {
// Normalisasi token whitespace non-breaking ke standar single-space atau entitas eksplisit
const normalized = rawHtml
.replace(/\u00A0/g, ' ')
.replace(/ /gi, ' ');
return DOMPurify.sanitize(normalized, {
ALLOWED_TAGS: ['p', 'b', 'i', 'strong', 'em', 'ul', 'ol', 'li', 'span', 'br'],
ALLOWED_ATTR: ['class', 'data-clause-id'],
RETURN_TRUSTED_TYPE: false,
});
}Render kontainer kontrak di dalam elemen pembungkus terisolasi menggunakan flag supresi yang tepat untuk komponen yang terpapar ekstensi eksternal:
interface ContractViewerProps {
htmlContent: string;
}
export function ContractClauseViewer({ htmlContent }: ContractViewerProps) {
const cleanHtml = sanitizeContract(htmlContent);
return (
<div
className="contract-content-body"
dangerouslySetInnerHTML={{ __html: cleanHtml }}
suppressHydrationWarning={true}
/>
);
}Catatan: Atribut suppressHydrationWarning hanya membungkam peringatan pada kedalaman satu level (atribut dan teks langsung). Elemen bersarang kompleks tetap harus deterministik sejak tahap sanitasi string.Penundaan Reaktivitas Checkbox Klausul Tanpa Layout Shift
Fitur interaktif seperti checkbox persetujuan Non-Disclosure Agreement (NDA) sering memicu layout shift jika dimuat bersyarat via isMounted rendering. Terapkan strategi isolasi state di mana elemen dirender secara statis di SSR, namun status interaktivitas (enabled/disabled) dideferensiasi setelah hidrasi selesai.
import { useState, useEffect } from 'react';
interface ClauseAgreementProps {
clauseId: string;
onAgree: (id: string, agreed: boolean) => void;
}
export function ClauseAgreementCheckbox({ clauseId, onAgree }: ClauseAgreementProps) {
const [hydrated, setHydrated] = useState(false);
const [checked, setChecked] = useState(false);
// ponytail: fallback direct browser check, upgrade jika ada global context hydration
useEffect(() => {
setHydrated(true);
}, []);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setChecked(e.target.checked);
onAgree(clauseId, e.target.checked);
};
return (
<label className="clause-checkbox-wrapper" style={{ minHeight: '24px', display: 'flex' }}>
<input
type="checkbox"
id={`checkbox-${clauseId}`}
checked={checked}
disabled={!hydrated}
onChange={handleChange}
/>
<span className="clause-checkbox-label">
Saya membaca dan menyetujui seluruh klausul rahasia (NDA) di atas.
</span>
</label>
);
}Verifikasi dan Testing Hidrasi
Jalankan pemeriksaan lokal untuk memastikan pohon DOM konsisten sebelum masuk tahap deployment:
- Nonaktifkan JavaScript di DevTools: Periksa apakah dokumen kontrak dirender lengkap tanpa elemen kosong.
- Bandingkan Snapshot Hash: Hitung hash SHA-256 dari output SSR HTML dan bandingkan dengan
document.querySelector('.contract-content-body').innerHTMLsaat runtime pertama browser. - Audits Layout Shift (CLS): Pastikan penundaan hidrasi checkbox memiliki reserved container width/height via CSS untuk menjaga nilai Cumulative Layout Shift tetap
0.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!