Next.jsのAppRouter/PagesRouter実行中に、Server/Client/Pagesのどのモジュールなのかを識別する
厄介なコンポーネントの状態
Next.jsのAppRouterではデフォルトでServerComponentの状態から始まり、"use client"を先頭につけることによって、ClientComponentsとして動作させることができます。また、コンポーネントを記述したファイルに"use xxxxx"を付けなかった場合は、呼び出し元のコンポーネントの状態を継承します。
つまり呼び出されたコンポーネントは、自分がServerComponentかもしれないし、ClientComponentの可能性もあります。
Server/Client両用コンポーネントを作りたい場合、現在の状態によって処理を切り替えたいという時が来るかもしれません。ということで、現状で可能な識別方法のサンプルを紹介します。
app/CheckComponent.tsx
1import React from "react";23export 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";34export const Server = () => {5 return <CheckComponent />;6};
app/Client.tsx
1"use client";2import { CheckComponent } from "./CheckComponent";34export const Client = () => {5 return <CheckComponent />;6};
app/page.tsx
1import { Client } from "./Client";2import { Server } from "./Server";34const Page = () => {5 return (6 <>7 <div>-- App --</div>8 <Server />9 <Client />10 <div>-- Pages --</div>11 <iframe src="/pages" />12 </>13 );14};1516export default Page;
pages/pages.tsx
1import { CheckComponent } from "../app/CheckComponent";23const Page = () => {4 return <CheckComponent />;5};67export default Page;
出力結果
一画面に収めたかったので、PagesRouterはiframeで表示しています
識別方法の解説
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で動けば良いというのが垣間見えてフレームワークが一強化したことによる弊害が出てきた状態です。