React 19 Script Tag Warning in Next.js and the next-themes Fix
React 19 flags every script tag inside a component, including the next-themes anti-FOUC script that actually runs. Here is how I silence it in dev only.
TL;DR
next-themes renders an inline script to prevent theme flash, but React 19 warns about every script tag in components, producing a harmless false positive on Next.js 16. Since the open issue won't get fixed soon, I shipped a dev-only console.error filter that silences that exact message. Production stays untouched, and dark mode keeps working without FOUC.
I just redeployed a Next.js 16 project with dark mode via next-themes, opened the developer tools, and was immediately greeted by a glaring red error on every page load. The message was highly specific: "Encountered a script tag while rendering React component. Scripts inside React components are never executed when rendering on the client."
My first instinct was to panic. If React says the script is never executed, the theme logic must be broken, right? I briefly considered the extreme options: dropping next-themes, moving the script into a separate file, or rebuilding the whole dark mode logic from scratch.
But when I double-checked, dark mode worked fine. No flash of wrong theme (FOUC) at all. The script was actually running correctly from the server-rendered HTML. The warning turned out to be a false positive.
The root cause: the anti-FOUC inline script
After some digging, the root cause is how next-themes works. The library deliberately renders an inline script element to set the theme before hydration happens, so the page never flashes the wrong theme on load [2]. The problem: React 19 now warns about every script tag rendered inside a component, regardless of the SSR context [2].
The official React docs make this clearer. An inline script rendered via children is not deduplicated and not moved to the document head [3]. The special move-to-head and deduplication behavior only applies to external scripts that have a src attribute and async={true} [3].
Worse, React's preinit() function only accepts an href (URL) parameter, so it cannot inject inline code [3]. Right now, React simply has no official escape hatch for inline scripts that must run before paint.
I checked the next-themes repository. Issue #387 discussing this react 19 script tag nextjs warning has been open since March 2026, back when people started migrating to Next.js 16.2.1 and next-themes 0.4.6 [1].
The issue is still open today. In fact, the repository has visibly seen no meaningful updates since March 2025 [1]. So waiting for an official fix from the maintainer is not a realistic strategy for a project that needs certainty.
The fix I shipped
Instead of chasing drop-in replacement packages that are heavily self-promoted in the issue threads and remain unverified, I went with the same approach the shadcn/ui documentation team took. They addressed this by adding a dev-only console.error filter to the ThemeProvider snippet in their Next.js dark mode guide [2].
I built a tiny client component whose only job is wrapping console.error, no matter who calls it. The logic is simple: the component checks process.env.NODE_ENV === "development" and that the code is running in a browser, not on the server. When both hold, it wraps the original error function with a flag on the global window object to prevent double-wrapping, then silently ignores messages whose first argument contains the string "Encountered a script tag".
// React 19 flags every <script> inside a component, even SSR-executed ones.
// This filter silences exactly that message, dev-only.
if (process.env.NODE_ENV === "development" && typeof window !== "undefined") {
const w = window as typeof window & { __egpScriptFilter?: boolean };
if (!w.__egpScriptFilter) {
const originalError = console.error;
console.error = (...args: unknown[]) => {
if (typeof args[0] === "string" && args[0].includes("Encountered a script tag")) {
return;
}
originalError(...args);
};
w.__egpScriptFilter = true;
}
}The component renders nothing (return null). I just mount it in the layout head so the filter is active before the theme bootstrap script runs. Most importantly, this logic never touches the production environment, so the site's performance and behavior stay untouched.
Limits worth noting
This solution has limits I should admit honestly. First, I have not tested whether placing a script directly in a server component head triggers the same warning, because none of the three reference sources covers that edge case.
Second, this filter depends on exact string matching. If the React team ever reformulates the warning message, the filter could silently stop matching.
For the current situation though, this is the most reasonable trade-off. I would rather handle a false positive in development than sacrifice the user experience with FOUC, or wait for a library update that may never come.