fouc problem with nextjs and material ui loads a raw html first

Viewed 28

i resently migrated to nextjs. im using material v4 and next version 10.2 ive tried the official example in material ui site to prevent loading the raw html first by adding the _document.js file https://github.com/mui/material-ui/tree/v4.x/examples/nextjs so far my problem still exists. this is my _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 '../assets/theme';

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

MyDocument.getInitialProps = async ctx => {
  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: [
      ...React.Children.toArray(initialProps.styles),
      sheets.getStyleElement(),
    ],
  };
};
export default MyDocument;

this is my _app.js

import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '@material-ui/core/styles';

import theme from '../assets/theme';
import withMui from '../components/withMui';
import store from '../utils/store';
import { makeStyles } from '@material-ui/core/styles';
import { Provider } from 'react-redux';
import Ribbon from '../components/RibbonComponent';
const useStyles = makeStyles(() => ({
  '@global': {
    '*::-webkit-scrollbar': {
      width: '0.4em',
    },
    '*::-webkit-scrollbar-track': {
      '-webkit-box-shadow': 'inset 0 0 6px rgba(0,0,0,0.00)',
    },
    '*::-webkit-scrollbar-thumb': {
      backgroundColor: 'rgba(0,0,0,.1)',
    },
  },
  appWrapper: {
    width: '100%',
    height: '100%',
    display: 'flex',
    flexDirection: 'column',
  },
  appContent: { flex: 1 },
}));
function MyApp(props) {
  const { Component, pageProps } = props;

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

  return (
    <React.Fragment>
      <Head>
        <title>Tisitano</title>
        <link
          rel="stylesheet"
          href="https://fonts.googleapis.com/css?family=Lato:300,400,500,700&display=swap"
        />
        <meta
          name="viewport"
          content="minimum-scale=1, initial-scale=1, width=device-width"
        />
        <script src="https://www.gstatic.com/firebasejs/5.5.3/firebase-app.js"></script>
        <script src="https://www.gstatic.com/firebasejs/5.5.3/firebase-firestore.js"></script>
        <script
          strategy="afterInteractive"
          dangerouslySetInnerHTML={{
            __html: `myfacebookKeyAndrelatedHtml`,
          }}
        />
      </Head>
      <ThemeProvider theme={theme}>
        <Provider store={store}>
          <Ribbon />
          <div className={classes.appWrapper}>
            <div className={classes.appContent}>
              <Component {...pageProps} />
            </div>
          </div>
        </Provider>
      </ThemeProvider>
    </React.Fragment>
  );
}

MyApp.propTypes = {
  Component: PropTypes.elementType.isRequired,
  pageProps: PropTypes.object.isRequired,
};
export default withMui(MyApp);

and my index.js

import React from 'react';

import Home from './home';

import MainFooter from '../components/home/mainFooter';

function Index() {
  return (
    <>
      <Home /> <MainFooter />
    </>
  );
}
export default Index;

my withMui.js file

import React, { Component } from 'react';

import { ThemeProvider } from '@material-ui/core/styles';
import { createTheme } from '@material-ui/core/styles';
import myTheme from '../assets/theme';

const muiTheme = myTheme;

export default function outputComponent(NextPage) {
  class outputComponent extends Component {
    constructor(props) {
      super(props);
      this.state = {
        nav: '',
      };
    }
    componentDidMount() {
      this.setState(state => {
        state.nav = navigator.userAgent;
        return state;
      });
    }
    static async getInitialProps(ctx) {
      const { req } = ctx;

      const userAgent = req ? req.headers['user-agent'] : this?.state?.nav;
      let pageProps = {};
      if (NextPage.getInitialProps) {
        pageProps = await NextPage.getInitialProps(ctx);
      }

      return {
        ...pageProps,
        userAgent,
      };
    }
    render() {
      let userAgent = this.props.userAgent;
      return (
        <ThemeProvider theme={createTheme({ userAgent, ...muiTheme })}>
          <NextPage {...this.props} />
        </ThemeProvider>
      );
    }
  }

  return outputComponent;
}

here is what i tried so far:

1.i tried removing withMui file and still i saw the raw html. by the way without withmui file all my styles break.

2.i tried mixing the _document.js with my withMui file to have them both on serverside it didnt work.

3.i also made sure that server restarts.

4.i even saw on a github link to add a dummy script(which was not convincing but i tried it) <script>0</script> but this didnt work either.

5.i added getInitialProps to my _app.js.

  1. i tried removing the ribbon and wrapper divisions from _app.js and adding them to index file it didnt work.

i can not update my material or next version due to dependency conflict for now so if anyone knows any workaround on these versions please help me.

1 Answers

I was also facing this problem but solved it doing this. Replace that code that you have wrote in _document.js with this -

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

ctx.renderPage = () =>
    originalRenderPage({
        enhanceApp: (App) =>
            function EnhanceApp(props) {
                return <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-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,
    emotionStyleTags,
  };
};

And in your _app.js add this

const clientSideEmotionCache = createEmotionCache();
const { Component, emotionCache = clientSideEmotionCache,pageProps} = props;


 return(
<CacheProvider value={emotionCache}>

// your return code of _app.js
</CacheProvider>
)
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
emotionCache: PropTypes.object,
pageProps: PropTypes.object.isRequired,
};

for more information here is the link - https://mui.com/material-ui/guides/server-rendering/

also check out this link - FOUC when using @material-ui/core with NextJS/React

Related