Integrating Material UI with NextJs

Howard Lee
3 min readAug 2, 2021

Part of my current responsibilities at work is to unify the differing styles found across our web platform. Right now, there is a mix of CSS modules, Tailwind, and Material UI styles in conjunction with NextJs, the framework we’re using.

However, that led to problems, as the styles wouldn’t render upon reloading, or apply inconsistently.

After investigation, we discovered that Next.Js, the framework we use, and the Material UI styles were improperly integrated. Some of the errors we encountered were:

  • Navigational bar not adhering to the layout across the page
  • CSS works only on first load then is missing
  • CSS only applying after changes are made in VSCode
  • CSS not updating

This is generally an example of FOUC.

FIX:

The reason was that we had not integrated Material UI properly with NextJs.

The solution: remove the server-side injected CSS and fix the resolution to inject the Material UI styles.

To begin, we will assume that NextJS is up and running.

  1. Install Material UI via NPM
npm install @material-ui/core

2. create or modify _document.js in /pages directory

Notes:

import React from 'react';import Document, { Html, Head, Main, NextScript, DocumentContext} from 'next/document';import { ServerStyleSheets } from '@material-ui/core/styles';export default class MyDocument extends Document {render() {return (<Html lang="en"><Head></Head><body><Main /><NextScript /></body></Html>);}}// `getInitialProps` belongs to `_document` (instead of `_app`),// it's compatible with server-side generation (SSG).MyDocument.getInitialProps = async (ctx :DocumentContext) => {// Resolution order//// On the server:// 1. app.getInitialProps// 2. page.getInitialProps// 3. document.getInitialProps// 4. app.render// 5. page.render// 6. document.render//// On the server with error:// 1. document.getInitialProps// 2. app.render// 3. page.render// 4. document.render//// On the client// 1. app.getInitialProps// 2. page.getInitialProps// 3. app.render// 4. page.render// Render app and page and get the context of the page with collected side effects.const sheets = new ServerStyleSheets();const originalRenderPage = ctx.renderPage;ctx.renderPage = () =>originalRenderPage({enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),});const initialProps = await Document.getInitialProps(ctx);return {...initialProps,// Styles fragment is rendered after the app and page rendering finish.styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],};};
  • _document.js is processed on server-side only
  • You need to inject an instance of the stylesheet into the initial props and context through getInitialProps. This will inject the CSS styles into the document as a string

3. Then we need to remove the server-generated CSS from client-side app through useEffect so the client-side styles can take over

React.useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement!.removeChild(jssStyles);
}
}, []);

There you go! The flickering issue should be gone and the styles should apply properly now.

You can view my repo here or you can use the Next example. I don’t use Material UI themes in general, so unlike the Next example, mine will not show that and is much more simplified.

--

--

Howard Lee

“Your sacred space is where you can find yourself over and over again.” — Joseph Campbell