本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
Next.js道場

Next.js のキャッシュ。いつ描かれるのか

Next.js のキャッシュは、そのページを事前に1枚作って全員に配るか、アクセスのたびに描くかの二択で、どちらになるかは何を使ったかで決まります。

聞くべきことは1つだけ

キャッシュの話は複雑に見えますが、ページ単位で見れば問いは1つです。

このページは、いつ描かれるのか。

答えは2つしかありません。ビルドのときに1枚だけ描いて全員に配るか、アクセスのたびに描くか。ページの中で読む process.env も、描いたときに読まれます(環境変数はいつ読まれるのか)。そしてどちらになるかは、自分で選ぶのではなく、何を使ったかで決まります。

ビルドの出力が答えを持っている

覚える必要はありません。ビルドすると、ページごとにどちらになったかが出ます。実機の出力です。

Route (app)            Revalidate  Expire
┌ ○ /                                        ← 事前に1枚
├ ƒ /cookie                                  ← 毎回描く
├ ○ /revalidated            1m      1y       ← 事前に1枚。1分で作り直す
└ ƒ /search                                  ← 毎回描く
記号の意味。ビルドのたびにここを見る
記号意味いつ描かれるか
○Staticビルドのとき1回
●SSGビルドのとき、パスの数だけ
ƒDynamicアクセスのたび

設計どおりかを確かめる場所は、コードではなくここです。

何を使うと、毎回描く側へ倒れるか

/cookie と /search が動的になったのは、次のものを使ったからです。

どれも「人によって違う」もの。1枚を全員に配れなくなる
使ったものなぜ倒れるか
cookies()人によって違う
headers()リクエストごとに違う
searchParamsURL のたびに違う

実機で確かめました。Cookie を1行読むだけのページは動的になり、成果物を検索してもその画面の文字は1件も見つかりません。事前に描かれていないからです。

「毎回描く」と「古くなったら作り直す」は別のもの

ここが混ざりやすいところです。

どちらも「最新にしたい」だが、動きがまったく違う
毎回描く
export const dynamic = 'force-dynamic'

アクセスのたびにサーバーが働く。全員が待つ。

古くなったら作り直す
export const revalidate = 60

配るのは出来あがったもの。作り直しは裏で起きる。

revalidate を書いたページは、事前に描かれたままです。実機の一覧でも ○ のまま Revalidate 1m と出ます。配るときは出来あがったものをそのまま返すので、誰も待ちません。

秒数は「古い内容が出ても困らない時間」で決めます。在庫や価格のように間違うと困るものは、時間で切るのではなく、更新したときに作り直す形にします(更新を反映させる最小構成)。データにタグを付けているなら、タグ単位で捨てる revalidateTag もあります。Next.js 16 で呼び方が変わったので注意してください(revalidateTag の第2引数と updateTag)。

force-static は、黙って空を焼き付ける

これがいちばん危険です。実機で確かめました。

export const dynamic = 'force-static'
 
import { cookies } from 'next/headers'
 
export default async function Page() {
  const c = await cookies()
  const name = c.get('user')?.value
  return <p>{name ? name + ' さん' : 'ログインしていません'}</p>
}

エラーも警告も出ません。ビルドは通り、出来あがった HTML はこうなります。

<p>ログインしていません</p>

この1枚がログイン済みの人にも配られます。

動的なパスは、一覧を渡したぶんだけ事前に描かれる

app/blog/[slug]/page.tsx のようなパスは、そのままでは毎回描く側です。どの slug が来るか分からないからです。

generateStaticParams で一覧を渡すと、その数だけ事前に描かれます。実機の出力です。

├   /blog/[slug]
│ ├ ● /blog/a
│ └ ● /blog/b
└ ƒ /news/[id]        ← 一覧を渡していないほう

記事のように数が決まっているものは渡す、検索結果のように決まらないものは渡さない、が目安です。

確かめ方

  1. pnpm build する
  2. ルートの一覧で、意図した記号になっているか見る
  3. 違っていたら、そのページが何を使っているかを探す
倒れた原因をたどる順番
  1. Build
    一覧で ƒ になっている

    事前に描かれていない。ここが出発点。

  2. Server
    cookies / headers / searchParams を探す

    ページ本体だけでなく、呼んでいる関数の中まで見る。

  3. Build
    その部分を Suspense で囲む

    外側は事前に描かれたまま残せる。

「キャッシュが効かない」の正体は、たいてい自分で動的に倒していることです。設定を足して直すより、何を使っているかを探すほうが早く着きます。

出典

このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。

理解できたか試す

では、このコードはどうなりますか。

このページはビルド時に作られますか。それともリクエストごとに作られますか。

この問題を解くこのテーマの問題 3 問