pacocoursey / next-themes

Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme with no flashing
https://next-themes-example.vercel.app/
MIT License
5.01k stars 179 forks source link

Flash Of Unstyled Content (FOUC) on Live Website #288

Closed dapatcho closed 5 months ago

dapatcho commented 5 months ago

Has anyone encountered FOUC on live but not dev? The default colour scheme is the light mode, which flashes briefly if the user's preferences are dark mode before switching to the dark mode theme. This is not the case for my "npm run dev" environment. My website is here: mboulos.dev

I have the ThemeProvider component inserted on the _app.jsx page (link below) as per the docs from what I understand. Would appreciate any help troubleshooting this.

https://github.com/dapatcho/no-round-corners/blob/3d0e33d4e57afad0eb2c48c40e27ae33d6ce932b/src/pages/_app.jsx#L4

ethan-web-dev commented 5 months ago

Clear your cache, check local machine configurations. There is no FOUC on your website when I navigate to it in Chrome, Firefox or Safari... in fact your app looks great!

dapatcho commented 5 months ago

Clear your cache, check local machine configurations. There is no FOUC on your website when I navigate to it in Chrome, Firefox or Safari... in fact your app looks great!

Hi Ethan thank you for this. Do you have it set to the dark mode theme or the light mode theme? In light mode there is no FOUC, but in the dark mode it flashes the white theme. On my end and other machines too.

ethan-web-dev commented 5 months ago

I set it to both, initially dark mode then closed out of the window, cleared my cache and switched system settings to light.

I'll start by saying that I have been using app dir since beta and never looked back, so with using pages this suggestion may be moot but lets jump in and see if we can't get this sorted.

I took a look at your code and your _app.jsx stuck out to me:

import "@/styles/globals.css";
import React, { useEffect, useState } from "react";
import Layout from "@/components/Layout";
import { ThemeProvider } from "next-themes";

export default function App({ Component, pageProps }) {
  return (
    <ThemeProvider attribute="data-theme" enableSystem={true} defaultTheme="system" disableTransitionOnChange> 
    /* add these props to your provider */
      <Layout>
        <div>
          <Component {...pageProps} />
        </div>
      </Layout>
    </ThemeProvider>
  );
}

I think by increasing specificity you can achieve the desired behaviour without flashing. Without setting a default theme you can almost guarantee the program to flash from my experience.

The disableTransitionOnChange prop is sort of unrelated but should increase the smoothness of the transition.

If that doesn't work you may have to dig deeper and see if something is inadvertently blocking the script, it shouldn't, to my knowledge next-themes runs async to the main process, and your code isn't all too complex or "hacky" but you never know.

Try that and if that doesn't work I have about 4 other ideas I am happy to go through. I really hope this helps though.

p.s. Before scrapping the above idea in the event it isn't working, consider adding a jsx fragment as the highest level wrapper, nesting it outside of the theme provider. See below:

import "@/styles/globals.css";
import React, { useEffect, useState } from "react";
import Layout from "@/components/Layout";
import { ThemeProvider } from "next-themes";

export default function App({ Component, pageProps }) {
  return (
    <>
      <ThemeProvider attribute="data-theme" enableSystem={true} defaultTheme="system" disableTransitionOnChange> 
      /* add these props to your provider */
        <Layout>
          <div>
            <Component {...pageProps} />
          </div>
        </Layout>
      </ThemeProvider>
    </>
  );
}
dapatcho commented 5 months ago

I added your changes but noticed the issue was still presenting itself. The problem is in my globals.css, specifically this line:

https://github.com/dapatcho/no-round-corners/blob/0c282de6116c803e1c76e22f807bf0a073566ab4/src/stylesglobals.css#L34

body { ... transition: background-color 0.3s, color 0.3s; ... }

Given that I imagine the workaround would be to move my transition elsewhere. Does the ThemeProvider component have a similar function built into it?

ethan-web-dev commented 5 months ago

That could very well be the issue. Transitions can be volatile when applied that high up.

If you remove the transition entirely does the flash go away? You may not be able to test this very easily but try it if you can.

If that works let me know, in the meantime I will think of a way to add the transition somewhere else. We can customize the _document file but that is getting into some territory that is probably best to avoid unless we are absolutely out of options.

I am going to read the page router docs in depth as well, we'll fix this before EOD.

Also, I assume the transition is intended for the theme to softly update when toggled by a user, is that correct?

dapatcho commented 5 months ago

Also, I assume the transition is intended for the theme to softly update when toggled by a user, is that correct?

Correct yes, that is the intention behind having the transition there to begin with. I ended up finding a solution _app.jsx

export default function App({ Component, pageProps }) {

  useEffect(() => {
    document.body.setAttribute("data-theme-initialized", true);
  }, []);

  return (
    <>
      <ThemeProvider
        attribute="data-theme"
        enableSystem={true}
        defaultTheme="system"
      >
        {" "}
        <div>
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </div>
      </ThemeProvider>
    </>
  );
}

globals.css

body {
  ...
  transition: none;
}

body[data-theme-initialized] {
  transition: background-color 0.3s, color 0.3s;
}

This sets the default behavior to have no transition, and then transition after opening. I believe this is sufficient and I will close with this comment. Hopefully people who encounter a similar issue find this helpful.

Thank you for your help @ethan-web-dev