😸
Next.jsのAppRouterでlayout.tsxを先に実行しpage.tsxにデータを配る(14系統版)
実行順序
昨今、AppRouter で page.tsx が layout.tsx より先に実行されることが話題になっていますが、去年こんな記事を書きました。
箸にも棒にも引っかからなかったこの記事ですが、次元を歪めることに成功し実行順序を変えています。しかし久々に実行してみると Next.js の仕様が変わって、"use server"したファイルが非同期関数しかエクスポートできないという仕様に変わっており、修正が必要になっていたので、焼き直しで記事を書くことにしました。
layout.tsx を page.tsx より先に実行する
ということでやってみます。
Sample コードと実行環境はこちら
- GitHub
https://github.com/SoraKumo001/next-approuter-context-test - Vercel
https://next-approuter-context-test.vercel.app/
app/context.tsx
React の ContextAPI 風にコンテキストを作ります。
import { createMixContext } from "next-approuter-context";
export const context1 = createMixContext<{ text: string; color: string }>(
"context1"
);
export const context2 = createMixContext<number>("context2");
app/layout.tsx
レイアウトにデータを設置します。当然のごとく、page.tsx よりも layout.tsx が先に実行が完了されないとデータを配れません。
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 component で、layout.tsx からのデータを受け取ります。
"use server";
import { context1, context2 } from "./context";
import { getMixContext } from "next-approuter-context";
export const Server = async () => {
const { text, color } = await getMixContext(context1);
const value = await getMixContext(context2);
return (
<>
<div style={{ color }}>
Server: {text} - {value}
</div>
</>
);
};
app/client.tsx
Client component で、layout.tsx からのデータを受け取ります。
"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>
</>
);
};
実行結果
まとめ
AppRouter で layout.tsx の実行を先に完了させ、さらに page.tsx へデータを配ることに成功しました。React の renderToReadableStream を使っている Next.js や Remix の場合、コンポーネントの処理中に保留と再開が任意に可能です。つまりサーバ側のレンダリング処理でそのタイミングを制御するのは、やろうと思えばいくらでも可能なのです。今回も、単にデータ配布の非同期解決まで page.tsx を待たせているだけで、大したことはしていません。
まずは、それが可能だと思うことが重要です。
Discussion
その発端となったのは 自分のコメント だと思うのですが、そこで自分が書いたのは
ということで実行の「開始順」でした
しかし、こちらの記事では
と書いてあるように実行の「完了順」を変えたという話ですね(より細かくいうと
page.tsx
とlayout.tsx
の間で同期するという話ですね)それがダメとか無価値とかいうことでは全然ないのですが、ちょっと話がズラされてるというか別の話をされてる感があります(そしてそれが明確ではない)
また、発端となった記事の主題は認証チェックであり、元々そこでやってほしかったことは「
layout.tsx
で未認証ユーザをリダイレクトしたならpage.tsx
は実行されない」ことであったと思いますなので自分は上と同じコメントで
とも書いたわけですが、こちらの記事で紹介されている方法でそれを解決できるわけではありませんね(だから価値がないという話ではありません)
実際はちょっとした高階関数で
page.tsx
等のコンポーネントをラップするだけで「そう見える」ようにするのはとても簡単なのですが、それをすべきだとは思いません発端となった記事への別コメントで
と書いたように、そもそも実行の順序に依存するのがApp Routerあるいはcomposabilityを重視するReact的な考え方から外れていると思うからです
本記事の締めである
を否定するわけではありませんが、同時に「それをすべきか」と考えることも重要ではないかなと思いました(余計なお世話なら申し訳ありません)
こちらの記事で紹介されているサンプルアプリを動かしたわけでも、そこで使われている
next-approuter-context
のコードをじっくり読んだわけでもないので勘違いしているかもしれませんが、これって<Link>
を使ったソフトナビゲーションでもちゃんと動きますか?(ちゃんと動くように使うのが大変じゃありませんか?の方が適切な質問かも)
App Routerでは
layout.tsx
が常に実行されるとは限らず、ソフトナビゲーションでは遷移元と遷移先で共通のセグメントに含まれるlayout.tsx
は実行されませんたとえば本記事のサンプルアプリに
app/foo/page.tsx
を追加して、ブラウザで/foo
から/
に<Link>
で遷移するとapp/layout.tsx
は実行されず、app/page.tsx
だけが実行されるはずです(app/page.tsx
が動的レンダリングなrouteの場合)すると
app/page.tsx
はPromise
の完了を永遠に待ち続けたりしませんか?そうはならないかもしれないし(ちゃんと実装を見たわけではないので)、そうなったとしても対策をすることは「可能だと思う」かもしれませんが、そもそも実行順に依存するという高い結合度を導入しない方がずっといいんじゃないかなと思ってしまいます