【Next.js】Warningが発生する誤ったCookieの使用例と改善方法

JavaScript

Next.jsのページのレンダリングはデフォルトでPre-rendering、つまりSSR(Server-side Rendering)もしくはSG(Static Generation)です1

サーバサイドではブラウザ上でのみ実行可能な機能は利用できません。具体例は以下の通りです。

ブラウザ上でのみ実行可能な機能の例
  • Windowの参照
  • Cookieの参照
  • localStrageの参照
  • sessionStorageの参照

上記の中でも特にCookieはコンポーネントの制御などでレンダリング時によく利用される機能です。
サーバサイドではCookieを利用できないため、サーバサイドでのレンダリングにCookieによる制御が組み込まれていると意図した結果になりません。

今回はNext.jsにおけるCookieの誤った利用例と改善方法について紹介します。
検証環境のnextは11.1.2を利用しています。

Pre-rendering時にCookieを利用しようとしているダメな例

Cookieの有無によってUIを出し分けるケースを考えます。

以下のコードは『hideMessageという名前のCookieが存在していなかったらメッセージを表示、存在していたら非表示』という実装の誤った例です。Cookieの操作はjs-cookieで行っています。

import type { NextPage } from "next";
import Cookies from "js-cookie";

const Home: NextPage = () => {
  const hideCookie = Cookies.get("hideMessage");
  const message = () => {
    if (hideCookie != null) {
      return null;
    } else {
      return <div>There is no Cookie</div>;
    }
  };

  return <>{message()}</>;
};

export default Home;

サーバサイドではCookieが取得できないため、hideMessageという名前のCookieが存在する場合でもサーバサイドではCookieが存在していないと認識されます。

以下の画像をみて分かる通り、サーバサイドではCookieが存在しないと判定されているためサーバサイドからのHTMLレスポンスには当該要素の文字列There is no Cookieが表示されています。
なお、Pre-rendering後に実行されるクライアントサイドのレンダリングではCookieを操作できるため、ブラウザ上の画面にはメッセージが表示されていません。

サーバサイドとクライアントサイドのレンダリング結果が異なるため、Warning: Did not expect server HTML to contain a <div> in <div>.というReactDOM.hydrate()の警告がReactのアプリケーションログに表示されています。
レンダリング結果の不一致はパフォーマンス低下やデザイン崩れの原因となるため改善が必要です。

なお、Next.jsのSSRの流れについては【図解】Next.jsのSSRが画面反映されるまでの具体的な流れReactDOM.hydrate()については【Next.js】Hydration時にReact.hydrate()による警告が発生するケースとその解決方法で紹介していますので、あわせてご覧になってください。

Cookieによる制御をPre-renderingのページで実装する方法

Cookieの有無によって表示するUIを出し分けるのであれば、例えば副作用を活用すると解決できます。
UIを出し分けるフラグをStateに追加し、副作用でCookieの有無に応じてフラグの真偽値を変更させます。
副作用はマウント後に実行されるので、副作用ではCookieの操作ができます。

具体例は以下の通りです。

import type { NextPage } from "next";
import Cookies from "js-cookie";
import { useEffect, useState } from "react";

const Home: NextPage = () => {
  const [isHide, setIsHide] = useState(false);
  const message = () => {
    if (isHide) {
      return null;
    } else {
      return <div>There is no Cookie</div>;
    }
  };

  useEffect(() => {
    const hideCookie = Cookies.get("hideMessage");

    if (hideCookie != null) {
      setIsHide(true);
    } else {
      setIsHide(false);
    }
  }, []);

  return <>{message()}</>;
};

export default Home;

今回は副作用を利用してCookieを操作する例を紹介しましたが、副作用以外にtypeof windowprocess.browserdynamic importを活用する方法もあります。
詳細解説はNext.jsのSSR回避(クライアントサイドでのみ実行)の実装パターン集 で紹介しています。

参考: リロードと画面遷移でページのUIに違いがあった時は要注意

Next.jsのルータ機能を利用するとCSRによる画面遷移となります。Next.jsのルータ機能にはnext/linknext/routerがあります。
一方、リロードで画面を表示する場合はPre-rendering(SSR/SG)となります。

リロードと画面遷移でページのUI、特に初期表示に違いがあった場合は今回のようにサーバサイドでCookieをはじめとしたブラウザの機能を使おうとしている可能性が高いので覚えておくとよいです。

なお、next/linkの流れの詳細は【Next.js】next/linkの画面遷移(CSR)から画面反映までの流れの図解解説で解説しています。

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!