5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js App Router + MUIのHydrationエラーの解消

Last updated at Posted at 2025-08-27

はじめに

Next.js/React固有のエラーのひとつとして以下のようなHydrationエラーがあります。

Hydration failed because the server rendered HTML didn't match the client.
As a result, this tree will be regenerated on the client.
This can happen if a SSR-ed Client Component is used.

error.png

このエラーは、サーバーサイドとクライアントサイドでレンダリングされるHTMLの構造が一致しない場合に発生します。

今回、このエラーを解消していく中でHydrationについて理解を深めることができたので、一連の流れをまとめようと思います。

最初に遭遇したHydrationエラー

APIから取得したデータに基づいて画面のコンテンツを動的に表示する機能を実装しているときにHydrationエラーが発生しました。
これは、データを取得するまでの間にサーバーサイドとクライアントサイドでレンダリングされる内容にズレが生じていたことが原因だと考えられます。

そこで、値を取得するまでの間にMUIのCircularProgressを使ってローディングUIを表示することで回避しようと以下のように実装しました。

'use client';

import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';

export default function DataDisplayPage() {
  const { data } = useDataFetching();

  // データがまだ取得できていない場合はローディング表示
  if (!data) {
    return (
      <Box>
         <CircularProgress />
      </Box>
    );
  }
  
  // データが正常に取得できた場合はメインコンテンツを表示
  return (
    <Box>
      {/* メインコンテンツ */}
    </Box>
  );
}

ひとまずこれでエラーは出なくなりました!

問題の再発:見た目を改善しようとして...

しかし、このままだとローディングUIが画面左上に表示されてしまい、やや見た目がよくない状態でした。

circular.png

そこで画面中央にローディングUIが表示されるように以下のように修正を加えました。

if (!data) {
  return (
    <Box
      sx={{
        position: 'fixed',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
      }}
    >
      <CircularProgress />
    </Box>
  );
}

circular_center.png

すると...Hydrationエラーが復活!😱

UIのスタイルを変更しただけなのに...?

原因を調査

2025/09/05 追記

記事公開後にコメントで指摘をいただき、根本的な原因はMUIのセットアップの問題であることが分かりました。

当初の内容は参考として残していますが、根本原因ではありませんでした。

【参考】当初の原因調査と解決策

原因について調べてみたところ、以下のようなことが分かりました。

  • スタイルを指定しない場合はサーバーサイドもクライアントサイドも同じHTML/CSSになるためHydrationエラーは発生しない
  • position: fixedtransformなどを使うと、親要素やレイアウトの影響を受けやすくなるため、サーバーサイドとクライアントサイドで異なるDOM構造やスタイルが生成されることがある
  • サーバーサイドではウィンドウサイズなどの情報がないため、クライアントサイドで再計算されるとズレが生じる場合もある

つまり、クライアント固有のスタイル指定によってサーバーサイドとクライアントサイドで描画結果にズレが生じていたというのが今回の原因のようでした。

UIのスタイルを変えただけでエラーが発生するという経験をしたことがなかったので少し驚きましたが、仕組みを理解すれば納得できました。

解決策(訂正前)

対応方法はいくつかあると思いますが、今回は以下のようにスタイル指定なしのLinearProgressを配置することで、画面上部にライン状のローディング表示をして対応しました。
これによって自然なローディング表示をしつつHydrationエラーを解消できました。

if (!data) {
  return (
    <Box>
      <LinearProgress />
    </Box>
  );
}

liner.png

解決策(訂正版)

MUIをNext.js App Routerで使用する場合、Next.js公式ドキュメントにある通り以下の手順で正しく設定する必要があります。

1. Next.js用のMUIパッケージをインストール

npm install @mui/material-nextjs

2. layout.tsxでAppRouterCacheProviderを設定

import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
// or `v1X-appRouter` if you are using Next.js v1X

export default function RootLayout(props) {
  return (
    <html lang="en">
      <body>
        <AppRouterCacheProvider>
          {props.children}
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}

なぜこの設定が必要なのか

MUIは内部でemotion(CSS-in-JS)を使用してスタイルを生成しています。サーバーサイドレンダリング時には、emotionのキャッシュを適切に管理する必要があります。

AppRouterCacheProviderは以下の役割を果たします:

  • サーバーサイドでのスタイル生成とキャッシュ管理
  • クライアントサイドでのHydration時のスタイル整合性の確保
  • emotionのサーバー/クライアント間でのスタイルキャッシュの同期

この設定により、ローディングUIのスタイル指定に関係なく、Hydrationエラーが根本的に解消されます

参考

Next.jsのlayout.tsx でMUIを使うとHydrationエラーになる?!

まとめ

  • MUIをNext.jsで使用する際は、適切なセットアップをする必要がある
  • CSS-in-JSライブラリを使用する場合、サーバーサイドレンダリングとの整合性に注意

コメントでのご指摘ありがとうございました。
同じような問題に遭遇した方の参考になれば幸いです。

参考リンク

5
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?