2

I have a page that shows huge icons and pretty much broken css on page load. Followed official nextjs mui5 example.

    "next": "^12.3.1",
 "react": "^18.2.0",
"@emotion/react": "^11.10.4",
"@emotion/server": "^11.10.0",
"@emotion/styled": "^11.10.4",
"@mui/icons-material": "^5.10.6",
"@mui/lab": "^5.0.0-alpha.102",
"@mui/material": "^5.10.8",
"@mui/styles": "^5.10.8",

Live demo of the issue - https://wedpicker.com/

Below is my code

appjs

import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { CacheProvider } from '@emotion/react';
import CssBaseline from '@mui/material/CssBaseline';
import Layout from '../components/layout';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import LinearProgress from '@mui/material/LinearProgress';
import * as gtag from '../lib/googleAnalytics';
import '../styles/globals.css';
import createEmotionCache from '../components/theme/createEmotionCache';

// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();

export default function NCD(props) {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
  const [isLoading, setLoadingState] = useState(false);

  const router = useRouter();

  useEffect(() => {
    const handleRouteChange = (url) => {
      gtag.pageview(url)
    }
    // Logging to prove _app.js only mounts once,
    // but initializing router events here will also accomplishes
    // goal of setting state on route change
    router.events.on('routeChangeStart', () => {
      setLoadingState(true);
    });

    router.events.on('routeChangeComplete', () => {
      handleRouteChange;
      setLoadingState(false);
    });

    router.events.on('routeChangeError', () => {
      setLoadingState(false);
    });

    if (typeof window == 'undefined') {
      setLoadingState(true);
    }

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

  return (
    <React.Fragment>
      <Head>
        <title>{process.env.SITENAME}</title>
        <meta name="viewport" content="initial-scale=1, width=device-width" />
      </Head>
      <CacheProvider value={emotionCache}>
      {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
      <CssBaseline />
        <Layout>
      {isLoading? (  
        <>
          <div className="pageLoader">
            <LinearProgress />
          </div>
         </>):(
          <>

          <Component {...pageProps} />
        
         </>
        )}
        </Layout>
        </CacheProvider>
    </React.Fragment>
  );
}

NCD.propTypes = {
  Component: PropTypes.elementType.isRequired,
  pageProps: PropTypes.object.isRequired,
};

document.js


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

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang='en'>
        <Head>

          <link rel="shortcut icon" href="/favicon.ico" />
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
          {/* Global Site Tag (gtag.js) - Google Analytics */}
          <script
            async
            src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
          />
          <script
            dangerouslySetInnerHTML={{
              __html: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${GA_TRACKING_ID}', {
              page_path: window.location.pathname,
            });
          `,
            }}
          />

        </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 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);

  const sheets = new ServerStyleSheets()

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) =>
        function EnhanceApp(props) {
          return sheets.collect(<App emotionCache={cache} {...props} />);
        },
    });

  const initialProps = await Document.getInitialProps(ctx);
  // This is important. It prevents Emotion to render invalid HTML.
  // See https://github.com/mui/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,
    emotionStyleTags,
  };
};

Layout.js


import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import {lightTheme, darkTheme} from '../components/theme';
import TopAppBar from './ui/topAppBar';
import { useCookies } from 'react-cookie';
import Copyright from '../components/ui/copyright';
import Box from '@mui/material/Box';

const layout = ({ children }) => {
  const [cookies, setCookie] = useCookies(['darkTheme']);

  return <>
    <StyledEngineProvider injectFirst>
      <ThemeProvider theme={cookies.darkTheme=="true" ? darkTheme : lightTheme}>
        <CssBaseline />
      
        <header>
          <nav>
          <TopAppBar />
          </nav>
        </header>

          <main>{children}</main>
      <Box mt={8} mb={4} >
        <Copyright />
      </Box>
      </ThemeProvider>
    </StyledEngineProvider>
  </>;
};

export default layout;

2 Answers 2

2

I struggled with next-js and mui combination for a while. Here are things you can update:

  • Update nextjs to latest.
  • Follow this example (check again if you have all the code here including createEmotionCache, saw you have it under components but not sure if it is same).
  • Remove <StyledEngineProvider injectFirst>, this was the thing that fixed styles for me.
  • Use Image from next/image instead of <img> element for images.
  • You don't need any of mui imports in Layout.js. Remove StyledEngineProvider, ThemeProvider.
Sign up to request clarification or add additional context in comments.

1 Comment

I was having the same issue when deploying to aws via amplify/serverless. Removing StyledEnginProvider fixed it for me!
0

The simplest way I found to fix this is to add a loader which will look nice while the page loads and also eliminate the flickering. useEffect or useLayoutEffect can be used as shown below to trigger the page.

const layout = ({ children }) => {
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    setLoading(false);
  }, []);
  const toReturn = (<>
    <StyledEngineProvider injectFirst>
      <ThemeProvider >
        <CssBaseline />
      
        <header>
          <nav>
          <TopAppBar />
          </nav>
        </header>
        <Box>
          <main>{children}</main>
        </Box>         
      </ThemeProvider>
    </StyledEngineProvider>
  </>);
  const loader = (
    <>
          <div className="pageLoader">
            <LinearProgress />
          </div>
    </>
  )
  if (!loading) {
    return toReturn;
  }
  else {
    return loader;
  }
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.