-
-
Notifications
You must be signed in to change notification settings - Fork 236
Open
Labels
Description
What happened?
Summary
When using next-themes 0.4.6 with Next.js 16 (cacheComponents: true) + React 19, switching locales causes a hidden ThemeProvider to reapply its stale theme when it becomes
active again. The UI and localStorage end up out of sync and the theme flips back to the previous value.
Environment
- next-themes: 0.4.6
- Next.js: 16.0.3 (App Router)
- React: 19.2.0
- cacheComponents: true (Activity enabled)
- next-intl: 4.5.3
- Tailwind: 4.1.x
Repro steps
- In
next.config.ts, setcacheComponents: true. - Wrap the app with
ThemeProviderfrom next-themes in a Providers component (standard setup:attribute="class",storageKey="my-theme",defaultTheme="system",
enableSystem,enableColorScheme). - Route structure with locales:
app/[locale]/layout.tsx,app/[locale]/page.tsx(or any content). - Use any two locales (e.g.,
en,es) vianext-intlor similar. - Run dev server, open
/en. - Sequence:
- Toggle theme to Dark.
- Switch locale to ES.
- Toggle theme to Light.
- Switch locale back to EN.
- Observe: theme flips back to Dark (the stale value). localStorage may show Light, but the DOM class flips to the older value when the hidden tree is reactivated.
Expected
The theme should remain at the last user selection (Light after the sequence), with DOM class and localStorage in sync.
Actual
When returning to the previous locale, the hidden ThemeProvider instance preserved by Activity (cacheComponents) re-applies its stale theme, flipping the UI. Multiple
ThemeProviders appear to mount/unmount; their effects each write to storage/DOM, causing oscillation.
Notes / Hypothesis
- React 19 + Next 16 Activity keeps previous route trees “hidden” instead of unmounted. next-themes assumes a single live provider; hidden instances still run effects and
write the theme (DOM class + storage) when they become active again. - With cacheComponents disabled (so Activity is off), the bug disappears.
- A custom single-writer theme manager that applies the class directly and listens to storage events fixes the issue.
- Mitigations could be:
- Make next-themes Activity-aware (single-writer or guard DOM/storage writes when hidden).
- Key ThemeProvider per locale to force unmount on locale change, or provide an option to opt out of reapplying state from hidden trees.
- Document incompatibility with cacheComponents/Activity for now.
Minimal reproduction (shape)
- Next.js 16 app with
cacheComponents: true app/[locale]/layout.tsx+app/[locale]/page.tsx- Providers with next-themes ThemeProvider wrapping the app
- A simple language switcher using locale routing and a theme switcher calling
setTheme('light' | 'dark') - Sequence above reliably reproduces in dev
Version
0.4.6
What browsers are you seeing the problem on?
Firefox
Reactions are currently unavailable