Next.jsのappディレクトリでMUIとEmotionを使ったSSR設定手順
Next.jsのappディレクトリでMUIとEmotionを使ったSSR設定手順
開発中にNext.jsのappディレクトリで、MUI(Material-UI)とEmotionを使用したサーバーサイドレンダリング(SSR)の設定でつまずいたので、備忘録として手順をまとめておきます。この記事では、必要なファイルのコードを含めて、設定手順を詳しく説明します。
目次
- 必要なパッケージのインストール
- Emotionのキャッシュ設定
- EmotionRegistryコンポーネントの作成
- ThemeRegistryコンポーネントの作成
layout.tsx
の更新- 開発サーバーの起動と確認
- まとめ
- 最終的なファイル構成
必要なパッケージのインストール
まず、プロジェクトに必要なパッケージをインストールします。
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
を更新し、EmotionRegistry
とThemeRegistry
を組み込みます。
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