Next.jsのSSR回避(クライアントサイドでのみ実行)の実装パターン集

JavaScript

Next.jsのページはデフォルトでPre-renderingです。
ですので、なにも意識をしないとWindowの参照をはじめとしたブラウザ上でのみ実行可能な処理をサーバサイドで行ってしまい、サーバエラーを引き起こすことがあります。

今回はNext.jsでSSR回避、つまりクライアントサイドでのみ処理を実行するように制御する方法について紹介します。

検証環境のnextは11.1.12、typescriptは4.4.4を利用しています。

サーバエラーが発生してしまうダメな実装例

以下のコードは『警告ダイアログを表示させる』という実装の誤った例です。

import type { NextPage } from "next";

const Home: NextPage = () => {
  window.alert("Hello");
  return <>Hello</>;
};

export default Home;

上記のコードはレンダリングのタイミングでwindow.alert()を実行しています。クライアントサイドではエラーになりませんが、サーバサイドではサーバエラーとなります。

Next.jsにおけるSSR回避の方法

Next.jsにおけるSSR回避の方法として今回は以下の4つを紹介します。

Next.jsにおけるSSR回避の方法
  • 『typeof window』を利用する
  • 『process.browser』を利用する
  • 副作用を利用する
  • 『ssr: false』オプションをつけたdynamic import(動的インポート)を利用する

『typeof window』を利用する

typeofはオペランド(被演算子、演算対象の変数のこと)の型を文字列として返すJavaScriptの演算子です。

window変数の型定義が取得できなければサーバサイド、取得できればクライアントサイドと判定できます。
typeof windowを利用したサーバサイドとクライアントサイドの識別方法の例は以下の通りです。

import type { NextPage } from "next";

const Home: NextPage = () => {
  if (typeof window !== "undefined") {
    // クライアントサイドでのみ実行されるのでサーバエラーにならない
    window.alert("Hello");
  }
  return <>Hello</>;
};

export default Home;

typeof演算子の詳細解説はTypeScriptにおけるtypeof演算子の使いどころで紹介しています。

『process.browser』を利用する

processとはNode.jsのグローバル変数の1つです。

process.browserがfalseならサーバサイド、trueならクライアントサイドと判定できます。
process.browserを利用したサーバサイドとクライアントサイドの識別方法の例は以下の通りです。

import type { NextPage } from "next";

const Home: NextPage = () => {
  if (process.browser) {
    // クライアントサイドでのみ実行されるのでサーバエラーにならない
    window.alert("Hello");
  }
  return <>Hello</>;
};

export default Home;

注意: 『process.client』は利用できない

他のネットの記事ではprocess.clientを利用した方法についても紹介されています。1

しかしNext.jsにおいてはprocess.clientはサーバサイド・クライアントサイドともにundefinedとなります。
ですので、process.clientを利用したSSR回避はNext.jsではできません。

以下はprocess.clientがサーバサイドでもクライアントサイドでもundefinedになることを示している例です。

import type { NextPage } from "next";

const Home: NextPage = () => {
  if (process.browser) {
    // クライアントサイド
    console.log((process as any).client); // undefined
  } else {
    // サーバサイド
    console.log((process as any).client); // undefined
  }
  return <>Hello</>;
};

export default Home;

副作用を利用する

副作用とは『JSXあるいはTSXからUIを構築する処理』以外に関する処理のことをいいます。

副作用はマウント後に実行されるため、Pre-renderingのページであってもブラウザ上でのみ実行可能な機能を利用できます。
具体例は以下の通りです。

import type { NextPage } from "next";
import { useEffect } from "react";

const Home: NextPage = () => {
  useEffect(() => {
    window.alert("Hello");
  }, []);
  return <>Hello</>;
};

export default Home;

【Next.js】SSR/SGでブラウザ機能(Cookieなど)を活用する際の注意点の記事ではwindow変数の参照以外の例についても紹介しています。

『ssr: false』オプションをつけたdynamic import(動的インポート)を利用する

dynamic import(動的インポート)とは動的にimportを実行するための機能です。ES2020でJavaScriptの仕様として導入されました。dynamic importを利用すると非同期でモジュールを読み込めます。

Next.jsはdynamic importをサポートしており、next/dynamicで利用できます。

ssr: falseというオプションをつけてnext/dynamicを利用するとSSR回避ができます。具体例は以下の通りです。

import type { NextPage } from "next";
import dynamic from "next/dynamic";

// Alertコンポーネントはクライアントサイドでのみレンダリングされる
const Alert = dynamic(() => import("../components/Alert"), { ssr: false });
const Home: NextPage = () => {
  return (
    <>
      Hello
      <Alert />
    </>
  );
};

export default Home;

dynamic importの詳細解説は【Next.js】dynamic import(動的インポート)の概要と使いどころ
で紹介しています。

さいごに

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

参考記事