Nguyên nhân: trình duyệt vẽ trang bằng theme mặc định trước, mãi tới khi JavaScript của app chạy xong (đọc localStorage, set state) mới đổi sang dark. Khoảng giữa hai mốc đó chính là nhịp nháy trắng.
Cách xử lý là quyết định theme trước khi trang được vẽ lần đầu bằng một script chặn nhỏ đặt ngay trong <head>:
html
<script>
(function () {
var saved = localStorage.getItem('theme')
var dark = saved ? saved === 'dark'
: matchMedia('(prefers-color-scheme: dark)').matches
document.documentElement.dataset.theme = dark ? 'dark' : 'light'
})()
</script>Đây là trường hợp hiếm hoi mà script đồng bộ, chặn render là đúng: nó chỉ vài dòng và phải chạy xong trước lần vẽ đầu tiên. Đặt thuộc tính lên <html> (không phải <body>) vì <body> chưa tồn tại lúc đó.
Các điểm liên quan:
- CSS viết theo token, đổi giá trị theo
[data-theme='dark'], để đổi theme chỉ là đổi một thuộc tính chứ không phải thay class ở nhiều nơi. - Ba trạng thái cần hỗ trợ: sáng / tối / theo hệ thống. Chỉ khi người dùng chọn tay mới ghi vào
localStorage; để trống nghĩa là theo hệ thống. - Khai báo
color-scheme(qua CSS hoặc thẻ meta) để form control, thanh cuộn và nền mặc định của trình duyệt cũng đổi theo. - Với SSR, server không biết theme của client nên markup luôn có nguy cơ lệch với client; script trong
<head>giải quyết trước khi React hydrate.