はじめに
Next.jsの人気がすごいですね。
実サービスで使っている人も多いかと思います。
そんなみなさんにNext.jsのMiddlewareを使いましょう。
という話をします。
- ABテスト
- 静的ページだけど、ちょっとだけパーソナライズ
- アクセスコントロール
- etc...
色々なものに活用できる技術です。
是非みていってください
Next.jsのmiddlewareとは?
Next.jsのv12.2.0
からstableになった機能です。
リクエストが完了する前に実行されます。
つまり、SSRであればgetServerSidePropsが動くより前に完了するし、SG(SSG)であったとしてもページのキャッシュがユーザーに変えるより先に実行されます。
Middlewareの魅力
1. エッジサーバーで動作すること
これはVercelにデプロイしたらという前提が入ってしまいますが、Middlewareはエッジサーバーで起動します。
ユーザーにより近い距離で、かつオリジンサーバーのリソースを食い潰すことなく動きます。
日本からしかアクセスのないサービスであればパフォーマンス面でめちゃくちゃ大きい恩恵があるというわけではないと思いますが、アクセスが集中した場合なども考えるとオリジンサーバーに負荷なく動くというのはとても魅力的かなと思います。
2. 静的コンテンツをパーソナライズできること
1にも絡んでくることですが、Middlewareはエッジサーバーで動作します。
CDNにキャッシュされたコンテンツより先に動作します。
つまり、静的コンテンツをパーソナライズすることが可能になったりします。
やりすぎ注意かと思いますが、静的コンテンツをちょっとだけパーソナライズしたいとか、振り分けしたい場合にはとても強力になるかと思います。
3. 全ページに対して一気にサーバーサイドの処理がかけれること
個人的にはこれが一番気に入ってます。
Next.jsを使っていると、サーバーサイドの処理を書くのがきついなぁと毎回思ってました。
アクセスコントロールなどはマイページ配下全体にかけたかったりするのに対し、各ページのgetServerSidePropsに書く必要があり、処理が漏れたり、下手なコードを書いてしまうと怖い、、
という気持ちがありました。
Middlewareは特定ページに対し、一気にサーバーサイド処理をかけれるため気に入ってます。
どうやって使うの?
pages/
配下にmiddleware.ts
を置くことで動作します。
引数にNextRequest、返り値にはNextResponseを必要とします。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return NextResponse.next()
}
Cookieの制御
他のサーバーサイドの処理と同様、request
に含まれているので、以下のようにすればアクセスできますし、response
へセットすれば次のリクエストからcookieを保持したリクエストで来てくれます。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const AB_TEST_KEY = 'ab'
export function middleware(request: NextRequest) {
const ab = request.cookies.get(AB_TEST_KEY)?.value
const response = NextResponse.next()
if (!ab) {
response.cookies.set(AB_TEST_KEY, createAB())
}
return response
}
リダイレクト
NextResponse.next()
ではなく、NextReponse.redirect()
を使います
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return NextReponse.redirect(new URL('/about-2', request.url))
}
特定のページだけMiddlewareを適応
configという名前のオブジェクトをexportすれば良いです
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
正規表現が使えるので、特定のページ以外に適応したいときはこんな感じ
export const config = {
matcher: [
'/((?!api|_next/static|favicon.ico).*)',
],
}
まとめ
いかがでしたでしょうか?
新しい機能なのでまだあまり文献もなく、ベストプラクティスがわからなかったりしますが、こんなことやってみたよというのをまた投稿してみたいなと思います。
あと、調べつつわからなかったことの共有しておきます。
上に出てきた、NextResponse.next()
について。
next()
という響きから、middlewareをチェインすることができるっぽい雰囲気がとてもしますが、その文献が見つからなかった。
https://github.com/vercel/next.js/blob/689626c6a4d509fe04aaf82c9971a5928669bcf4/packages/next/server/web/spec-extension/response.ts#L64
をみると、x-middleware-next
というCookieを格納しているので、これにより何かチェインできたりするかもしれない(誰か知ってたら教えてください