Next.js の middleware をチョットイイ感じに書く
🗣 はじめに
こちらのライブラリの宣伝です。 Next.js の middleware をより可読性高く、管理しやすくすることを目的に開発しました。
本記事で紹介しているコードは deprecated です。最新版は↓の記事をご覧ください。
⛰ 背景
先日 Next.js の middleware がついに安定版になりました ( v12.2.0 以降)。
ベータ版からの破壊的変更の一つとして、 nested middleware の廃止 があります。つまり、プロジェクト内に middleware は1つしか存在できなくなります。
公式ドキュメントにいくつか middleware の実装例が載っていますが、上記の変更を如実に受けた実装例の一つとして、
「パスに応じて middleware 内の処理の分岐を行いたい場合は middleware 内部で if 文を使って分岐するというものがあります。
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
これは middleware.ts ファイルはプロジェクトにつき1つしか存在できない、という制約によって生み出された新しい様式です。( nested middleware 時代はファイルを置く場所で指定していた)
💡 課題 & 解決策
パスに応じて完全に処理が分かれている先ほどのような場合だと特に問題なさそうに見えます。では、もう少し複雑な以下の要件の場合はどうでしょうか?
- Basic認証をかけたい(失敗したらその時点で return )
- 特定のページの場合はリダイレクト
- cookie に特定の値を付与する
export function middleware(request: NextRequest) {
const res = NextResponse.next()
const success = basicAuth(req);
if (!success) {
return NextResponse.rewrite(new URL('/api/basic-auth', req.url))
}
if (req.nextUrl.pathName.startsWith('/secret')) {
const isValidUser = validateUser(req);
if (!isValidUser) {
return NextResponse.rewrite(new URL('/', req.url))
}
}
res.cookies.set('foo', 'foo');
reutrn res;
}
if 文が増えたことで middleware 全体でどういった処理の流れなのかが追いづらくなってしまいました。もっと機能が増えていった場合はかなりひどいコードになりそうです。 Basic認証の検証部分などを関数として切り出していてもこの有様です。
成功/失敗時の処理の分岐なども含めて切り出してあげるとだいぶ見通しが良くなりそうですが、何も考えずに切り出すと早期 return したい場合に対応するのが面倒だったり、結局 if 文が残ってしまったりします。
そこで本ライブラリの出番です。
export function middleware(request: NextRequest) {
return pipeMiddleware(req, NextResponse.next(), [
basicAuthMiddleware,
[redirectMiddleware, {matcher: path => path.startsWith('/secret')}],
fooCookieMiddleware
]);
}
各処理の順番や実行時の条件などが一つにまとまり、かなり処理の見通しが良くなりました🎉
これなら全貌を掴みやすく、管理も楽そうです。今回のような、全ページに対して実行したい処理が複数ある場合は本ライブラリが活躍してくれると考えています。
(詳細については README をお読みください)
🌃 終わりに
以上、 Next.js の middleware を使う上での課題感の共有とその解決策の一つを本ライブラリの機能として紹介しました。
気に入ってくださった方はぜひ本ライブラリを使ってくださると嬉しいです😎 デモ
Discussion