🌀
Layout.tsxはミドルウェア的に使ってもいいの?(Next.js 14)
背景
App RouterのLayout.tsxをミドルウェア的に使っている例を見る。
export default async function Layout({ children }: { children: ReactNode }) {
const supabaseClient = createSupabaseServerComponentClient();
const { data, error } = await supabaseClient.auth.getUser();
if (!data.user) {
// This is unreachable because the user is authenticated
// But we need to check for it anyway for TypeScript.
return redirect('/login');
} else if (error) {
return <p>Error: An error occurred.</p>;
}
return <>{children}</>;
}
これはレイアウトじゃなくてミドルウェアじゃないかって思いつつ、現状のNext.jsのmiddlewareはルートにしか置けないのでルーティングをグループ化して適用するには力不足といいたい気持ちもわかる。Route Groupsがあるので、特定のルートに対してMiddleware的な処理を書きたいなら、ルートのMiddlewareでマッチャーを頑張るよりコロケーション観点で望ましい書き方になる。
この現状が、できちゃうけどいずれ違う書き方が提供されちゃうよ?系の問題なのか調べてみる。
補足
本記事公開後、 @teruhisa さんからReply記事を公開いただきました!ぜひこちらも合わせてご覧ください。
補足さらにその2
本記事公開後、以下記事にて「Layout.tsx内で認証情報を取り扱うのは適切ではない」旨が示されました。合わせてご覧ください。
(要は、Layout.tsxで弾いてもページ内容がレンダリング(実行)されないことは保証されない。正確には、ページ内容の取得自体を認証チェックすれば防げるが、紛らわしいので推奨されない。という感じです)
調査
-
https://nextjs.org/docs/app/api-reference/file-conventions/layout
- 「A layout is UI that is shared between routes.」って書いているのでめっちゃめちゃ違う用途じゃないか?
- たとえば文中に「Layout components do not receive the
searchParams
prop」とあるように、再レンダリング防止のためとあらば特定の機能を絞ることも辞さないように見える。全然Frameworkの方針としては正しい
-
https://nextjs.org/docs/app/api-reference/file-conventions/middleware
- middlewareはルートに置くというので間違ってなさそうなのだが、各ディレクトリごとに置けたほうが嬉しそうなのだが。昔はそうだったような・・・
- あ、もともとルートごとに置けたんだけどSimplifyのためにルートOnlyにしたらしい。経緯はこちら→ https://nextjs.org/docs/messages/middleware-upgrade-guide
意見
- Middlewareの再利用性と可読性担保として高階関数による実装方法が提案されている
-
createSupabaseServerComponentClient
のような関数が内部的に通常のServer Componentsで利用されることを想定した実装になっているだろうが、これがLayout.tsxにおいても今後恒久的に提供されうることは何ら保証されていないし、されなくても文句がない -
コロケーションはどうか?
- Middlewareの機能部分のみをExportし、ルートのMiddlewareで処理を適用する
- Middlewareの機能部分のみをExportし、各DirのLayout.tsxで処理を適用する
logged-in-pages
├── dashboard
│ └── page.tsx
├── middleware.ts
└── layout.tsx
将来的に何らかの不都合が解消されてNestedなmiddleware.tsが復活することを願いつつ・・・
middleware.ts
export const checkLoggedIn = async () => { ... }
layout.tsx
export default async function Layout () {
await checkLoggedIn()
}
Runtimeの差
-
また、そもそもNext.jsのMiddlewareはEdge Runtimeで動作することが必須なので、Vercelで動かさない場合(ECSなど)はMiddlewareが使えないはず。その場合は消去法的にLayout.tsx一択になる→コメント欄で間違いを指摘していただきました!ありがとうございます! - それにしても元々のNextbaseのテンプレートRepoではMiddlewareもLayout.tsxも併用しちゃっていたので、それだったらLayout.tsxに統一してもいいんじゃない?という気も。
結論
- Next.jsのmiddlewareがルートだけになっているのは明確な意思があるため今後も代わりにくい
- Layout.tsxは本来UIの共通化のためにあるが、Middlewareのようにアクセス制限を掛ける目的でも使えてしまっている
- 個人的な見解として、Layout.tsxはドキュメントを読む限りレンダリングの最適化のために機能を意図的に制限することもあるらしいから、本来のMiddlewareに近い用途で使うのであれば、面倒でもルートのMiddlewareに書く方針でやったほうがよさそう。それに単純に命名と実情がかけ離れていて可読性が低い
- ただ、Pages時代からの引き継ぎでミドルウェア相当の実装が既にあるんです、といった場合はLayoutに乗っける方が工数短縮しそうだし、いたずらにミドルウェア使ってないからって責めていい理由にはならない。それくらい微妙だと思う
- Reactが何でもできちゃうから自由と責任がついてまわってますな(感想)
- 折衷案として、Middlewareを高階関数で構築し、各高階関数をルートからExportすることでコロケーションを担保するのも一案かと思うが、Middlewareはあらゆるルートで呼ばれるということなので、最初のマッチャーをちゃんと組んで無駄に動くことが無いようにしたいところ。このへん綺麗に実装されたTemplate Repoがあれば知りたい。
- 具体的には、Matcherの正規表現はmiddleware.ts内に書かれていてパスはFile Basedだと責務が分かれているのが気持ち悪い。たとえばPathpidaや Statically Typed Links などの仕組みと合わせてFile Based側が変更になったときにデグレをTSCが拾えるようにできたらいいかな?仕方ないかもしれないが。
Discussion
投稿ありがとうございます、参考になります!
自分もmiddlewareに対して過去近しい意見だったものの、今は変化し&少し長くなりそうだったので返信として記事にしました🙂
これは誤りな気がします。Edge Runtime「でも」動くよう制約がかかりますが、Self-hosting でも使えます。
ありがとうございます!上記の記事でも指摘いただいてますので、後ほど修正します!
こちらについて、ご指摘いただいた旨本文にも記載しました!