空雲 Blog

Eye catch
Next.jsのAppRouterでlayout.tsxからServer/Clientコンポーネントにデータを配る

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

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


サンプルソース🔗




app/context.ts🔗


Serverコンポーネント間で共有するコンテキストを生成します。
Server/Client間でContextのインスタンスを自動識別する方法がどうしても思いつかなかったので、複数のContextを扱うときはユニークな名前が必要です。


1import { createMixContext } from "next-approuter-context";
2
3export const context1 = createMixContext<{ text: string; color: string }>(
4 "context1"
5);
6export const context2 = createMixContext<number>("context2");


app/layout.tsx🔗


既存のContextAPIに似せてProviderを作る書き方にしています。


1import { context1, context2 } from "./context";
2
3export default function RootLayout({
4 children,
5}: {
6 children: React.ReactNode;
7}) {
8 return (
9 <html lang="en">
10 <body>
11 <context1.Provider
12 value={{ text: "Send colors and text from Layout", color: "red" }}
13 >
14 <context2.Provider value={123456}>{children}</context2.Provider>
15 </context1.Provider>
16 </body>
17 </html>
18 );
19}


app/page.tsx🔗


動作確認用にServer/Clientコンポーネントを呼び出します。


1import { Client } from "./client";
2import { Server } from "./server";
3
4const Page = () => {
5 return (
6 <>
7 <Server />
8 <Client />
9 </>
10 );
11};
12
13export default Page;


app/server.tsx🔗


ServerコンポーネントからContextを取得しています。コンポーネントをasyncにする場合は、データの取得方法がgetMixContextを使った形に書き換える必要があります。


1"use server";
2
3import { useMixContext } from "next-approuter-context";
4import { context1, context2 } from "./context";
5
6export const Server = () => {
7 // If the component is async, it should be written as follows
8 // const { text, color } = await getMixContext<ContextType1>();
9 const { text, color } = useMixContext(context1);
10 const value = useMixContext(context2);
11 return (
12 <>
13 <div style={{ color }}>
14 Server: {text} - {value}
15 </div>
16 </>
17 );
18};


app/client.tsx🔗


ClientコンポーネントからContextを取得しています。基本的にServerコンポーネントと同じコードになるように、ライブラリを作ってあります。


1"use client";
2
3import { useMixContext } from "next-approuter-context";
4import { context1, context2 } from "./context";
5
6export const Client = () => {
7 const { text, color } = useMixContext(context1);
8 const value = useMixContext(context2);
9 return (
10 <>
11 <div style={{ color }}>
12 Client: {text} - {value}
13 </div>
14 </>
15 );
16};


実行結果🔗


見事、データが配れない問題と実行順序の問題を解決しました。


{"width":"525px","height":"66px"}


色々なことを力技で解決していますが、具体的なソースはこちらを見てください。


https://github.com/ReactLibraries/next-approuter-context/tree/master/src


まとめ🔗


Server/Client の両方に layout.tsx からデータを配れるようになりました。このやり方を使えば、UI ライブラリのテーマ設定も簡単です。