ミドルウェア
すべてのクライアントサイドナビゲーションの前に実行されるルートガード。
src/middleware.ts は、すべてのナビゲーション — 初回マウント、push/replace、ブラウザの戻る/進む — の前に実行され、それをリダイレクトできるデフォルト関数をエクスポートできます:
import type { Middleware } from 'murasaki'
const middleware: Middleware = ({ pathname }) => {
if (pathname === '/admin') return { redirect: '/' }
}
export default middleware<AppRouter>(スキャフォールドのエントリがマウントします)は、この関数を仮想ルートテーブルから自動的に読み込みます — 手動で登録する必要はなく、このファイルが存在しなくても何も起こりません。
シグネチャ
export interface MiddlewareContext {
pathname: string
search: string
}
export type MiddlewareResult = { redirect: string } | void | undefined
export type Middleware = (
ctx: MiddlewareContext,
) => MiddlewareResult | Promise<MiddlewareResult>ctx は意図的に小さく保たれています — ナビゲーション先の pathname と、そのクエリ文字列(search、location.search 形式: ''、または ? で始まる文字列)だけです。request/response もヘッダーも cookie もありません — これは完全にクライアントサイドで、あなたのルートと同じ WebView の中で実行され、エッジやサーバーランタイムではありません。リダイレクトせずに早期リターンするには、何も返さない(undefined)だけです。
非同期でも構いません — <AppRouter> はマッチしたルートをレンダリングする前にそれを await し、その間は何もレンダリングしないため、ガードされたルートが一瞬でも表示されることはありません。
middleware は pathname が変わったときに再実行され、クエリだけのナビゲーションでは再実行されません — ?tab=... のような更新は、再度 await でページをゲートする代わりに前回の判定をそのまま使い回します。クエリ文字列自体を読みたい場合は、ルート側で useSearchParams() を使ってください。
ルートをガードする
もっとも一般的な形は、クライアントサイドの状態(ストア、localStorage、あるいは document.cookie 経由の cookie)を読んで、未認証の訪問者を保護されたパスから遠ざける認証チェックです:
import type { Middleware } from 'murasaki'
import { useAuth } from '@/lib/auth'
const PROTECTED = ['/settings', '/billing']
const middleware: Middleware = ({ pathname }) => {
const isProtected = PROTECTED.some((p) => pathname.startsWith(p))
if (isProtected && !useAuth.getState().isSignedIn) {
return { redirect: '/login' }
}
}
export default middlewareリダイレクトなしの副作用
middleware は必ずしもリダイレクトする必要はありません — void を返せばナビゲーションはそのまま通過するため、すべてのナビゲーションで副作用を実行する場所(例えば分析用のページビュー計測)としても便利です:
import type { Middleware } from 'murasaki'
import { trackPageview } from '@/lib/analytics'
const middleware: Middleware = ({ pathname }) => {
trackPageview(pathname)
}
export default middlewareリダイレクトループ(5 回以上連続したリダイレクト)は自動的に検出されます: Murasaki は警告をログに出力し、ハングする代わりに現在のパスをそのままレンダリングします。
どのパスでミドルウェアを実行するかを絞り込む matcher/config のようなエクスポートはありません — Next.js と異なり、middleware は常にすべてのナビゲーションで実行されるため、上の例のように関数の中で pathname に応じて自分で分岐します。
これは Next.js のミドルウェアに相当する Murasaki のクライアントサイド版です — あなたのルートと同じ WebView の中で実行されるため、エッジランタイムは関与しません。