dark mode flashes white for about 130ms on every hard reload, next.js app router Dark Mode
Class strategy. A useEffect in a client component reads localStorage and sets document.documentElement.classList.add('dark').
Profiled it with 4x CPU throttle: first paint at 82ms, fully light. Class lands at 214ms. So there is a solid 130ms of white page before it snaps to dark, and on a cold load on a phone it is worse.
I have moved the effect higher up the tree three times now and it obviously does not help. Timeline attached. What is the actual fix here, not a hack?
@lowell_frame · 3h ago · 3 replies
You cannot fix this in React, and moving the effect up the tree will never work, because every effect runs after hydration and hydration runs after paint. The class has to be on
<html>before the browser paints anything, which means a synchronous script in<head>.Two things people leave off. Set
suppressHydrationWarningon<html>, because the server rendered it without the class and React will complain about a mismatch it cannot do anything about. And setcolor-scheme, otherwise form controls, the scrollbar and the default canvas stay light and you get a smaller version of the same flash.Cost is about 2ms of parse. That is the entire fix.
Reply
Report
@pattern_tracer · 16h ago
Worth adding
<meta name="color-scheme" content="dark light">as well. The browser paints its own default canvas before your stylesheet is applied, and on a slow connection that shows up as a white frame even with the script in place.Reply
Report
@burr_and_edge · 3h ago
If you are already using next-themes this is literally what it injects, so a flash usually means the provider ended up under something that made the whole subtree client-rendered, or the script got stripped by a wrapper. Worth checking the actual HTML in view-source rather than devtools.
Reply
Report