7

Styles appear for maybe 50ms and disappear in the below code in this SSR app. I'm curious what could be causing that.

// This component is a child of index.tsx in the /pages folder
    <Button
      color="primary"
      variant="outlined"
      size="large"
    >Test Button</Button>

After the styles disappear a plain HTML button is left.

I believe Next.js is causing this. I checked the Next.js file and have added the next/babel loader to .babelrc. Other than that I only saw this other relevant change. This is in /pages/_document.js:


MyDocument.getInitialProps = async ctx => {
  const sheets = new MuiServerStyleSheets();
  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()],
  };
};

Things done to attempt to resolve

  1. Restart server

No change to issue.

  1. Force refresh Chrome 78 (CTRL + F5)

No change.

Theory

I think there is a server side problem. Client and server should be on the same machine, localhost. That would explain the correct initial result (client initial UI) then being overridden by a server update.

Update 1

Forgot to mention that I did update /pages/_app.js too. Here's the updated portion:

class MyApp extends App {
  componentDidMount() {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles && "parentElement" in jssStyles) {
      (jssStyles.parentElement as HTMLElement).removeChild(jssStyles);
    }
  }
Sean D
  • 3,810
  • 11
  • 45
  • 90

3 Answers3

9

The root cause of this error for me, was that the classes generated during server side rendering of the document, don't match the styles that are generated after hydration.

One way to fix this, is to force a rerender after hidration.

One way to do this, is to update the key prop on your component.

// inside your component
const [key, setKey] = React.useState(0);

React.useEffect(() => {
  setKey(1);
}, []);

return (<MyComponent key={}key />)

My full _app.tsx file:

import React from 'react';
import {
  ThemeProvider,
  createGenerateClassName,
  StylesProvider
} from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';

import { darkTheme } from '../theme';

const generateClassName = createGenerateClassName({
  productionPrefix: 'myclasses-'
});

export default function MyApp(props) {
  const { Component, pageProps } = props;

  const [key, setKey] = React.useState(0);

  React.useEffect(() => {
    setKey(1);
  }, []);

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

  return (
    <StylesProvider key={key} generateClassName={generateClassName}>
      <React.Fragment>
        <ThemeProvider theme={darkTheme}>
          <CssBaseline />
          <Component {...pageProps} />
        </ThemeProvider>
      </React.Fragment>
    </StylesProvider>
  );
}
  • +1: The block that finally fixed it for me was: `const generateClassName = createGenerateClassName({ productionPrefix: 'myclasses-' });` which I hadn't seen mentioned in any other examples. Thanks! – James Hooper Aug 27 '20 at 07:57
  • Make sure your NODE_ENV is set to production if you are doing a production build – this will most likely solve your problem without the above. – Denis Pshenov Oct 12 '20 at 16:49
2

TLDR; Make sure to set NODE_ENV to production if you are running a production build, eg: NODE_ENV=production npm start


For me, this was happening only on my machine when creating a production build with npm run build and then running it with npm start.

It was strange why server-rendered responses used development style classes such as makeStyles-root-123 instead of jss123 as explained by https://material-ui.com/styles/advanced/#class-names.

Clearly, some part of the server acted as we were still in a development environment. To fix this I tried starting the production build with NODE_ENV set to "production" and the issue was gone.

Denis Pshenov
  • 11,157
  • 6
  • 42
  • 42
0

Tentatively solved by commenting out the code to remove the server jssStyles

class MyApp extends App {
  // componentDidMount() {
  //   // Remove the server-side injected CSS.
  //   const jssStyles = document.querySelector('#jss-server-side');
  //   if (jssStyles && "parentElement" in jssStyles) {
  //     (jssStyles.parentElement as HTMLElement).removeChild(jssStyles);
  //   }
  // }

If anyone knows why this code is included in the example, please comment. There must be a reason for it. https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_app.js

Sean D
  • 3,810
  • 11
  • 45
  • 90
  • Tried to update to latest version(4.9.0) and faced with this issue too. Removed this block of code but it wasn't helped me. Moved back to old version, which works - "@material-ui/core": "4.8.1", "@material-ui/styles": "4.8.2" – slava_slava Jan 24 '20 at 12:28
  • This is to prevent FOUC (flash of unstyled content) as the styles are added after hydration. If you removed that block (therefore leaving the server side styles) you should notice duplicate styles in the dev console. At least that was my experience – Verbal_Kint Jun 12 '20 at 01:36
  • MUI docs about FOUC and server-side styles: https://mui.com/guides/server-rendering/#heading-mui-on-the-server – David Calhoun Oct 14 '21 at 20:25