Next.js App Router middleware の cookie が Cloudflare Pages で動かない原因

※本記事にはプロモーション(広告・アフィリエイトリンク)を含みます。
Cloudflare Pages に Next.js App Router をデプロイし、middleware.ts で cookie を書いているのに「セットされない」「読めない」という問題は、原因が複数同時に絡みやすい。この記事では動かない原因を3つのパターンに整理し、それぞれの対処をコードで示す。
Cloudflare Pages で Next.js が動く仕組みの前提
Next.js を Cloudflare Pages にデプロイするには、公式アダプター @cloudflare/next-on-pages(執筆時点)を使う。このアダプターが Next.js のビルド出力を Cloudflare Workers(Edge Runtime)向けに変換する。
通常の next start(自前 Node.js サーバー)との最大の違いはランタイムだ。
| 実行環境 | ランタイム | Node.js API |
|---|---|---|
next start(VPS・自宅サーバー等) |
Node.js | フル利用可 |
| Cloudflare Pages(next-on-pages) | Workers / Edge | 制限あり |
| Next.js middleware(どの環境でも) | Edge Runtime 固定 | 制限あり |
重要なのは、Next.js の middleware.ts はどの環境にデプロイしても必ず Edge Runtime で動くという点だ。Cloudflare Pages 固有の制約もあるが、middleware の cookie 問題の多くは Cloudflare かどうかに関係なく再現する。
参考までに、私自身の EC サイトは Cloudflare Tunnel + systemd 常駐の Node.js サーバーで動いており、middleware は Node.js ランタイム上で動作している。この記事は Cloudflare Pages(Workers ランタイム)の挙動を公式ドキュメントと仕様から整理したものだ。
原因 1:next/headers の cookies() を middleware で使っている
最も多いパターンがこれだ。
// ❌ middleware.ts — これは動かない
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const cookieStore = cookies() // Edge Runtime では利用不可
cookieStore.set('session', 'value')
return NextResponse.next()
}
cookies() は Server Component や Route Handler 用の関数で、middleware では使えない。Next.js 公式ドキュメントにも「cookies() is not supported in Middleware」と明示されている。Cloudflare 固有の問題ではなく、ローカルの next dev でも同様に動かない。
middleware で cookie を扱うには request.cookies(読み込み)と response.cookies.set()(書き込み)を使う。
// ✅ middleware.ts — 正しい書き方
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
// 読み込み
const token = request.cookies.get('token')?.value
// 書き込み
const response = NextResponse.next()
response.cookies.set('token', 'new-value', {
httpOnly: true,
secure: true, // 本番(HTTPS)では必須
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24, // 24時間(秒単位)
})
return response
}
原因 2:Cloudflare の CDN キャッシュが Set-Cookie を落としている
Cloudflare の CDN はリクエスト経路にキャッシュを挟む。キャッシュヒットしたレスポンスには Set-Cookie ヘッダーが含まれないため、ブラウザまで cookie が届かない。
確認方法はブラウザの DevTools「Network」タブでレスポンスヘッダーを見ること。cf-cache-status: HIT が付いているリクエストに Set-Cookie がない、というパターンが典型だ。
対処は2つある。
① レスポンスに Cache-Control: no-store を付ける
const response = NextResponse.next()
response.headers.set('Cache-Control', 'no-store, no-cache')
response.cookies.set('token', value, { httpOnly: true, secure: true, path: '/' })
return response
② Cloudflare の Cache Rules でパスをキャッシュ対象から除外する
Cloudflare ダッシュボードの「キャッシュ」→「Cache Rules」で、認証が絡む URL パス(/api/* や /login など)に「Bypass Cache」を設定する。旧来の Page Rules でも設定できるが、現在は Cache Rules が推奨されている(執筆時点)。
原因 3:matcher の設定漏れ
middleware が対象のパスで動いているかを確認する。
// middleware.ts
export const config = {
matcher: ['/dashboard/:path*'],
}
上記では /login へのリクエストで middleware が走らない。/login で cookie をセットしたいなら matcher に含める必要がある。
matcher を書かない場合はすべてのパスに適用されるが、_next/static などの静的ファイルにも適用されてパフォーマンスに影響する。定番のパターンは以下だ。
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico).*)',
],
}
middleware が実際に動いているかは、console.log を仕込んで wrangler pages deployment tail コマンドのログで確認できる。
middleware でセットした cookie を Server Component から同一リクエスト内で読む
response.cookies.set() でセットした cookie は、同じリクエストを処理している Server Component からはすぐに読めない。これは Cloudflare Pages に限らない Next.js の基本動作で、新しい cookie 値は次のリクエストから有効になる。
同一リクエスト内で Server Component に値を渡したい場合は、cookie ではなくカスタムリクエストヘッダーを使う。
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-user-id', userId)
const response = NextResponse.next({
request: { headers: requestHeaders },
})
// クライアントへの cookie セットは response 側に書く
response.cookies.set('token', value, { httpOnly: true, secure: true, path: '/' })
return response
}
// app/dashboard/page.tsx
import { headers } from 'next/headers'
export default async function DashboardPage() {
const headersList = await headers()
const userId = headersList.get('x-user-id')
// ...
}
Web 標準の Request オブジェクトはイミュータブルなため、ヘッダーを変更するには必ず new Headers(request.headers) でコピーを作る必要がある。Workers ランタイム上での Request/Response の扱い方については Hono × Cloudflare Workers で SSE を実装した記事 も参考になる。
まとめ
| 症状 | 確認・対処 |
|---|---|
| cookie がそもそもセットされない | middleware で next/headers の cookies() を使っていないか確認 |
DevTools に Set-Cookie ヘッダーが来ない |
cf-cache-status を確認し、Cache-Control: no-store を追加 |
| 特定のページだけ middleware が動かない | config.matcher にそのパスが含まれているか確認 |
| Server Component から新しい値が読めない | NextResponse.next({ request: { headers } }) でリクエストヘッダー経由に変更 |
| どれでもない | @cloudflare/next-on-pages と Next.js のバージョン互換を公式ドキュメントで確認 |
デバッグの入口は DevTools「Network」タブのレスポンスヘッダー確認だ。Set-Cookie がヘッダーに含まれていないなら Cloudflare のキャッシュか middleware のコードを疑い、Set-Cookie は届いているのに cookie が読めないなら httpOnly / SameSite / Secure の属性設定を見直す。まずここから切り分けてほしい。
この記事で触れたもの
- Next.js 実践楽天で探す
よくある質問
Cloudflare Pages で Next.js の middleware.ts は動きますか?
動きます。`@cloudflare/next-on-pages` アダプター経由で Cloudflare Workers(Edge Runtime)上で実行されます。ただし Node.js 固有の API は使えず、`next/headers` の `cookies()` など一部の関数は利用不可です。バージョン互換も公式ドキュメントで確認が必要です。
middleware でセットした cookie を同じリクエストの Server Component から読めますか?
`response.cookies.set()` でセットした cookie は次のリクエストから有効になるため、同一リクエスト内の Server Component からはすぐに読めません。値を渡す場合は `NextResponse.next({ request: { headers } })` でカスタムリクエストヘッダーを付与する方法が有効です。
`next/headers` の `cookies()` は middleware で使えますか?
使えません。Server Component や Route Handler 向けの関数です。middleware では `request.cookies`(読み込み)と `response.cookies.set()`(書き込み)を使います。これは Cloudflare Pages 固有の制約ではなく Next.js の仕様で、ローカルの `next dev` でも同様に動きません。