👩‍🎤

Next.jsのappディレクトリでMUIとEmotionを使ったSSR設定手順

2024/10/12に公開

Next.jsのappディレクトリでMUIとEmotionを使ったSSR設定手順

開発中にNext.jsのappディレクトリで、MUI(Material-UI)とEmotionを使用したサーバーサイドレンダリング(SSR)の設定でつまずいたので、備忘録として手順をまとめておきます。この記事では、必要なファイルのコードを含めて、設定手順を詳しく説明します。


目次

  1. 必要なパッケージのインストール
  2. Emotionのキャッシュ設定
  3. EmotionRegistryコンポーネントの作成
  4. ThemeRegistryコンポーネントの作成
  5. layout.tsxの更新
  6. 開発サーバーの起動と確認
  7. まとめ
  8. 最終的なファイル構成

必要なパッケージのインストール

まず、プロジェクトに必要なパッケージをインストールします。

npm install @mui/material @emotion/react @emotion/styled @emotion/cache

または、Yarnを使用している場合:

yarn add @mui/material @emotion/react @emotion/styled @emotion/cache

Emotionのキャッシュ設定

SSRでEmotionを正しく機能させるために、Emotionのキャッシュを設定します。

createEmotionCache.tsの作成

utilsディレクトリを作成し、その中にcreateEmotionCache.tsを作成します。

import createCache from '@emotion/cache';

export default function createEmotionCache() {
  return createCache({ key: 'css', prepend: true });
}

EmotionRegistryコンポーネントの作成

Emotionのキャッシュを管理し、SSRでのスタイルフラッシュを防ぐためのコンポーネントを作成します。

EmotionRegistry.tsxの作成

componentsディレクトリを作成し、その中にEmotionRegistry.tsxを作成します。

'use client';

import { CacheProvider } from '@emotion/react';
import { useServerInsertedHTML } from 'next/navigation';
import * as React from 'react';

import createEmotionCache from '@/utils/createEmotionCache';

interface Props {
  children: React.ReactNode;
}

export default function EmotionRegistry({ children }: Props) {
  const [emotionCache] = React.useState(() => createEmotionCache());

  useServerInsertedHTML(() => {
    if (!emotionCache) return null;

    return (
      <style
        data-emotion={`${emotionCache.key} ${Object.keys(emotionCache.inserted).join(' ')}`}
        dangerouslySetInnerHTML={{
          __html: Object.values(emotionCache.inserted).join(' '),
        }}
      />
    );
  });

  return <CacheProvider value={emotionCache}>{children}</CacheProvider>;
}

ポイント:

  • 'use client';を追加して、このコンポーネントがクライアントコンポーネントであることを指定します。
  • useServerInsertedHTMLフックを使用して、サーバーサイドで生成されたスタイルを<head>に挿入します。

ThemeRegistryコンポーネントの作成

MUIのテーマ設定を行い、クライアントコンポーネントとして提供するためのコンポーネントを作成します。

ThemeRegistry.tsxの作成

componentsディレクトリ内にThemeRegistry.tsxを作成します。

'use client';

import CssBaseline from '@mui/material/CssBaseline';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import * as React from 'react';

interface Props {
  children: React.ReactNode;
}

export default function ThemeRegistry({ children }: Props) {
  const theme = createTheme({
    palette: {
      primary: {
        main: '#1976d2',
      },
    },
  });

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      {children}
    </ThemeProvider>
  );
}

ポイント:

  • 'use client';を追加し、クライアントコンポーネントであることを明示します。
  • createThemeの呼び出しをクライアントサイドで行います。

layout.tsxの更新

appディレクトリ内のlayout.tsxを更新し、EmotionRegistryThemeRegistryを組み込みます。

import * as React from 'react';

import EmotionRegistry from '@/components/EmotionRegistry';
import ThemeRegistry from '@/components/ThemeRegistry';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <head>{/* 必要なメタタグやリンクを追加 */}</head>
      <body>
        <EmotionRegistry>
          <ThemeRegistry>{children}</ThemeRegistry>
        </EmotionRegistry>
      </body>
    </html>
  );
}

ポイント:

  • layout.tsxはサーバーコンポーネントのままにします('use client';は追加しない)。
  • クライアント専用の機能は、クライアントコンポーネント内で処理します。

開発サーバーの起動と確認

設定が完了したら、開発サーバーを起動して動作を確認します。

npm run dev

または

yarn dev

ブラウザでhttp://localhost:3000にアクセスし、MUIのコンポーネントが正しく表示されていることを確認します。


まとめ

以上の手順で、Next.jsのappディレクトリでMUIとEmotionを使用したSSRの設定が完了しました。サーバーコンポーネントとクライアントコンポーネントを適切に分離し、クライアント専用の機能はクライアントコンポーネント内で処理することが重要です。


最終的なファイル構成

components/
  ├── EmotionRegistry.tsx
  └── ThemeRegistry.tsx
utils/
  └── createEmotionCache.ts
app/
  └── layout.tsx

各ファイルのコード詳細

components/EmotionRegistry.tsx

'use client';

import { CacheProvider } from '@emotion/react';
import { useServerInsertedHTML } from 'next/navigation';
import * as React from 'react';

import createEmotionCache from '@/utils/createEmotionCache';

interface Props {
  children: React.ReactNode;
}

export default function EmotionRegistry({ children }: Props) {
  const [emotionCache] = React.useState(() => createEmotionCache());

  useServerInsertedHTML(() => {
    if (!emotionCache) return null;

    return (
      <style
        data-emotion={`${emotionCache.key} ${Object.keys(emotionCache.inserted).join(' ')}`}
        dangerouslySetInnerHTML={{
          __html: Object.values(emotionCache.inserted).join(' '),
        }}
      />
    );
  });

  return <CacheProvider value={emotionCache}>{children}</CacheProvider>;
}

components/ThemeRegistry.tsx

'use client';

import CssBaseline from '@mui/material/CssBaseline';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import * as React from 'react';

interface Props {
  children: React.ReactNode;
}

export default function ThemeRegistry({ children }: Props) {
  const theme = createTheme({
    palette: {
      primary: {
        main: '#1976d2',
      },
    },
  });

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      {children}
    </ThemeProvider>
  );
}

utils/createEmotionCache.ts

import createCache from '@emotion/cache';

export default function createEmotionCache() {
  return createCache({ key: 'css', prepend: true });
}

app/layout.tsx

import * as React from 'react';

import EmotionRegistry from '@/components/EmotionRegistry';
import ThemeRegistry from '@/components/ThemeRegistry';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <head>{/* 必要なメタタグやリンクを追加 */}</head>
      <body>
        <EmotionRegistry>
          <ThemeRegistry>{children}</ThemeRegistry>
        </EmotionRegistry>
      </body>
    </html>
  );
}

注意点

  • サーバーコンポーネントとクライアントコンポーネントの分離:サーバーコンポーネント内でクライアント専用のコード(createThemeなど)を直接使用しないようにします。
  • 'use client';の使用:クライアントコンポーネントであることを明示するために、ファイルの先頭に'use client';を追加します。
  • パフォーマンス向上layout.tsxをサーバーコンポーネントのままにすることで、パフォーマンスの最適化が期待できます。

参考


これで設定手順の解説は以上です。
この記事が誰かのプロジェクト開発に役立つことを願っています🙏

Discussion