Hydration
Server gửi HTML tĩnh giúp hiển thị tức thì; client tải JS về để 'tưới nước' (hydrate) - gắn event listener và phục hồi state nội bộ.
Hydration là gì và tại sao nó đắt đỏ?
Khi áp dụng Server-Side Rendering (SSR) hoặc Static Site Generation (SSG), server sẽ sinh sẵn HTML và gửi về cho trình duyệt. Trình duyệt nhanh chóng dựng HTML này lên màn hình, giúp người dùng thấy giao diện gần như ngay lập tức (chỉ số FCP cực tốt). Tuy nhiên, đây chỉ là một 'bức tranh tĩnh' — các nút bấm chưa thể click, các ô nhập liệu chưa thể tương tác vì chưa có mã JavaScript xử lý sự kiện.
Quá trình Hydration (tưới nước) là bước tiếp theo khi client tải xong bundle JavaScript. Framework (như React hoặc Vue) sẽ duyệt qua toàn bộ cây DOM tĩnh đang hiển thị, khớp nối nó với cây DOM ảo (Virtual DOM) được dựng ở phía client, gắn các bộ lắng nghe sự kiện (event listeners) và khôi phục các trạng thái ứng dụng (state). Quá trình này biến trang web tĩnh từ 'đóng băng' thành 'sống động' và có thể tương tác.
Hãy lưu ý: Hydration là một chi phí thuần túy (overhead). Nó không vẽ thêm bất kỳ pixel mới nào lên màn hình, nhưng nó ngốn CPU đáng kể vì phải tải file JS khổng lồ, parse, compile, duyệt cây DOM và chạy logic gắn kết trên Main Thread, trực tiếp ảnh hưởng tiêu cực đến chỉ số thời gian tương tác INP (Interaction to Next Paint).
NÊNLuôn đảm bảo cấu trúc HTML tạo ra từ Server và Client trùng khớp đến từng byte (byte-identical) trong lần render đầu tiên.
CẢNH BÁOTránh xa việc gọi các API chỉ tồn tại trên trình duyệt như `window`, `document`, `localStorage` hoặc các hàm sinh dữ liệu ngẫu nhiên/thời gian (`Math.random()`, `new Date()`) trực tiếp trong chu kỳ render đồng bộ đầu tiên.
Hydration Mismatch và cách khắc phục triệt để
Lỗi Hydration Mismatch xảy ra khi cấu trúc HTML do server sinh ra khác biệt so với cấu trúc mà client tính toán được ở lần render đầu tiên. Khi phát hiện mismatch, React sẽ phải vứt bỏ phần DOM tĩnh bị lệch từ server và tiến hành render lại toàn bộ subtree đó ở phía client. Điều này không chỉ gây lãng phí hiệu năng nghiêm trọng (re-render O(n)) mà còn gây ra hiện tượng giật lag, nhấp nháy giao diện (Layout Shift) rất mất thẩm mỹ.
Để khắc phục, các dữ liệu động chỉ có ở client (như timezone, thông tin đăng nhập trong localStorage, kích thước màn hình) bắt buộc phải được trì hoãn. Chúng ta chỉ nên đọc và cập nhật chúng sau khi component đã được 'mount' thành công lên DOM thực tế (tức là bên trong useEffect hoặc onMounted), lúc này quá trình hydration đã hoàn tất an toàn.
import React, { useState, useEffect } from 'react';
function DynamicClock() {
// Khởi tạo state bằng null hoặc giá trị placeholder tĩnh giống hệt server
const [timeString, setTimeString] = useState<string | null>(null);
useEffect(() => {
// Chỉ chạy sau khi component mount thành công trên client-side
// Luỹ này, hydration đã hoàn tất, cập nhật state động hoàn toàn an toàn
setTimeString(new Date().toLocaleTimeString());
}, []);
return (
<div className="clock-wrapper">
Thời gian hiện tại: <span className="time-value">{timeString ?? "—"}</span>
</div>
);
}VÌ SAOKhi hoãn cập nhật bằng useEffect, lần render đầu tiên ở client vẫn trả về HTML giống hệt server (chứa '—'), giúp hydration thành công mượt mà. Ngay sau đó, useEffect kích hoạt lượt re-render thứ hai để điền dữ liệu thật.