TL;DR
-
middleware.tsでNextResponse.redirect()を返すだけ。next.config.jsもuseEffectも不要 -
matcherで対象パスを絞れば静的ファイルへの不要な介入を防げる - 307 (一時) か 308 (永続) かは用途次第で使い分け
背景・課題
Next.js App Router で SaaS を開発していると、こんなケースに出くわすことがあります。
LP 用のサブドメイン (
app.example.com) に/adminでアクセスされたとき、
ログイン画面/loginに飛ばしたい。
要件としては単純ですが、「どこに書けばいいか」で迷う人は多いです。
やりがちな解法とその欠点
パターン 1: next.config.js の redirects()
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/admin',
destination: '/login',
permanent: false,
},
]
},
}
欠点: redirects() はビルド時に静的評価されるため、動的な条件分岐ができません。「Cookie の有無で分岐」「特定のヘッダーがある場合のみ」といったロジックを挟めないので、実運用では使いにくい場面が多いです。
パターン 2: app/admin/page.tsx から useEffect + router.push
// app/admin/page.tsx (NG 例)
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
export default function AdminPage() {
const router = useRouter()
useEffect(() => {
router.push('/login')
}, [router])
return null
}
欠点:
- CLS (Cumulative Layout Shift) が発生する: ページが一瞬レンダーされてから遷移するため、Lighthouse スコアを下げます
- SEO に不利: HTML が先に返ってからリダイレクトするため、クローラーが混乱しやすい
- 余分なページコンポーネントが必要: リダイレクトだけのために tsx ファイルを作るのは無駄
正解: Middleware で 1 行
ファイル配置
middleware.ts はプロジェクトルート (または src/ 配下) に置きます。
my-app/
├── app/
│ └── ...
├── middleware.ts ← ここ
└── next.config.js
実装コード
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/admin/:path*',
}
これだけです。 本当に 1 行 (+ matcher 設定) で完結します。
各行の解説
| 行 | 意味 |
|---|---|
NextResponse.redirect(new URL('/login', request.url)) |
リクエスト元の origin を維持しつつ /login へ 307 リダイレクト |
matcher: '/admin/:path*' |
/admin および /admin/settings 等のサブパスにのみ Middleware を適用 |
request.url を base にして new URL('/login', request.url) とすることで、https://app.example.com/login のように 自動的に origin が補完されます。ハードコードが不要なのが嬉しいポイントです。
matcher を設定する理由
matcher を省略すると、_next/static/、_next/image/ などの静的ファイルリクエストにも Middleware が走り、パフォーマンスが劣化します。対象パスを明示的に絞るのがベストプラクティスです。
307 vs 308 の使い分け
NextResponse.redirect() はデフォルトで 307 Temporary Redirect を返します。用途に応じて使い分けましょう。
| ステータス | 意味 | 使うべき場面 |
|---|---|---|
| 307 Temporary Redirect | 一時的な転送。POST リクエストのメソッドを維持 | 認証ガード・仮のリダイレクト |
| 308 Permanent Redirect | 永続的な転送。POST リクエストのメソッドを維持 | URL 変更が確定した場合 |
| 301 Moved Permanently | 永続的な転送。POST が GET に変わることがある | SEO 目的の URL 統一 (注意が必要) |
| 302 Found | 一時的な転送。POST が GET に変わることがある | 一般的な一時リダイレクト |
認証ガードとして /admin → /login に使う場合は 307 が適切です。「まだ未認証だから一時的にログインページに飛ばす」という意味合いで、将来的に認証後は /admin に戻れるからです。
ステータスコードを変更するには、第 2 引数でオプションを渡します:
return NextResponse.redirect(new URL('/login', request.url), {
status: 308,
})
動作確認: curl で検証
ローカル (next dev) を起動した状態で試してみましょう。
# -L でリダイレクトを追跡、-v で詳細表示
curl -Lv http://localhost:3000/admin 2>&1 | grep -E "< HTTP|Location"
期待される出力:
< HTTP/1.1 307 Temporary Redirect
< Location: /login
...
< HTTP/1.1 200 OK
/admin に対して 307 が返り、/login で 200 が得られれば成功です。
Lighthouse への影響 (一般論)
| 方式 | FCP | CLS | SEO |
|---|---|---|---|
useEffect + router.push
|
遅め | 悪化しやすい | 不利 |
next.config.js の redirects()
|
速い | 影響なし | ○ |
| Middleware | 最速 | 影響なし | ◎ |
Middleware はリクエストがルートレンダリングに到達する前に処理されるため、ブラウザに HTML が届く前にリダイレクトレスポンスが返ります。CLS は原理的に発生せず、Lighthouse の Performance・SEO スコアにも悪影響を与えません。
応用: サブドメインで条件分岐
同じパターンを応用すると、サブドメインによって処理を分岐させることもできます。
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const hostname = request.headers.get('host') ?? ''
// 管理用サブドメインからのアクセスのみ /login にリダイレクト
if (hostname.startsWith('admin.')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/:path*',
}
next.config.js の静的な redirects() では書けない、動的ロジックが光るユースケースです。
Next.js v16 の注意点: middleware.ts → proxy.ts
Next.js v16.0.0 から、middleware.ts は proxy.ts に改名される予定です (2026 年 5 月時点のドキュメントより)。現在は公式 codemod でマイグレーション可能です。
npx @next/codemod@canary middleware-to-proxy .
v15 以前のプロジェクトでは引き続き middleware.ts が動作します。新規プロジェクトを v16 で始める場合は proxy.ts の命名を採用しましょう。
まとめ
- Next.js Middleware は SPA でも有効。1 行 で特定パスのリダイレクトを実装できる
-
useEffectの CLS 問題、next.config.jsの動的条件不可問題を根本解決できる -
matcherでパスを絞ることでパフォーマンスへの影響を最小化 - サブドメイン分岐・Cookie 条件・ヘッダー条件など動的ロジックにも対応可能
- v16 以降は
proxy.tsへの移行も視野に
参考リンク
- Next.js 公式 - Middleware (proxy)
- Next.js 公式 - NextResponse API
- Next.js 公式 - next.config.js redirects()
本記事中のコードサンプルは学習目的の最小例です。MIT ライセンス相当として自由にご利用ください。
弊社について
合同会社ジモラボは東京八王子で AI × 業務効率化の SaaS を複数運営する研究所型企業です。AI 議事録 (realingo)・GEO 検索最適化 (lookupai)・AI セキュリティ (Promtect)・会計 (locatax) などを展開しています。