Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

Next.js + Chakra UI で初期描画からダークモードを反映させたい

問題: なにも設定しないと初期描画のときにカラーモードが反映されない

demo

このように、Chakra UI でなにも設定しないとリロード直後に一瞬ライトモードで表示されます。この記事ではこの問題を解消する方法を紹介します。

tl;dr

ColorModeScript を追加する + initialColorMode をセットする

'use client';
export const ChakraProviderClient = ({
  children,
  initialColorMode,
}: PropsWithChildren<{
  initialColorMode: 'light' | 'dark';
}>) => (
  <ChakraProvider
    colorModeManager={cookieStorageManager}
    theme={extendTheme({ config: { initialColorMode } })}
  >
    <ColorModeScript type="cookie" />
    {children}
  </ChakraProvider>
);

対処1: ColorModeScript を追加する

demo

ColorModeScript をセットすると、最初から背景色や文字色が設定されるようになります。 (コンポーネントの色は一瞬ライトモードで表示されていますが、これは後述の initialColorMode で直ります)

ColorModeScript はこのように使います。

<ColorModeScript type="cookie" />

<ColorModeScript type="cookie" /> はページ読み込み時に cookie を見て body に light/dark クラスを追加してくれます(ColorModeScript の実装)。これによって背景色などがうまく機能するようになります。

対処2: initialColorMode をセットする

initialColorMode をセットすると、リロード時にもボタンの色が設定したカラーモードで表示されるようになります。(ColorModeScript との効果を比較するために、動画ではColorModeScript は抜いています)

demo

initialColorMode をセットしないと、サーバーレンダリング時点では設定で決まった色でレンダリングしてしまいます。そのため、設定したカラーモードで描画するために intialColorMode をセットするようにします。

<ChakraProvider
  colorModeManager={cookieStorageManager}
  theme={extendTheme({ config: { initialColorMode } })}
>
  {children}
</ChakraProvider>

intialColorMode はサーバーサイドで cookies().get('chakra-ui-color-mode')?.value で取得できます。あとはこれを ChakraProvider に渡せば完了…なのですが、ここで一つ注意すべき点があります。それは、Client component 内でないと theme={extendTheme({ config: { initialColorMode } })} を直接渡せないということです。これは、extendTheme で返されるオブジェクトのプロパティに関数が混ざっていて「クライアントコンポーネントに直接関数を渡せない」という制限に引っかかるのが原因です。

これに引っかからないように、クライアントコンポーネントの中で ChakraProvider に theme を渡すようにしてください。

完成形

以上をまとめるとこのようになります。

// ChakraProviderClient.tsx
'use client';
export const ChakraProviderClient = ({
  children,
  initialColorMode,
}: PropsWithChildren<{
  initialColorMode: 'light' | 'dark';
}>) => (
  <ChakraProvider
    colorModeManager={cookieStorageManager}
    theme={extendTheme({ config: { initialColorMode } })}
  >
    <ColorModeScript type="cookie" />
    {children}
  </ChakraProvider>
);
// layout.tsx
import { cookies } from 'next/headers';
import { ChakraProviderClient } from './ChakraProviderClient';

export default function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang='en'>
      <body>
        <ChakraProviderClient initialColorMode={cookies().get('chakra-ui-color-mode')?.value}>
          {children}
        </ChakraProviderClient>
      </body>
    </html>
  );
}

demo

一応動画も撮りましたが、表示が一切変わらないのでわかりづらい…