fix(theme): use next/script beforeInteractive — <script> in RSC was breaking the flight client

Next.js 16 + React 19 reject <script> tags rendered from React
components: 'Encountered a script tag while rendering React component.
Scripts inside React components are never executed when rendering on the
client.' The error fires during RSC rendering for every page that uses
the root layout, which corrupts Turbopack's flight stream and surfaces
as 'chunk.reason.enqueueModel is not a function' on every navigation
(vercel/next.js#92362).

The dev-overlay recommendation to use <template> is misleading — that
won't execute a script. The supported App Router pattern is
<Script strategy="beforeInteractive"> from next/script, which inlines
the script in <head> before hydration. Theme behaviour is unchanged.
This commit is contained in:
Dave 2026-09-17 11:22:40 +02:00
parent 5c26f39f2c
commit 950c3fb8ee

View file

@ -1,10 +1,19 @@
import Script from "next/script";
/**
* Applies the stored theme before first paint.
*
* This has to be a blocking inline script in <head>. A React effect runs after
* hydration, by which point the browser has already painted the default theme
* and the user sees a white flash on every navigation to a dark board. There
* is no way to fix that from inside React.
* Has to be inline and before hydration. A React effect runs after hydration,
* by which point the browser has already painted the default theme and the
* user sees a white flash on every navigation to a dark board.
*
* Renders via `next/script` with `beforeInteractive` rather than a plain
* `<script>` element. Next.js 16 + React 19 reject `<script>` inside React
* components because the React renderer does not execute them, and the
* resulting error during RSC streaming corrupts Turbopack's flight client
* (the `chunk.reason.enqueueModel is not a function` crash in
* vercel/next.js#92362). `beforeInteractive` is the supported way to inject
* a blocking inline script in the App Router.
*/
const SCRIPT = `
(function () {
@ -19,5 +28,9 @@ const SCRIPT = `
`;
export function ThemeScript() {
return <script dangerouslySetInnerHTML={{ __html: SCRIPT }} />;
return (
<Script id="theme-init" strategy="beforeInteractive">
{SCRIPT}
</Script>
);
}