Back to skills
extension
Category: Development & EngineeringNo API key required

nextjs-app-router-guide

A guide to using Server Components / Server Actions / Route Handlers, etc. in the Next.js App Router. Refer to this when answering questions such as 'the difference between Server Component and Client Component', 'how to use Server Actions', and 'the difference between revalidatePath/revalidateTag'.

personAuthor: jakexiaohubgithub

Next.js App Router 使い分けガイド

最小の判断フロー

flowchart TD
    A[何を実現したい?] --> B{UIコンポーネント?}

    B -->|Yes| C{interactivity必要?<br/>state/handlers/browser API}
    C -->|Yes| D["Client Component<br/>(use client)"]
    C -->|No| E[Server Component]

    B -->|No<br/>関数/ロジック| F{クライアントから呼ぶ?}
    F -->|No| G["server-only関数<br/>(import server-only)"]
    F -->|Yes| H{HTTP API公開?}
    H -->|Yes| I[Route Handler]
    H -->|No| J["Server Function<br/>(use server)"]

    J --> K{mutation文脈?<br/>form action等}
    K -->|Yes| L[Server Action]
    K -->|No| J

    L --> M{キャッシュ更新}
    M -->|stale許容| N[revalidateTag]
    M -->|即時反映必須| O[updateTag]
    M -->|パス指定| P[revalidatePath]

Server Action選択後のキャッシュ更新:

  • stale許容 → revalidateTagprofile="max"でstale-while-revalidate)
  • 即時反映必須 → updateTag
  • パス指定 → revalidatePath

関連Hooksの使い分け

| Hook | 用途 | 典型例 | |------|------|--------| | useTransition | UIをブロックせず低優先度更新 | 保存中も入力可能にしたい | | useFormStatus | フォーム送信のpending状態取得 | 二重送信防止、スピナー表示 | | useActionState | action結果に基づくstate管理 | バリデーションエラー表示 | | useOptimistic | 楽観的UI更新 | いいねボタンの即時反映 |


Q1. Server ComponentsとClient Componentsの違いは?

環境(server / browser)ごとの「できること」で分ける。

| 用途 | 選択 | |------|------| | state / event handlers / useEffect / browser API | Client Component | | DB/API近接でのデータ取得、秘密情報利用、JS削減、ストリーミング | Server Component |

App Routerではlayouts/pagesはデフォルトでServer Component。Client Componentが必要な場合のみファイル先頭に'use client'を付ける。

補足:重いClient Componentは遅延ロードを検討

重いUI(チャート、エディタ等)は遅延ロードして初期JSを抑える。

import dynamic from 'next/dynamic'

const HeavyChart = dynamic(() => import('./Chart'), { ssr: false })

export function Dashboard() {
  return <HeavyChart />
}

Q2. Server FunctionsとServer Actionsの違いは?

「クライアントからネットワーク経由で呼べるか」で分ける。

Server Function('use server'

サーバーで動く非同期関数で、クライアントからネットワークリクエスト経由で呼べるためasyncである必要がある。use serverをファイル先頭に置けば、そのファイルの全exportがserver側になる。

Server Action(= mutation文脈)

「action / mutation文脈」ではServer FunctionsはServer Actionsとも呼ばれる。<form action={...}><button formAction={...}>に渡すと自動的にその扱いになり、Next.jsのキャッシュ機構と統合され、更新後UIと新データを1回のサーバー往復で返せる。

補足:Server→Clientのデータ受け渡し(シリアライズ / 最小化)

引数・戻り値はシリアライズ可能である必要がある。クライアントに返すデータは「必要最小限」にするとよい(例:画面に使わない列を返さない、機密をそのまま返さない、巨大な構造体を返さない等)。

普通のserver-only関数

クライアントから呼ぶ必要がないユーティリティは「普通のserver-only関数」で十分。import 'server-only'でマークすると、Client Componentがimportした場合にビルドエラーにできる。

Q3. Server Actionsはどこに置く?

「どこから呼ぶか」で制約が変わる。

  • Server Component内でインライン定義:関数先頭に'use server'を置く
  • 別ファイルで定義:ファイル先頭に'use server'を置く

Client Componentsは「モジュール先頭の"use server"」で定義されたactionしかimportできない。つまりClient Componentから使いたい場合は、専用ファイルを作る必要がある。

// actions.ts
'use server'

export async function createPost(formData: FormData) {
  // Server Actionの実装
}

Q4. Route HandlersとServer Actionsの使い分けは?

「HTTP APIとしての入出力が必要か」で分ける。

| ユースケース | 選択 | |-------------|------| | ブラウザから直接fetch('/api/...')したい | Route Handler | | Webhookを受けたい | Route Handler | | 外部クライアントに公開したい | Route Handler | | フォーム送信・ボタン操作などUIからの更新 | Server Action |

Route Handlersはapp/**/route.tsに配置し、Web標準のRequest/Response APIを使う。Server Actionsは裏側でPOSTを使い、Next.jsのキャッシュ/UI更新と統合される。

Q5. useTransitionはいつ使う?

「UIをブロックせずに低優先度の更新を裏で進めたい」とき。

useTransitionは「UIをブロックせずにstateを更新する」ためのReact Hookで、isPendingstartTransitionを返す。

const [isPending, startTransition] = useTransition()

function handleSave() {
  startTransition(async () => {
    await saveData()  // この間もUIは操作可能
  })
}

Server Actionsは慣習的にstartTransitionと一緒に使われ、<form action>経由ならそれが自然に成立する。

Q6. useFormStatusはいつ使う?

「フォーム送信のpending状態」をUIに反映したいとき。

useFormStatusは直近のform submissionのステータス情報を返すHook。

function SubmitButton() {
  const { pending } = useFormStatus()
  return <button disabled={pending}>送信</button>
}

注意:pendingを正しく取るには<form>の中でレンダリングされるコンポーネント内で使う必要がある。

Q7. useActionState / useOptimistic はいつ使う?

useActionState

「form actionが呼ばれたときに更新されるstate」を作りたいとき。

const [state, formAction, isPending] = useActionState(
  async (prevState, formData) => {
    const result = await submitForm(formData)
    return result  // これが次のstateになる
  },
  initialState
)

return <form action={formAction}>...</form>

バリデーションエラーの表示や、サーバーからの応答に基づくUI更新に便利。

useOptimistic

「非同期アクション実行中だけ楽観的に違うstateを見せたい」とき。

const [optimisticLikes, addOptimisticLike] = useOptimistic(
  likes,
  (state, newLike) => [...state, newLike]
)

async function handleLike() {
  addOptimisticLike({ id: 'temp', pending: true })  // 即座にUI反映
  await likePost()  // サーバー処理完了後、実際の値に置き換わる
}

「いいね」ボタンなど、即座にフィードバックを見せたいUIに有効。

Q8. revalidatePath()はいつ使う?

「特定パスのキャッシュをオンデマンドで無効化したい」とき。

revalidatePathは特定pathのcached dataをオンデマンドでinvalidateできる。Server FunctionsとRoute Handlersで呼べるが、Client Componentsでは呼べない。

挙動の違いに注意:

  • Server Functionで呼ぶ:影響パスを見ているときUIが即時更新されうる。「訪問済みページが次回ナビゲーションでrefreshされる」挙動は一時的
  • Route Handlerで呼ぶ:次にそのpathが訪問されたときにrevalidateされる

動的セグメントのパターン指定:

revalidatePath('/posts/[id]', 'page')

Q9. revalidateTagとupdateTagの違いは?

「キャッシュをタグでまとめて制御」し、読み取り一貫性の要件で使い分ける。

| 関数 | 特徴 | ユースケース | |------|------|-------------| | revalidateTag | profile="max"指定でstale-while-revalidate(裏でfreshを取る間staleを返す) | 更新が多少遅れてもよいコンテンツ | | updateTag | staleを返さずfreshを待つ | read-your-own-writes(更新直後に必ず最新を見せたい) |

updateTagはServer Actions内でのみ使える。

tag系APIが効くためには、読み取り側でタグ付けが必要:

  • fetch(... { next: { tags: [...] }})
  • use cache + cacheTag(...)

Q10. serverActions設定はいつ使う?

「CSRF対策(プロキシ経由)」や「送信ボディが大きい」とき。

// next.config.js
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['my-proxy.com'],  // CSRF対策
      bodySizeLimit: '2mb',              // デフォルト1MB
    },
  },
}

セキュリティの仕組み(Origin/Hostチェック)

Next.jsのServer ActionsはOriginヘッダとHostを比較し、一致しない場合はActionを拒否する。裏側ではPOSTのみがactionsをinvokeできる。

Q11. 「action / mutation文脈」って結局なに?

「読み取り(read)ではなく、書き込み(mutation)を起こす操作」の文脈。

  • Server Function:サーバーで動く非同期関数の総称
  • Server Action:その中でも「mutationを起こす文脈」で使われるもの

<form action=...><button formAction=...>で自然に発火し、キャッシュ/UI更新と統合される。

Q12. Cache Components / use cache はいつ使う?

「コンポーネントや関数をキャッシュ可能としてマークしたい」とき(Next.js 16+のopt-in機能)。

// ファイル全体をキャッシュ可能に
'use cache'

export async function getCachedData() {
  // この関数の結果がキャッシュされる
}

Cache ComponentsはNext configでcacheComponentsフラグを有効化して使うopt-in機能。

Q13. Suspense境界はいつ使う?

「部分的に先に表示して、残りを後から差し込みたい」とき(ストリーミング最適化)。

App RouterではSuspenseを前提に「ルートセグメント単位でLoading UIを差し込む」設計が取りやすい。loading.jsはReact Suspenseで意味のあるLoading UIを作り、サーバーからコンテンツがストリーミングされる間に即時のフォールバックを表示できる。

典型例:

  • 上部レイアウト(ヘッダ/ナビ)は即表示
  • 記事本文や一覧など、データ依存部は後からストリームで差し込む
  • 「体感速度」を上げたいページ(ダッシュボード、検索結果、フィードなど)

Q14. React.cache() はいつ使う?

「同一リクエスト内での重複データ取得を避けたい」とき(リクエスト単位の重複排除)。

RSCの文脈で、Reactのcacheは「関数呼び出し結果をメモ化する」。同一リクエスト内で同じ引数の呼び出しが繰り返される場合に、重複計算・重複I/Oを抑える。

import { cache } from 'react'

const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } })
})

「layoutとpageの両方で同じデータを触る」「複数コンポーネントが同じ参照データを読む」などで有効。

Server Actionsの制約

  • mutation向け:データ取得(fetch用途)には推奨されない
  • 直列実行:クライアントは「1つずつ順番にdispatchしてawait」するため並列フェッチには向かない
  • シリアライズ制約:引数・戻り値はシリアライズ可能である必要がある

並列でデータを取りたい場合:

  • Server Componentsでのデータフェッチ
  • 1つのServer Function内でPromise.all

まとめ

  • デフォルトはServer Component、state/handlers/browser APIが必要なときだけClient Component
  • UIからの更新はServer Action、HTTP API公開はRoute Handler
  • Server Actionsはmutation向け、並列データ取得にはServer Componentsを使う
  • キャッシュ制御revalidatePath/revalidateTag、即時反映(read-your-own-writes)ならupdateTag
  • server-onlyにしたいモジュールはimport 'server-only'で漏洩防止
  • フォームUIにはuseFormStatus/useActionState/useOptimisticを活用
  • 重いClient Componentは遅延ロード体感速度はSuspense境界で最適化重複取得はReact.cacheで抑制