DEV Community

Cover image for How to implement Server Side Rendering for Material UI's media queries in NextJs to avoid flash?
Rakshit Nayak
Rakshit Nayak

Posted on

How to implement Server Side Rendering for Material UI's media queries in NextJs to avoid flash?

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>
  }
)
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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,
  };
};
Enter fullscreen mode Exit fullscreen mode

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',
    }),
  });
 ........
Enter fullscreen mode Exit fullscreen mode

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,
        },
      },
    }
Enter fullscreen mode Exit fullscreen mode
  • 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;

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
snaka profile image
Shinji NAKAMATSU

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.

Collapse
 
thisismustakim profile image
Mustakim Khondaker

How to solve this in NextJS new /app directory

Collapse
 
pmehul profile image
Mehul Parmar

Hello @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