Hydration mismatch terjadi ketika Document Object Model (DOM) hasil render server tidak identik dengan Virtual DOM yang dihasilkan klien saat proses hidrasi pertama kali dieksekusi oleh framework frontend seperti React atau Vue. Pada arsitektur hybrid di mana Go Fiber bertindak sebagai web server atau SSR orchestrator, ketidaksesuaian ini hampir selalu berakar pada perbedaan cara Go dan JavaScript menginterpretasikan tipe data dasar saat serialisasi state.
Akar Masalah Hydration Mismatch pada Go Fiber
Saat Fiber merender template HTML dan menyuntikkan initial state untuk dikonsumsi frontend, terdapat tiga anomali serialisasi yang memicu error hidrasi di konsol browser:
- Pemotongan Presisi
int64: Tipe data integer 64-bit pada Go dapat bernilai hingga9.223.372.036.854.775.807. JavaScript menggunakan spesifikasi IEEE 754 double-precision float untuk semua tipeNumber, dengan batas amanNumber.MAX_SAFE_INTEGERsebesar9.007.199.254.740.991(253 - 1). Nilai seperti Snowflake ID atau ID database berukuran 64-bit akan dibulatkan oleh parser JSON browser, menghasilkan teks ID yang berbeda antara server dan klien. - Perbedaan Format dan Timezone
time.Time: Serialisasi bawaan Go untuktime.Timemenghasilkan string berformat RFC3339. Jika server Fiber berada pada UTC sedangkan browser klien berada pada zona waktu lokal (misal: UTC+7), formatting tanggal secara langsung pada komponen frontend sebelum hidrasi selesai akan menghasilkan teks tanggal yang berbeda antara SSR dan Client. - HTML Escaping Corrupt pada Serializer: Menggunakan fungsi serialisasi standar pada engine template Fiber (seperti
html/template) dapat mengubah karakter seperti<,>, dan&menjadi entitas HTML (<,&). Ketika browser mencoba mengurai inline script tersebut, parsing JSON menjadi gagal atau string teks menjadi tidak cocok.
Solusi 1: Standardisasi Data Transfer Object (DTO) di Go
Jangan pernah mengekspos model database internal langsung ke serialisasi template. Gunakan DTO yang secara eksplisit mengubah nilai non-primitif JavaScript menjadi tipe yang aman.
package dto
import (
"strconv"
"time"
)
type ProductResponse struct {
ID string `json:"id"` // Diserialisasi sebagai string untuk menjaga presisi int64
Name string `json:"name"`
Price int64 `json:"price"` // Aman selama di bawah Number.MAX_SAFE_INTEGER
CreatedAt string `json:"createdAt"` // Format baku ISO-8601/RFC3339
}
func FromDomain(id int64, name string, price int64, createdAt time.Time) ProductResponse {
return ProductResponse{
ID: strconv.FormatInt(id, 10),
Name: name,
Price: price,
CreatedAt: createdAt.UTC().Format(time.RFC3339),
}
}
Solusi 2: Injeksi State Aman Menggunakan Data Island
Hindari penulisan inline JavaScript seperti window.__INITIAL_STATE__ = {{ .State }}; karena berisiko Cross-Site Scripting (XSS) dan rentan terhadap kesalahan translasi escaping karakter oleh HTML parser. Pendekatan standar industri adalah menggunakan tag <script type="application/json"> (sering disebut JSON Data Island).
Konfigurasi Fiber Handler
package main
import (
"encoding/json"
"html/template"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
)
type ViewPayload struct {
Title string
StateJSON template.HTML // Mencegah double-escaping saat disisipkan ke template
}
func main() {
engine := html.New("./views", ".html")
app := fiber.New(fiber.Config{
Views: engine,
})
app.Get("/products", func(c *fiber.Ctx) error {
// Contoh data
data := map[string]interface{}{
"productId": "9223372036854775806", // BigInt aman
"serverTime": time.Now().UTC().Format(time.RFC3339),
"title": "Katalog Produk",
}
jsonBytes, err := json.Marshal(data)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Serialization error")
}
return c.Render("index", ViewPayload{
Title: "Katalog Produk",
StateJSON: template.HTML(jsonBytes),
})
})
app.Listen(":3000")
}
Template HTML (views/index.html)
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>{{ .Title }}</title>
</head>
<body>
<div id="app"><!-- Markup awal server-rendered di sini --></div>
<!-- JSON Data Island: Tidak dieksekusi sebagai JS, aman dari parsing engine browser -->
<script id="__SERVER_STATE__" type="application/json">
{{ .StateJSON }}
</script>
<script src="/bundle.js"></script>
</body>
</html>
Solusi 3: Parsing dan Hidrasi State di Sisi Frontend
Di sisi klien, baca isi elemen script tersebut dan lakukan parsing JSON murni sebelum memulai siklus hidup rendering framework. Hindari format tanggal berbasis locale pengguna pada render pertama jika komponen dirender secara identik di server.
Implementasi di Klien (React / Vue)
// entry-client.js
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App';
function getInitialState() {
const stateElement = document.getElementById('__SERVER_STATE__');
if (!stateElement || !stateElement.textContent) {
return null;
}
try {
return JSON.parse(stateElement.textContent);
} catch (err) {
console.error('Gagal mem-parsing state server:', err);
return null;
}
}
const initialState = getInitialState();
const container = document.getElementById('app');
if (container && initialState) {
hydrateRoot(container, <App initialData={initialState} />);
}
Menangani Format Waktu pada Komponen
Jika representasi tanggal lokal diperlukan, tunda konversi timezone hingga hidrasi selesai untuk mencegah ketidaksesuaian tampilan DOM antara server dan browser:
// Component DateDisplay.jsx
import React, { useState, useEffect } from 'react';
export function DateDisplay({ isoDateString }) {
// Render awal menggunakan format ISO yang sama persis dengan SSR
const [formattedDate, setFormattedDate] = useState(isoDateString);
useEffect(() => {
// Efek ini hanya dieksekusi di browser setelah hidrasi tuntas
setFormattedDate(new Date(isoDateString).toLocaleDateString());
}, [isoDateString]);
return <span>{formattedDate}</span>;
}
Verifikasi dan Debugging
Untuk memastikan hidrasi berjalan tanpa mismatch, terapkan langkah verifikasi berikut:
- Periksa Console Output: Mode pengembangan React (
React 18+) atau Vue 3 akan memunculkan peringatan "Hydration failed because the initial UI does not match what was rendered on the server" secara detail beserta perbedaan diff text atau atribut jika ada ketidaksesuaian. - Nonaktifkan JavaScript: Matikan JavaScript di browser via DevTools, muat ulang halaman, dan catat representasi tanggal atau ID numerik. Aktifkan kembali JavaScript lalu verifikasi apakah elemen teks mengalami pergeseran bentuk (flicker) seketika setelah script selesai dimuat.
- Validasi Encoding Payload: Periksa tab Elements di DevTools, pastikan tag
<script type="application/json">tidak memuat entitas HTML seperti"melainkan representasi karakter kutip ganda valid (").
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!