Next.jsのAppRouterでlayout.tsxからServer/Clientコンポーネントにデータを配る
Contextを利用できないServerコンポーネント
Next.jsのAppRouterのServerコンポーネントでは、createContextが使用できません。これに加え、Next.jsではcreateServerContextも使用できなくなったため、Contextを使ってデータをコンポーネントツリーに配ることができません。
PagesRouter上では_app.tsxからContextを配ることが出来てとても便利でした。しかしAppRouterのServerコンポーネントにはその機能がありません。
そもそもpage.tsxがlayout.tsxよりも先に実行される
AppRouterのコンポーネント評価順序は以下のようになります。
(1) page.tsx
(2) layout.tsx
つまり、先にpage.tsxが実行され、その後layout.tsxのコンポーネントが評価されます。そのため、layoutからpageにデータを配ろうにも、データを配る前にpageが実行されてしまい、データを配ることができません。
データを配ることも出来なければ、そもそも実行順序も違う
そう、不可能なのです。普通にやったら。
不可能を可能にする
ということで、不可能を可能とするコードを書きました。
npmにパッケージ化したものを登録してあります。
https://www.npmjs.com/package/next-approuter-context
サンプルソース
GitHub
https://github.com/SoraKumo001/next-approuter-context-testCodeSandbox
https://codesandbox.io/p/github/SoraKumo001/next-approuter-context-test/master
app/context.ts
Serverコンポーネント間で共有するコンテキストを生成します。
Server/Client間でContextのインスタンスを自動識別する方法がどうしても思いつかなかったので、複数のContextを扱うときはユニークな名前が必要です。
import { createMixContext } from "next-approuter-context";
export const context1 = createMixContext<{ text: string; color: string }>(
"context1"
);
export const context2 = createMixContext<number>("context2");app/layout.tsx
既存のContextAPIに似せてProviderを作る書き方にしています。
import { context1, context2 } from "./context";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<context1.Provider
value={{ text: "Send colors and text from Layout", color: "red" }}
>
<context2.Provider value={123456}>{children}</context2.Provider>
</context1.Provider>
</body>
</html>
);
}app/page.tsx
動作確認用にServer/Clientコンポーネントを呼び出します。
import { Client } from "./client";
import { Server } from "./server";
const Page = () => {
return (
<>
<Server />
<Client />
</>
);
};
export default Page;app/server.tsx
ServerコンポーネントからContextを取得しています。コンポーネントをasyncにする場合は、データの取得方法がgetMixContextを使った形に書き換える必要があります。
"use server";
import { useMixContext } from "next-approuter-context";
import { context1, context2 } from "./context";
export const Server = () => {
// If the component is async, it should be written as follows
// const { text, color } = await getMixContext<ContextType1>();
const { text, color } = useMixContext(context1);
const value = useMixContext(context2);
return (
<>
<div style={{ color }}>
Server: {text} - {value}
</div>
</>
);
};app/client.tsx
ClientコンポーネントからContextを取得しています。基本的にServerコンポーネントと同じコードになるように、ライブラリを作ってあります。
"use client";
import { useMixContext } from "next-approuter-context";
import { context1, context2 } from "./context";
export const Client = () => {
const { text, color } = useMixContext(context1);
const value = useMixContext(context2);
return (
<>
<div style={{ color }}>
Client: {text} - {value}
</div>
</>
);
};実行結果
見事、データが配れない問題と実行順序の問題を解決しました。
色々なことを力技で解決していますが、具体的なソースはこちらを見てください。
https://github.com/ReactLibraries/next-approuter-context/tree/master/src
まとめ
Server/Client の両方に layout.tsx からデータを配れるようになりました。このやり方を使えば、UI ライブラリのテーマ設定も簡単です。