Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

next-themes-inert

A faithful port of next-themes 0.4.6 (MIT, © Paco Coursey) with one behavioral change: the anti-flash theme-init script is emitted as raw HTML through a hidden <div dangerouslySetInnerHTML> instead of a JSX <script> element.

Why

React refuses to mount <script> elements during client rendering — it logs

Encountered a script tag while rendering React component. Scripts inside React components are never executed when rendering on the client.

and silently drops the tag from the DOM. Upstream's inline <script> works only while the ThemeProvider never remounts. Any client-side remount triggers the error and loses the script — typical cases:

  • a root layout inside a dynamic segment (e.g. app/[locale]/layout.js), remounted by a client-side locale switch;
  • Fast Refresh remounts during development.

Scripts inside innerHTML are inert, which is exactly the behavior wanted here: the browser executes the script while parsing the server-rendered document (so there is no flash of the wrong theme), and on a client remount it is not re-executed — the provider's effects re-apply the theme instead.

Install

No build step, no dependencies — installable straight from a git URL:

npm install github:<owner>/next-themes-inert
# or, pinned to a tag/commit (recommended):
npm install github:<owner>/next-themes-inert#v1.0.0

Usage

Drop-in for the next-themes API surface listed below:

import { ThemeProvider, useTheme } from "next-themes-inert";

Because the script wrapper is a <div>, render the provider inside <body>, not <head> (that is where it normally lives anyway):

export default function RootLayout({ children }) {
  return (
    <html suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

useTheme() returns { theme, setTheme, resolvedTheme, themes, systemTheme, forcedTheme } with upstream semantics (theme is undefined until hydration). ThemeProvider accepts upstream's props: themes, defaultTheme, attribute (default "data-theme"), value, forcedTheme, enableSystem, enableColorScheme, disableTransitionOnChange, storageKey, nonce.

Differences from upstream 0.4.6

  • The theme-init script is injected inertly (see Why). Everything about when it runs on a full page load is unchanged.
  • The undocumented scriptProps prop is not supported.
  • A nonce is rendered on both server and client (upstream blanks it on the client to dodge a hydration quirk that the inert wrapper doesn't have).
  • Ships readable, unminified ES module source; no CJS build.

Security notes

  • Zero runtime dependencies; the only peer is react >= 18.
  • No network access, no storage beyond localStorage[storageKey].
  • ThemeProvider config values are serialized into an inline script. They are meant to be compile-time constants in your layout — do not feed them from user input.

Maintenance

The source is one file (index.js), hand-ported from upstream's published dist. To audit against upstream:

npm pack next-themes@0.4.6 && tar xzf next-themes-0.4.6.tgz
# compare package/dist/index.mjs with index.js

If upstream ships a fix you need, port it and bump the version tag.

About

Port of [next-themes](https://github.com/pacocoursey/next-themes) used in our projects

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages