@ecosy/next-themes
next-themes with React 19 support and three fixes not yet in an upstream release.
A fork of next-themes. The API is unchanged — use the upstream documentation:
Nothing on this page repeats it. What follows is only what differs, because a fork that documents itself twice is a fork whose two copies drift apart.
Why it exists#
next-themes@0.4.6 renders its inline script as a <script> tag from a React
component. On the server that works: the script arrives in the initial HTML,
the browser's parser runs it, and the theme is applied before first paint.
On the client it does not. React inserts that tag through the DOM rather than the HTML parser, and a script inserted that way never executes — so on a client-side render the theme was never applied and the page flashed.
What changed#
1. The client applies the theme through useInsertionEffect, not a script tag.
ThemeScript now calls the theme function directly in a useInsertionEffect.
That hook fires synchronously before the browser paints the mutation, which is
what keeps the flash away — a useEffect would run a frame too late.
2. The script tag is still rendered, on both sides.
The tag in the initial HTML is what prevents a flash before hydration, and no effect can run early enough to replace it, so the server keeps emitting it.
It is rendered on the client too — not because the client needs it to run, but
because hydration needs the two trees to match. Returning it on the server and
null on the client raised Hydration failed because the server rendered HTML
didn't match the client and re-rendered the subtree, which is the flash this
component exists to prevent. suppressHydrationWarning does not help:
it forgives an element's attributes, not its absence.
On a client render React inserts the tag through the DOM, where it does not execute — that is what change 1 is for. Fixed in 0.4.7; 0.4.6 has the mismatch.
3. A theme missing from value no longer leaks its internal name.
Pre-existing upstream bug. Given value={{ dark: 'dark-mode' }} and a theme
with no entry, updateDOM fell back to the raw theme name — writing an
internal identifier into the DOM as if it were a class or attribute value.
It now skips instead: the class is not added, and the attribute is removed rather than set to something meaningless.
Also carried#
The fork branches after upstream's v0.4.6 tag, so it also carries three
commits that upstream has merged but not yet published — next-themes on npm
is still 0.4.6 as well:
setThemereceives the latest state (#347)<ThemeScript>is exported (#355)PropsWithChildrentyping under@types/react17 (#356)
Installing#
yarn add @ecosy/next-themes
Same API, different package name — so the import specifier changes:
- import { ThemeProvider } from "next-themes";
+ import { ThemeProvider } from "@ecosy/next-themes";
Or alias next-themes to it in your bundler and leave every import alone.
The version numbers ran together at 0.4.6#
The fork started at upstream's version so the lineage stayed readable, which
made next-themes@0.4.6 and @ecosy/next-themes@0.4.6 two different builds
under one number. From 0.4.7 the fork moves on its own; upstream is still
at 0.4.6.
When to go back#
When upstream ships React 19 support. This fork exists for one reason, and it should stop existing when that reason does.