Warning Script Tag React 19 di Next.js dan Fix next-themes
React 19 men-flag setiap script tag di komponen, termasuk script anti-FOUC next-themes yang sebenarnya jalan normal. Cara saya membungkamnya di dev saja.
Ringkasan
Pas naik ke Next.js 16, next-themes bikin error merah soal script tag di komponen React, padahal dark mode jalan normal. Ternyata cuma false positive, script anti-FOUC-nya tetap jalan, React 19 emang warning semua script inline tanpa mikir konteks SSR. Solusinya gampang, tinggal filter console.error khusus dev buat abaikan pesan itu, kayak trik shadcn/ui.
Baru saja nyoba bangun situs Next.js 16 dengan fitur dark mode pakai next-themes. Pas jalanin dev server, layar langsung merah. Overlay error muncul di setiap page load dengan pesan yang bikin deg-degan: "Encountered a script tag while rendering React component. Scripts inside React components are never executed when rendering on the client."
Insting pertama saya langsung panik. Saya kira script tema ini gagal total dieksekusi. Pikiran saya langsung lari ke opsi ekstrem: hapus next-themes, pindahin script ke file terpisah, atau malah bangun ulang logika dark mode dari nol.
Tapi pas saya cek ulang, dark mode-nya jalan normal. Nggak ada flash of wrong theme (FOUC) sama sekali. Ternyata script itu tetap berjalan dengan benar dari HTML yang di-render di server. Warning ini ternyata cuma false positive.
Akar masalah: script inline anti-FOUC
Setelah telusuri, akar masalahnya ada di cara next-themes bekerja. Library ini memang sengaja me-render elemen script inline untuk mengatur tema sebelum proses hydration terjadi, supaya nggak ada kedipan tema saat halaman dimuat [2]. Masalahnya, React 19 sekarang memberi warning untuk setiap script tag yang di-render di dalam komponen, tanpa peduli konteks SSR-nya [2].
Dokumentasi resmi React juga memperjelas hal ini. Inline script yang di-render lewat children tidak akan di-deduplikasi dan tidak dipindahkan ke document head [3]. Fitur khusus untuk pindah ke head dan deduplikasi itu cuma berlaku buat external script yang punya atribut src dan async={true} [3].
Lebih parahnya lagi, fungsi preinit() di React cuma menerima parameter href (URL), jadi nggak bisa dipakai untuk inject inline code [3]. Saat ini, React memang belum punya escape hatch resmi untuk inline script yang perlu dijalankan sebelum proses paint.
Saya sempat cek repositori next-themes. Ternyata issue #387 yang membahas warning react 19 script tag nextjs ini sudah dibuka sejak Maret 2026, saat orang-orang mulai migrasi ke Next.js 16.2.1 dan next-themes 0.4.6 [1].
Sampai sekarang, issue tersebut masih terbuka. Faktanya, repositori ini memang terlihat nggak ada update berarti sejak Maret 2025 [1]. Jadi, menunggu fix resmi dari maintainer bukanlah strategi yang realistis untuk proyek yang butuh kepastian.
Solusi yang saya terapkan
Daripada pusing mencari paket drop-in replacement yang banyak dipromosikan sendiri di thread issue dan belum terverifikasi, saya memilih pendekatan yang sama dengan tim dokumentasi shadcn/ui. Mereka mengatasi ini dengan menambahkan filter console.error khusus environment development pada snippet ThemeProvider di panduan dark mode Next.js mereka [2].
Saya membuat komponen klien kecil yang cuma bertugas membungkus console.error, siapa pun pemanggilnya. Logikanya sederhana: komponen ini mengecek apakah process.env.NODE_ENV === "development" dan kode berjalan di browser, bukan di server. Jika keduanya terpenuhi, ia membungkus fungsi error asli dengan sebuah flag di objek window global untuk mencegah double-wrapping, lalu diam-diam mengabaikan pesan yang argumen pertamanya mengandung 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;
}
}Batasan yang perlu dicatat
Solusi ini punya beberapa batasan yang perlu saya akui secara jujur. Pertama, saya belum menguji apakah menaruh script langsung di head server component akan memicu warning yang sama atau tidak, karena tidak ada dari ketiga sumber referensi yang membahas edge case tersebut.
Kedua, filter ini sangat bergantung pada pencocokan string yang persis. Kalau di masa depan tim React mengubah formulasi pesan warning tersebut, filter ini bisa saja gagal mendeteksinya secara diam-diam.
Tapi untuk kondisi saat ini, ini adalah trade-off yang paling masuk akal. Saya lebih memilih menangani false positive di development daripada mengorbankan pengalaman pengguna dengan FOUC atau menunggu update library yang nggak pasti.