空雲 Blog

Eye catch
Next.jsのAppRouter/PagesRouter実行中に、Server/Client/Pagesのどのモジュールなのかを識別する

publication: 2023/11/09
update:2024/02/20

厄介なコンポーネントの状態🔗


Next.jsのAppRouterではデフォルトでServerComponentの状態から始まり、"use client"を先頭につけることによって、ClientComponentsとして動作させることができます。また、コンポーネントを記述したファイルに"use xxxxx"を付けなかった場合は、呼び出し元のコンポーネントの状態を継承します。


つまり呼び出されたコンポーネントは、自分がServerComponentかもしれないし、ClientComponentの可能性もあります。


Server/Client両用コンポーネントを作りたい場合、現在の状態によって処理を切り替えたいという時が来るかもしれません。ということで、現状で可能な識別方法のサンプルを紹介します。


  • app/CheckComponent.tsx


1import React from "react";
2
3export const CheckComponent = () => {
4 return (
5 <div>
6 {!React["useState"]
7 ? "Server"
8 : React["createServerContext"] || !React["useOptimistic"]
9 ? "Pages"
10 : "Client"}
11 </div>
12 );
13};
14


  • app/Server.tsx


1"use server";
2import { CheckComponent } from "./CheckComponent";
3
4export const Server = () => {
5 return <CheckComponent />;
6};


  • app/Client.tsx


1"use client";
2import { CheckComponent } from "./CheckComponent";
3
4export const Client = () => {
5 return <CheckComponent />;
6};


  • app/page.tsx


1import { Client } from "./Client";
2import { Server } from "./Server";
3
4const Page = () => {
5 return (
6 <>
7 <div>-- App --</div>
8 <Server />
9 <Client />
10 <div>-- Pages --</div>
11 <iframe src="/pages" />
12 </>
13 );
14};
15
16export default Page;


  • pages/pages.tsx


1import { CheckComponent } from "../app/CheckComponent";
2
3const Page = () => {
4 return <CheckComponent />;
5};
6
7export default Page;


出力結果


一画面に収めたかったので、PagesRouterはiframeで表示しています


{"width":"389px","height":"319px"}


識別方法の解説🔗


Server/ClientでそれぞれCheckComponentを呼び出しています。CheckComponentではReact.useStateが存在するかどうかをチェックしています。ServerComponentsではuseStateがundefinedとなるため、識別が可能となります。


さらにpagesに置かれているかどうかという判定を加えると、少々面倒です。


  • ServerComponentsはuseStateがない
  • ClientComponentsはuseStateがある
  • PagesではuseStateがある
  • ClientComponentsではcreateServerContextが取り除かれる
  • PagesではcreateServerContextは取り除かれない
  • createServerContextはreact@canary以降に存在する
  • useOptimisticはreact@canary以降に存在する
  • AppRouterはreact@canary以降で実行される
  • PagesRouterはreact@18.2.0以前とreact@canaryのどちらかで実行される


という条件を満たすように判定する必要があります。


これはあくまでNext.jsの仕様です。ServerComponentsでuseStateがモジュールバンドラの設定で除去されるのはNext.jsが勝手にやっているだけです。Reactの仕様ではありません。そのため、あらゆるフレームワークで状況に応じて柔軟に対応できる汎用的なコンポーネントを作るのはほぼ不可能です。本来であればReactの基本機能として判別するための機能を用意するべきなのですが、Next.jsで動けば良いというのが垣間見えてフレームワークが一強化したことによる弊害が出てきた状態です。