After setting up dark mode on Material UI version 5 (MUI 5) with Next.js, and being happy that everything seemed to work fine, I pushed a production build to Vercel.
Upon setting dark mode on the published app, and then reloading the page (which is rendered from the server), I noticed the light theme for a split second before the switch to dark.
This created quite a jarring flash, and I really wasn’t happy with it.
The few posts I found that dealt with this issue did not really deal with MUI. Many suggested using global CSS variables with Emotion, but this seemed overly complicated and not really doable.
I decided to look more deeply into my set up, (which was built using guidance from this post).
As detailed in the post, when you set up MUI 5 with Next.js, you create a _document.js file that uses the Emotion cache. This prints out the essential CSS needed to the rendered file, so the styles are instantly available and not loaded after.
I wondered while looking at my _app.js file, if setting the default theme to dark would instruct whatever builds this CSS string under the hood from Emotion, to use the dark theme styles on render.
const [mode, setMode] = React.useState<PaletteMode>("dark");
This worked, so whichever default theme was set, the theme styles would be selected and printed to the file.
I now just needed a way to check which theme had been selected by the user, before rendering the page.
Cookies
You cannot read from local storage server-side but you can read a cookie. I had to change my implementation and stored the value of the users theme selection in a cookie. I used react-cookie and cookie.
Once the cookie exists you retrieve the value by adding getInitialProps to the App
App.getInitialProps = async ({ ctx }: AppContext) => { let themeSetting; if (ctx.req && ctx.req.headers.cookie) { themeSetting = parseCookies(ctx).cookieColorMode; } return { themeSetting, }; };
and passing the cookie value along with any other initial props.
We pass this value as the default theme to the hook.
const [mode, setMode] = React.useState<PaletteMode>( props.themeSetting || 'light' );
And the page is now rendered with the correct CSS from the user's chosen theme, and no flash.
disclaimer: In my implementation I also check for whether the user has dark mode set as a preference from their system. If so, there will be a one time only flash. I cannot find a way of getting this information server-side, as the info is only retrieved from a media query. I do however set the cookie to this selection after, and personally I can live with this one time only flash.
Here you can see the simple dark mode application.
And here is the repo with the implementation