What is this post about?
This post will help you to implement server side rendering(SSR) for Material UI's media queries if you are using NextJs with MUI(formerly Material-UI). If you implement useMediaQuery using SSR you will avoid the flash of desktop view in the mobile width devices during initial second of the render when you load a page. This post will explain each step to you to make it much more clear.
Why do you see the flash of desktop view during initial render in mobile ?
Since useMediaQuery Defaults to false. In order to perform the server-side rendering reconciliation, it needs to render twice. A first time with nothing and a second time with the children. This double pass rendering cycle comes with a drawback. If your code contains lines something like below then majorly you will see the flash
const xs = useMediaQuery(theme.breakpoints.down('sm'))
...
return(
{xs ?
<p>Small device</p>
:
<p>Regular size device</p>
}
)
More context of flash
If you load the page in mobile, initial second during render will return "Regular size device" and then to "small device" but the case it should render is "Small device" during initial render itself.
This happens due to not using correct method of useMediaQuery during ssR
Here is the useMediaquery mui documentation but its really confusing how to implement in your code, so below are the steps
Step 1: Get device type from server side App.getInitialProps
- I assume your _app.js looks like below,
// _app.js
import NextApp from 'next/app';
import { createTheme } from '@mui/material';
const App = ({ Component, pageProps }) => {
const theme = createTheme({
// your MUI theme configuration goes here
components: {
//your default component styling goes here
}
});
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
);
};
App.getInitialProps = async (context) => {
return {
...NextApp.getInitialProps(context),
};
};
export default App;
- Edit App.getInitialProps to get deviceType, for this you require ua-parser-js. install this package and Parse the user agent string of the client to extract device type
App.getInitialProps = async (context) => {
let deviceType;
if (context.ctx.req) {
deviceType = parser(context.ctx.req.headers['user-agent']).device.type || 'desktop';
}
return {
...NextApp.getInitialProps(context),
deviceType,
};
};
Step 2: Provide an implementation of ssr matchMedia
- Pass deviceType as prop and the following code in the function
- The recommended way for emulating match media is Using css-mediaquery
const App = ({ Component, pageProps, deviceType }) => {
const ssrMatchMedia = (query) => ({
matches: mediaQuery.match(query, {
// The estimated CSS width of the browser.
width: deviceType === 'mobile' ? '0px' : '1024px',
}),
});
........
Step 3: Override MuiUseMediaQuery default props in theme
- The useMediaQuery is set to false as default. In order to detect the device width in server side you need to change the default props as the ssrMatchMedia defined above.So Add a following lines of code
const theme = createTheme({
// your MUI theme configuration goes here
components: {
MuiUseMediaQuery: {
defaultProps: {
ssrMatchMedia,
},
},
}
- This will set the deviceType during server side rendering and apply the mediaQuery before loading the page.
so final code looks something like below and you can use useMediaQuery in any component to avoid the flash
// _app.js
import NextApp from 'next/app';
import parser from 'ua-parser-js';
import mediaQuery from 'css-mediaquery';
import { createTheme } from '@mui/material';
const App = ({ Component, pageProps, deviceType }) => {
const ssrMatchMedia = (query) => ({
matches: mediaQuery.match(query, {
// The estimated CSS width of the browser.
width: deviceType === 'mobile' ? '0px' : '1024px',
}),
});
const theme = createTheme({
// your MUI theme configuration goes here
components: {
MuiUseMediaQuery: {
defaultProps: {
ssrMatchMedia,
},
},
}
});
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
);
};
App.getInitialProps = async (context) => {
let deviceType;
if (context.ctx.req) {
deviceType = parser(context.ctx.req.headers['user-agent']).device.type || 'desktop';
}
return {
...NextApp.getInitialProps(context),
deviceType,
};
};
export default App;
Conclusion
If you follow the above steps you will avoid the flash and the case explained in context of flash works smooth.
That's it guys thank you :)
Top comments (3)
I had read the MUI documentation and could not understand how to actually implement it.
The article you posted was very helpful.
Thank you very much.
How to solve this in NextJS new
/app
directoryHello @rakshitnayak
Thanks for providing clear understanding of MUI useMediaQuery buddy
But unfortunately I've tried this process on my next app but it didn't work, the flash is still coming here