I have been switching my React + material-ui SPA to a Next.js statically rendered site (with next export). I have followed the steps shown on the material-ui example with next.js and everything works fine on non-mobile screen widths (> 960), but the content is shown unstyled in the initial render if the screen width on initial render is at or below the mobile breakpoint. Subsequently navigating to any page on the client renders pages correctly, even when navigating back to the original offending page which was broken on initial render, again this is only on mobile screen widths.
In my code there is a lot of this:
...
const windowWidth = useWindowWidth();
const isMobile = windowWidth < 960;
return (
// markup
{ isMobile ? (...) : (...) }
// more markup
);
...
Where useWindowWidth.js
does this:
function useWindowWidth() {
const isClient = typeof window === "object";
const [width, setWidth] = useState(isClient ? window.innerWidth : 1000); // this will be different between server and client
useEffect(() => {
setWidth(window.innerWidth);
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return width;
}
Any page that has this will show this warning in the console when the initial render is done within the bounds of a mobile screen width:
Warning: Expected server HTML to contain a matching <div> in <div> // or something similar depending on what was conditionally rendered with isMobile
Only these pages have this css styling issue. It seems that when rendering these pages within that screenwidth when there is conditional rendering creates styles with a different name, so instead of the makeStyles-button-96
class the element calls for it will only have makeStyles-button-97
therefore leaving the element unstyled.
I have been through the material-ui issues and the docs, and made sure my project reasonably mirrors the examples. Including the _document.js
and _app.js
files. How do I remedy this?
PS:
There was something I recall reading on my search which stated that React expects server and client rendered output to match but if there is no way around it there is some way to signify this in the code. I am not sure if this only silences the warning or if it prevents the class renaming altogether. Can someone please shed some light on this? I can't seem to find where I read that...
Problem Identified:
To be clear, the window width difference between the server and client, is the offender here. In the useWindowWidth
hook shown above, setting the default to below the 960 mobile threshold, like this:
const isClient = typeof window === "object";
const [width, setWidth] = useState(isClient ? window.innerWidth : 900); // change the default to 900 if not on client, so below the mobile threshold
Makes the inverse of my problem happen. So initial load on a mobile screenwidth is fine but a larger screen width breaks the css with mismatched class names. Is there a recommended method to conditionally render depending on screen width that would somehow keep the output the same?
UPDATE:
While I have found a fix, as stated in my own answer below, I am not satisfied with it and would like to better understand what is happening here so I can address this at build time as opposed to this solution which patches the issue as opposed to preventing it. At this point any answer which just points me in the right direction will be accepted.