Prevent page flash in Next.js 12 with Tailwind CSS class-based dark mode

Viewed 1763

How can one prevent the page flash when using class-based dark mode in Tailwind CSS with Next.js v12 without using any 3rd party pkgs like next-themes?

I've looked at:

// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.classList.add('dark')
} else {
    document.documentElement.classList.remove('dark')
}

I think they restrict putting things in Head from v12 to prepare for Suspense / Streaming / React v18.

Either way, I'm lost on how to do it without next-themes, does anyone know how can we just inject that bit of script to prevent that page flash?

Hope this question makes sense, if not, please give me a shout.

I like simple and minimalistic things, hence the aim to reduce the dependency on 3rd party pkgs, such a simple thing should be possible without overcomplicated solution IMO.

1 Answers

I had the same problem and solved it like this in Next 12.1.0:

  1. Create theme.js within the public folder (mine has the following content):
;(function initTheme() {
  var theme = localStorage.getItem('theme') || 'light'
  if (theme === 'dark') {
    document.querySelector('html').classList.add('dark')
  }
})()
  1. Add <Script src="/theme.js" strategy="beforeInteractive" /> to _app.tsx or _app.jsx. This is important - I tried putting it inside _document.tsx which didn't work. The final _app.tsx looks like this:
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Head from 'next/head'
import Script from 'next/script'

function App({ Component, pageProps }: AppProps) {
  return <>
    <Head>
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    </Head>
    <Script src="/theme.js" strategy="beforeInteractive" />
    <Component {...pageProps} />
  </>
}

export default App

Works like a charm for me - no flickering and all Next.js magic. :) Hope this works for you too.

Related