Ryo.gift | Web developer blog

Web開発者のブログです。

Next.js + Material-UIでリロードした時のエラーについて

目次

  1. 初めに
  2. 原因
  3. 対応方法
  4. 変更前
  5. 変更後
  6. 追記

1. 初めに

Next.js + Material-UI(styled-components)を使ってリロードしたときに、以下のエラーが発生してスタイルが崩れました。

Warning: Prop `className` did not match.

2. 原因

サーバーサイドで新しく生成されたCSSとクラス名が、クライアントサイドで指定しているCSSとクラス名とで異なっているため、スタイルが適用されずにエラーが発生します。

3. 対応方法

対応方法はMaterial-UIのサイトに記載されています。

material-ui.com

TypeScriptを利用しているときに遭遇した事象のため、参考までにtypescriptでの修正例を以下に記載します。

4. 変更前
// pages/_app.tsx
import '../styles/globals.css';
import { AppProps } from 'next/app';

const App = ({ Component, pageProps }: AppProps) => (
  <Component {...pageProps} />
);

export default App;

5. 変更後
// pages/_app.tsx
import React from 'react';
import '../styles/globals.css';
import { AppProps } from 'next/app';

const App = ({ Component, pageProps }: AppProps) => {
  // 以下を追加する
  React.useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentElement?.removeChild(jssStyles);
    }
  }, []);

  return (
    <Component {...pageProps} />
  )
};

export default App;

6. 追記

初回レンダリング後のリロードでは上記の対応のみでもエラーが解消されましたが、コードの修正を行って、ホットリロードした後にリロードすると再びエラーになります。解決するためには、以下のファイルを追加する必要があります。(_document.tsxは出力されるHTMLファイルの構成を変更できるそうです。)

// pages/_document.tsx
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import {ServerStyleSheets} from "@material-ui/styles";

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

MyDocument.getInitialProps = async (ctx) => {

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

export default MyDocument;

以下のコードを参考にしています。 github.com

Material-UIはSSR用に作成されているツールではないため、追加の設定が必要みたいです。