suppressHydrationWarning trong Next.js: Hướng dẫn sử dụng an toàn & gỡ lỗi đầy đủ

Table of Contents
Hydration là một quá trình cơ bản trong các ứng dụng React hiện đại, đặc biệt là trong các framework như Next.js, nơi Server-Side Rendering (SSR) hoặc Static Site Generation (SSG) rất phổ biến. Đây là cơ chế mà React "gắn" vào HTML được render từ máy chủ, biến đánh dấu tĩnh thành giao diện người dùng tương tác. Khi cây component React phía client không khớp chính xác với HTML được render từ máy chủ, một "lỗi không khớp hydration" xảy ra, dẫn đến các cảnh báo và khả năng không nhất quán về giao diện người dùng.
React cung cấp suppressHydrationWarning như một lối thoát để tắt các cảnh báo này. Hướng dẫn này trình bày chi tiết các trường hợp sử dụng thích hợp, những cạm bẫy tiềm ẩn và cách gỡ lỗi các vấn đề trong môi trường Next.js sản xuất bằng React 19 và Next.js 15.
Hiểu về lỗi không khớp Hydration
Lỗi không khớp hydration xảy ra khi cây component React được render trên máy chủ khác với cây component được render trên client. React mong đợi lần render phía client ban đầu sẽ tạo ra cấu trúc và nội dung DOM giống hệt như HTML được render từ máy chủ. Nếu phát hiện sự khác biệt, React sẽ ghi lại cảnh báo ở chế độ phát triển:
Warning: Prop `className` did not match. Server: "foo" Client: "bar"
Warning: Text content did not match. Server: "Hello" Client: "World"
Warning: Expected server HTML to contain a matching <tag> in <parent>.
Những cảnh báo này rất quan trọng. Chúng cho biết ứng dụng React phía client đang cố gắng tiếp quản một cấu trúc DOM không phù hợp với mong đợi của nó. Điều này có thể dẫn đến:
- Giảm hiệu suất: React có thể loại bỏ HTML được render từ máy chủ cho phần không khớp và render lại hoàn toàn trên client, làm mất đi một số lợi ích của SSR.
- Lỗi giao diện người dùng: Các nhấp nháy ngắn của nội dung không chính xác hoặc thay đổi bố cục.
- Vấn đề về khả năng truy cập: Trình đọc màn hình hoặc công nghệ hỗ trợ có thể tương tác với DOM không ổn định.
- Hành vi không mong muốn: Trình xử lý sự kiện có thể không gắn đúng cách hoặc trạng thái có thể được khởi tạo không chính xác.
Vai trò của suppressHydrationWarning
Prop suppressHydrationWarning là một thuộc tính boolean có thể được thêm vào bất kỳ phần tử HTML hoặc component React nào. Khi được đặt thành true, React sẽ chặn cảnh báo hydration cho phần tử cụ thể đó và các phần tử con của nó.
<div suppressHydrationWarning={true}>
{/* Content that might cause a hydration mismatch */}
</div>
Điều quan trọng là, suppressHydrationWarning không khắc phục lỗi không khớp cơ bản. Nó chỉ đơn thuần yêu cầu React tiếp tục hydration bất chấp sự khác biệt, mà không ghi lại cảnh báo. React vẫn sẽ cố gắng đối chiếu DOM, thường bằng cách render lại phần không khớp trên client. Do đó, nó nên được sử dụng một cách thận trọng, chỉ khi lỗi không khớp được hiểu rõ, được mong đợi và vô hại.
Các trường hợp sử dụng an toàn cho suppressHydrationWarning
Có những kịch bản cụ thể mà lỗi không khớp hydration là không thể tránh khỏi hoặc vô hại, khiến suppressHydrationWarning trở thành một giải pháp chấp nhận được.
1. Tiện ích mở rộng trình duyệt
Các tiện ích mở rộng trình duyệt có thể chèn HTML tùy ý vào DOM, thường nằm ngoài tầm kiểm soát của ứng dụng của bạn. Nội dung bên ngoài này có thể khiến React phát hiện lỗi không khớp, ngay cả khi mã ứng dụng của bạn hoàn toàn nhất quán.
Ví dụ: Một tiện ích quản lý mật khẩu có thể chèn một biểu tượng bên cạnh trường nhập liệu.
// components/LoginForm.tsx
export default function LoginForm() {
return (
<form>
<label htmlFor="username">Username</label>
<input type="text" id="username" name="username" />
<label htmlFor="password">Password</label>
{/* Browser extensions might inject elements here */}
<input type="password" id="password" name="password" suppressHydrationWarning />
<button type="submit">Login</button>
</form>
);
}
Trong trường hợp này, áp dụng suppressHydrationWarning cho phần tử input (hoặc phần tử cha của nó div) có thể ngăn chặn các cảnh báo do sự can thiệp bên ngoài gây ra.
2. Dấu thời gian và ngày tháng
Ngày và giờ vốn dĩ là đặc trưng của client. new Date() trên máy chủ sẽ phản ánh múi giờ và thời gian của máy chủ, trong khi new Date() trên client sẽ phản ánh múi giờ và thời gian cục bộ của người dùng. Điều này thường dẫn đến sự không khớp về nội dung văn bản.
Ví dụ: Hiển thị dấu thời gian "Cập nhật lần cuối".
// components/TimestampDisplay.tsx
'use client'; // This component needs client-side execution for accurate local time
import { useEffect, useState } from 'react';
interface TimestampDisplayProps {
isoString: string; // ISO string from server
}
export default function TimestampDisplay({ isoString }: TimestampDisplayProps) {
const [displayTime, setDisplayTime] = useState('');
// Option 1: Use useEffect to ensure client-side rendering only
// This is generally preferred for complex client-side logic
useEffect(() => {
const date = new Date(isoString);
setDisplayTime(date.toLocaleString());
}, [isoString]);
// Option 2: Use suppressHydrationWarning for simple cases where initial mismatch is acceptable
// The server might render a different locale/timezone, but the client will quickly correct it.
// This is applied to the element whose content might differ.
const initialDate = new Date(isoString);
const serverRenderedTime = initialDate.toLocaleString(); // This will be based on server's locale/timezone
return (
<div>
<p>
Last updated (Client-side useEffect): {displayTime}
</p>
<p suppressHydrationWarning>
Last updated (suppressHydrationWarning): {serverRenderedTime}
</p>
</div>
);
}
// Usage in a Server Component or Page:
// import TimestampDisplay from '@/components/TimestampDisplay';
// export default function MyPage() {
// const serverTime = new Date().toISOString();
// return <TimestampDisplay isoString={serverTime} />;
// }
Đối với phương pháp suppressHydrationWarning, lần render ban đầu sẽ sử dụng toLocaleString() của máy chủ, có thể khác với của client. Client sau đó sẽ render lại với toLocaleString() của riêng nó, ghi đè nội dung ban đầu mà không có cảnh báo. Đối với định dạng phức tạp hơn hoặc khi sự nhấp nháy ban đầu không thể chấp nhận được, phương pháp useEffect (Tùy chọn 1) vượt trội hơn vì nó đảm bảo giá trị phía client chỉ được render sau khi hydration.
3. Nhấp nháy chủ đề (Trạng thái ban đầu phía Client)
Khi chủ đề của người dùng (ví dụ: chế độ tối/sáng) được xác định phía client (ví dụ: từ localStorage hoặc prefers-color-scheme), máy chủ không thể biết chủ đề chính xác để render ban đầu. Điều này có thể dẫn đến "nhấp nháy nội dung không được định kiểu" (FOUC) hoặc lỗi không khớp chủ đề.
Ví dụ: Một bộ chuyển đổi chủ đề đọc từ localStorage.
// components/ThemeSwitcher.tsx
'use client';
import { useState, useEffect } from 'react';
type Theme = 'light' | 'dark';
export default function ThemeSwitcher() {
const [theme, setTheme] = useState<Theme | null>(null); // Start with null to indicate unknown theme
useEffect(() => {
// This runs only on the client
const storedTheme = localStorage.getItem('theme') as Theme || 'light';
setTheme(storedTheme);
document.documentElement.setAttribute('data-theme', storedTheme);
}, []);
const toggleTheme = () => {
setTheme(prevTheme => {
const newTheme = prevTheme === 'light' ? 'dark' : 'light';
localStorage.setItem('theme', newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
return newTheme;
});
};
// Render a placeholder or null until theme is known client-side
if (theme === null) {
return <div suppressHydrationWarning style={{ visibility: 'hidden' }}>Loading theme...</div>;
}
return (
<button onClick={toggleTheme} suppressHydrationWarning>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
);
}
Ở đây, suppressHydrationWarning được áp dụng cho nút. Máy chủ có thể render một chủ đề mặc định, nhưng client sẽ nhanh chóng đọc localStorage và cập nhật chủ đề. Cảnh báo bị chặn vì lỗi không khớp ban đầu được mong đợi và nhanh chóng được giải quyết. Một giải pháp mạnh mẽ hơn cho các chủ đề thường liên quan đến một tập lệnh chạy trước khi hydration để đặt thuộc tính data-theme trên <html> dựa trên localStorage, hoặc sử dụng các biến CSS.
4. ID không xác định
Một số thư viện hoặc logic tùy chỉnh tạo ID duy nhất phía client (ví dụ: cho các thuộc tính trợ năng như aria-labelledby). Nếu các ID này được sử dụng trong lần render ban đầu, chúng sẽ khác nhau giữa máy chủ và client.
Ví dụ: Sử dụng trình tạo ID phía client tùy chỉnh (React 18+ useId xử lý điều này tự động).
// components/UniqueIdComponent.tsx
'use client';
import { useState, useEffect } from 'react';
let globalIdCounter = 0; // Not ideal for production, but demonstrates the concept
function generateUniqueId() {
globalIdCounter += 1;
return `client-id-${globalIdCounter}`;
}
export default function UniqueIdComponent() {
const [id,
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Lỗi Hydration trong Next.js: Cách sửa "Text Content Mismatch" & Error 418 (2026)
Hướng dẫn sửa triệt để lỗi Hydration failed (#418), Text content does not match server-rendered HTML, lỗi dark mode flash next-themes và localStorage trong Next.js.
Read more
Quản lý trạng thái trong React 2026: Vượt xa Redux
Hướng dẫn toàn diện về quản lý trạng thái React năm 2026: so sánh React 19 actions, trạng thái máy chủ TanStack Query, Zustand, Jotai và Signals.
Read more
Nắm vững Metadata & Open Graph trong Next.js 14+: Thẻ xã hội động ở quy mô lớn
Biến các lượt chia sẻ trên mạng xã hội thành động lực thúc đẩy lưu lượng truy cập tự nhiên khổng lồ bằng cách nắm vững generateMetadata của Next.js, thẻ Open Graph, Twitter Cards và tạo hình ảnh Edge OG động.
Read more