Material UI style Disappeare on Page Refresh

Viewed 860

I am converting my React app into Next app to take advantage of SSR. I am using Material ui 4 for styling.

I have implemented _app.js and _document.js file as per the Material ui documentation but the problem is when the page is loaded for the first time, material ui styles are not being applied but when I make some changes in my components then only all the styles appear.

I am posting this question only after referring this, this answers and other resources on internet

_app.js

import '@assets/fonts/global.css';
import React, {useState, useEffect} from "react";
import Layout from "@layout/Layout";
import Footer from "@layout/Footer/Footer";
import Header from "@layout/Header/Header";
//import Sidebar from "@layout/Sidebar/Sidebar";

//Material UI
import CssBaseline from "@material-ui/core/CssBaseline";
import theme from "@helper/theme/theme";
import {ThemeProvider as MuiThemeProvider} from "@material-ui/styles";

//Redux
import {wrapper} from "@store/store";


function MyApp({Component, pageProps}) {

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

    return (

        <>
            <MuiThemeProvider theme={theme}>
                <CssBaseline/>
                <Header/>
                <Layout>
                    <Component {...pageProps} />
                </Layout>
                <Footer/>
            </MuiThemeProvider>
        </>
    );
}

export default wrapper.withRedux(MyApp);

_document.js

import React from 'react';
import Document, {Html, Head, Main, NextScript} from 'next/document';
import {ServerStyleSheets} from '@material-ui/core/styles';
import theme from "@helper/theme/theme";

export default class MyDocument extends Document {

    render() {

        return (
            <Html lang="en">
                <Head>
                    <meta name="theme-color" content={theme.palette.primary.main} />
                </Head>
                <body>
                    <Main/>
                    <NextScript/>
                </body>
            </Html>
        );
    }
}

MyDocument.getInitialProps = async (ctx) => {

    console.log('DOC Called');

    // 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()],
    };
};

Note: Even after using _app.js and _document.js file as per the Material ui documentation I'm still getting the following warning in console

next-dev.js?3515:25 Warning: Prop `className` did not match.
2 Answers

Perhaps the style mismatch issue? The reactStrictMode is turned on by default if using create-next-app

According to the following link here: https://github.com/mui/material-ui/issues/18018 Material UI v4 is not strict mode compatible so until then you can try disabling reactStrictMode or use Material UI v5.

// in next.config.js
module.exports = {
  reactStrictMode: false
}

Apologies if I misunderstood anything!

add _document.tsx to your project and paste this one. add theme meta tag and also user ServerStyleSheet as well

import createEmotionServer from '@emotion/server/create-instance';
import { ServerStyleSheets } from '@mui/styles';
import Document, { Head, Html, Main, NextScript } from 'next/document';
import * as React from 'react';
import light from 'styles/theme/light';
import createEmotionCache from 'utils/createEmotionCache';

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang='en'>
        <Head>
          <link
            rel='stylesheet'
            href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'
          />
          <meta name='theme-color' content={light.palette.primary.main} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with static-site generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
  // 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

  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;

  // You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
  // However, be aware that it can have global side effects.
  const cache = createEmotionCache();
  const { extractCriticalToChunks } = createEmotionServer(cache);

  /* eslint-disable */
  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App: any) => (props) =>
        sheets.collect(<App emotionCache={cache} {...props} />),
    });
  /* eslint-enable */

  const initialProps = await Document.getInitialProps(ctx);
  // This is important. It prevents emotion to render invalid HTML.
  // See https://github.com/mui-org/material-ui/issues/26561#issuecomment-855286153
  const emotionStyles = extractCriticalToChunks(initialProps.html);
  const emotionStyleTags = emotionStyles.styles.map((style) => (
    <style
      data-emotion={`${style.key} ${style.ids.join(' ')}`}
      key={style.key}
      // eslint-disable-next-line react/no-danger
      dangerouslySetInnerHTML={{ __html: style.css }}
    />
  ));

  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [
      ...React.Children.toArray(initialProps.styles),
      ...emotionStyleTags,
    ],
  };
};

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  // reactStrictMode: true,
  lib: ['es6', 'dom'],
  noImplicitAny: true,
  noImplicitThis: true,
  strictNullChecks: true,
  images: {
    domains: ['rickandmortyapi.com'],
    loader: 'custom',
    path: '/static/images/',
  },
  env: {
    BASE_URL: process.env.NEXT_PUBLIC_BASE_URL,
  },
};

module.exports = nextConfig;

Related