NextjsのApp RouterにMaterial UIを統合させる

## はじめに
[Material UI](https://mui.com/material-ui)はReactのUIコンポーネントライブラリの１つです。名のとおりGoogleのMaterial Designを実装するUIコンポーネントを提供しています(提供されるのは最新のMaterial Design 3ではなくMaterial Design 2です)。
その洗礼された豊富なコンポーネントを手軽に、独自のデザインシステムにカスタマイズしながら利用できる優れたライブラリです。
そんなMaterial UIですが、バージョン5.15.0でNext.jsと統合するためのライブラリとして、`@mui/material-nextjs`の提供を開始しました。
この記事では`@mui/material-nextjs`を用いたNext.jsのApp Routerとの統合について解説します。

## 統合
### Nextjsの環境準備
`npx create-next-app`でNextjsの最新のプロジェクトを作成します。作成のための質問には以下のように答えました。装飾はMaterial UIで行いたいのでTailwind CSSをNoに選択しました。
![スクリーンショット 2023-12-17 17.43.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/0aa2ca3e-6dc2-1fbf-d9b5-238f282bf62f.png)

装飾をMaterial UIをベースに行わせるために、CSSやCSS Modulesで書かれている部分を外していきます。CSSファイルは削除して、`tsx`ファイルはそれぞれ以下のように変更します。
```tsx:layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}
```

```tsx:page.tsx
export default function Home() {
  return <></>
}
```


### Material UIを利用可能に
次に、Material UIに関連するライブラリを導入します。
```sh
npm install @mui/material @emotion/react @emotion/styled @mui/material-nextjs @emotion/cache
```
すでにMaterial UIの環境がある場合は下の２つだけで問題ないです。
```sh
npm install @mui/material-nextjs @emotion/cache
```
パッケージ類の準備は完了なので`layout.tsx`と`page.tsx`を触っていきます。
まずは、`layout.tsx`で`body`以下の要素に対するProviderを設置します。
```tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <AppRouterCacheProvider>
          {children}
        </AppRouterCacheProvider>
      </body>
    </html>
  )
}
```
Nextjsのバージョン14以上を利用の方は上記のように、13以上の場合は`'@mui/material-nextjs/v13-appRouter'`から同じようにProviderを取得してください。

その後、Material UIを使ったコードを`page.tsx`で利用可能になります。

```tsx:page.tsx
import { Button, Stack, TextField, Typography } from "@mui/material";

export default function Home() {
  return (
    <Stack height="100lvh" justifyContent="center" alignItems="center" gap="32px">
      <Typography id="login_heading" variant="h1" fontSize="1.5rem">ログインフォーム</Typography>
      <Stack component="form" width={560} gap="24px" aria-labelledby="login_heading">
        <TextField label="メールアドレス" />
        <TextField label="パスワード" />
        <Button variant="contained">ログイン</Button>
      </Stack>
    </Stack>
  );
}
```
出力は以下の通りMaterial UIで装飾されたものです。
![スクリーンショット 2023-12-17 18.17.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/26c3b9d4-71ce-e5a7-b524-fa3516b2d002.png)
これで統合は完了です。

## AppRouterCacheProvider
`@mui/material-nextjs`を利用してApp RouterにMaterial UIを統合するためには`AppRouterCacheProvider`をルートに設置するだけでした。
`AppRouterCacheProvider`はReactによって生成されたスタイルを`@emotion/cache`を利用して収集して、`useServerInsertedHTML`で`head`に追加しています。
Material UIで使われているEmotionについての解説はありませんが、[Nextjsのドキュメント](https://nextjs.org/docs/app/building-your-application/styling/css-in-js#configuring-css-in-js-in-app)では`styled-jsx`や`styled-component`などのCSS in JSについて`useServerInsertedHTML`を用いて同様の処理が書かれています。

### オプション
`AppRouterCacheProvider`には`children`の他に２つのpropsをとります。

1つ目は`emotion/cache`の`creaeCache`に渡すための設定に加えて`enableCssLayer`をキーとした真偽値を任意のオブジェクトとして`options`という名前で持ちます。

`creaeCache`に渡すための設定は`CSP(Content Security Policy)`が有効な時に使われる`nonce`属性や、前処理中にStylisによって実行されるプラグインを渡す`stylisPlugins`、クラス名のプレフィックスとして利用される`key`、スタイルタグを挿入するDOMを指定する`container`などを渡します([参考](https://emotion.sh/docs/@emotion/cache))。

`enableCssLayer`は真偽値を値として取り、`true`にした場合は生成したスタイルを`@layer mui`で囲むようになります。これによってEmotionを利用せずに、Tailwind CSSやCSS Modulesなどを用いてMaterial UIのスタイリングを上書きすることが可能となります。`@layer`は詳細度をより細かく制御するためのアットルールです。

2つ目は`CacheProvider`です。　デフォルトでは`emotion/react`の`CacheProvider`が利用されていますが、他のEmotionに対するキャッシュプロバイダを渡せます。

## さいごに
NextjsのApp RouterにMaterial UIを統合させる方法を紹介しました。Material UIはこれまでApp Routerで利用するためには[例](https://github.com/mui/material-ui/blob/e7b9793afd312c338affbb58c49cd8a28bad8110/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/ThemeRegistry.tsx)にあるように複雑なコンポーネントを手前で組み込んで利用する必要がありましたが、`@mui/material-nextjs`の登場で解決しました。

未だ、ほとんどのコンポーネントは`'use client'`が貼られており、クライアントコンポーネントとして扱うことになってしまいますが、これまでより少し使いやすくなったのではないかなと思います。