claude/skills/nextjs-app-router-guide/SKILL.md
Next.js App RouterでのServer Components / Server Actions / Route Handlers等の使い分けガイド。「Server ComponentとClient Componentの違い」「Server Actionsの使い方」「Server FunctionとServer Actionの違い」「revalidatePath/revalidateTag/updateTagの違い」「use cacheの使い方」「Client側のデータ取得パターン」「useTransition/useFormStatus/useActionState/useOptimistic」などの質問に回答する際に参照する。
npx skillsauth add lilpacy/dotfiles nextjs-app-router-guideInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
flowchart TD
A[何を実現したい?] --> B{UIを描画する?}
B -->|Yes| C{state / handlers / useEffect / browser API が必要?}
C -->|Yes| D["Client Component<br/>(use client)"]
C -->|No| E["Server Component"]
E --> E2{データ取得が必要?}
E2 -->|Yes| E3["Server Componentで fetch / DB / ORM"]
E2 -->|No| E4["そのまま描画"]
D --> D2{データ取得の起点は?}
D2 -->|初回表示 / SSRで足りる| E3
D2 -->|Clientで継続取得したい| D3["use() + ServerからPromiseを渡す<br/>または SWR / React Query"]
B -->|No<br/>関数 / ロジック| F{HTTP endpointとして公開したい?}
F -->|Yes| G["Route Handler<br/>(public endpoint / webhook / any content type)"]
F -->|No| H{Clientから呼ぶ?}
H -->|No| I["server-only関数 / DAL<br/>(import 'server-only')"]
H -->|Yes| J{主目的はmutation?}
J -->|Yes| K["Server Function<br/>(mutation文脈では Server Action)"]
J -->|No| L{小さな対話的read?}
L -->|Yes| M["Server Functionも可<br/>ただし第一選択ではない"]
L -->|No| N["Server Component / Client data fetching / Route Handlerを再検討"]
K --> O{更新後の反映は?}
O -->|現在の画面を再取得| P["refresh()"]
O -->|特定pathを無効化| Q["revalidatePath()"]
O -->|stale-while-revalidate| R["revalidateTag(tag, 'max')"]
O -->|read-your-own-writes| S["updateTag()"]
補足:
client -> use serverのreadはできるが、現行docsの軸は「データ取得はServer Component、mutationはServer Function」。そのためuse serverのreadは可能な選択肢であって、デフォルト推奨ではない。
| Hook | 用途 | 典型例 |
|------|------|--------|
| useTransition | UIをブロックせず非同期処理をTransitionとして流す | onClickからServer Functionを呼ぶ |
| useFormStatus | フォーム送信のpending状態取得 | 二重送信防止、スピナー表示 |
| useActionState | Action結果に基づくstate管理 | バリデーションエラー表示 |
| useOptimistic | 楽観的UI更新 | いいねボタンの即時反映 |
環境(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'を付ける。
重いUI(チャート、エディタ等)は遅延ロードして初期JSを抑える。ssr: falseはClient Componentでのみ使えるので、呼び出し側もClient Componentにしておく。
'use client'
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('./Chart'), { ssr: false })
export function DashboardChart() {
return <HeavyChart />
}
'use server'で定義されるのがServer Functionで、そのうちmutation文脈で使う呼び名がServer Action。
'use server')Server Functionは「サーバーで動く非同期関数」で、Clientからnetwork request経由で呼べる。そのためasyncである必要がある。use serverディレクティブは、ファイル先頭に置けばそのファイルのexportをserver-sideとして扱い、関数内に置けばその関数だけをServer Functionとして扱える。
Server Functionは、action / mutation contextではServer Actionとも呼ばれる。<form action={...}>や<button formAction={...}>に渡すと、その呼び出しは自動的にTransition上で扱われ、更新後のUIと新データを1回のserver roundtripで返せる。
Server FunctionはClientから呼べるが、現行docsでは「Server Functions are designed for server-side mutations」と明記されている。Clientは現状、Server Functionを1つずつ順番にdispatchしてawaitするため、Client発のreadをこれで統一するのは第一選択ではない。
readはまず次を優先すると整理しやすい:
use()Server Functionの引数・戻り値はシリアライズ可能である必要がある。return valueはserializeされてClientへ送られるので、UIに必要な最小限だけ返すのがよい。巨大な構造体や内部フィールドつきのDB recordをそのまま返さないようにする。
Clientから呼ぶ必要がないDBアクセスや認可ロジックは、use serverのaction本体に全部書くよりも、**Data Access Layer(DAL)**として切り出すと管理しやすい。import 'server-only'でマークすると、Client側からimportされたときにbuild errorにできる。
「どこから呼ぶか」で制約が変わる。
'use server'を置く'use server'を置くClient ComponentからServer Functionを使うときは、モジュール先頭に'use server'を書いた専用ファイルに置く。Client Component内でServer Functionを直接定義することはできない。
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
// Server Function / Server Action の実装
}
「HTTP APIとしての入出力が必要か」で分ける。
| ユースケース | 選択 | |-------------|------| | ブラウザや他クライアントからHTTPで叩きたい | Route Handler | | Webhook / callback URLを受けたい | Route Handler | | JSON / XML / file / textなど、UI以外のレスポンスを返したい | Route Handler | | フォーム送信・ボタン操作など、UIからの更新 | Server Function / Server Action |
Route Handlerは、Web標準のRequest / Response APIを使うcustom request handlerで、public HTTP endpointとして任意のclientからアクセスできる。
一方、Server Actionは「UIからのmutation」に向く。ただし、「API公開しない = 安全」ではない。Server Function / Server Actionはapplication UI経由だけでなくdirect POSTでも到達可能なので、各action内で認証・認可を必ず検証する。
できる。ただし、まずは第一選択かどうかを見直す。
整理すると次の順で考えるとブレにくい:
use()use serverのServer Functionも可最後の4番も成立するが、現行docsはServer Functionsをserver-side mutations向けと位置づけており、Clientからは現状1件ずつ順次処理される。そのため、「Clientから呼ぶreadだからuse serverが第一選択」ではない、というのが最新の整理。
「UIをブロックせずに、手動のイベントから非同期処理をTransitionとして流したい」とき。
useTransitionはUIの一部をbackgroundでrenderできるReact Hookで、isPendingとstartTransitionを返す。<form action>やformActionはもともとTransitionの文脈に乗るが、onClickやuseEffectからServer Functionを手動で呼ぶときはstartTransitionが役立つ。
'use client'
import { useTransition } from 'react'
import { savePost } from '@/app/actions'
export function SaveButton() {
const [isPending, startTransition] = useTransition()
return (
<button
disabled={isPending}
onClick={() => {
startTransition(async () => {
await savePost()
})
}}
>
{isPending ? '保存中...' : '保存'}
</button>
)
}
「フォーム送信のpending状態」をUIに反映したいとき。
useFormStatusは直近のform submissionのstatus informationを返すHook。
'use client'
import { useFormStatus } from 'react-dom'
function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>送信</button>
}
注意: useFormStatusは<form>の中でrenderされるcomponentから呼ぶ必要があり、親<form>のstatusだけを返す。同じcomponent内でrenderした<form>自身のstatusは追えない。
「Actionの結果に基づくstate」を作りたいとき。
useActionStateは、Actionの結果のためのstateを作るHookで、現在state・dispatch関数・pendingを返す。
'use client'
import { useActionState } from 'react'
const initialState = { message: '' }
export function SignupForm() {
const [state, formAction, isPending] = useActionState(
async (_prevState, formData: FormData) => {
const email = formData.get('email')
if (!email) return { message: 'メールアドレスは必須です' }
return { message: '送信しました' }
},
initialState
)
return (
<form action={formAction}>
<input name="email" type="email" />
<button disabled={isPending}>送信</button>
<p>{state.message}</p>
</form>
)
}
バリデーションエラー表示や、Server Actionの結果に応じたUI更新に便利。
「非同期Actionの最中だけ、楽観的なstateを見せたい」とき。
useOptimisticは楽観的にUIを更新するHook。setterはAction / Transitionの中で呼ぶのがポイント。
'use client'
import { useOptimistic, useState, useTransition } from 'react'
import { likePost } from '@/app/actions'
export function LikeButton({ initialLikes }: { initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes)
const [isPending, startTransition] = useTransition()
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(current, delta: number) => current + delta
)
function handleLike() {
startTransition(async () => {
addOptimisticLike(1)
const nextLikes = await likePost()
setLikes(nextLikes)
})
}
return (
<button onClick={handleLike} disabled={isPending}>
{optimisticLikes}
</button>
)
}
「いいね」ボタンやカート追加など、即座にフィードバックを見せたいUIに有効。
「特定pathのcacheをon-demandで無効化したい」とき。
revalidatePathは特定pathのcacheをon-demandでinvalidateできる。Server FunctionsとRoute Handlersで呼べるが、Client Componentsでは呼べない。
挙動の違いに注意:
動的segmentのpattern指定:
revalidatePath('/posts/[id]', 'page')
「tagでまとめてcacheを制御」し、読み取り一貫性の要件で使い分ける。
| 関数 | 特徴 | ユースケース |
|------|------|-------------|
| revalidateTag | revalidateTag(tag, 'max')が推奨。stale-while-revalidateで、次回訪問時にstaleを返しつつ裏でfresh取得 | 更新が多少遅れてもよいコンテンツ |
| updateTag | staleを返さず、次requestでfreshを待つ | read-your-own-writes |
updateTagはServer Actions内でのみ使える点に注意。Route Handlerなど他のcontextではrevalidateTagを使う。
現行docsでは、revalidateTagの第2引数なしはdeprecated。blockingな更新が必要ならupdateTagへ寄せるのが分かりやすい。
tag系APIを効かせるには、読み取り側でtagを付けておく必要がある(例: fetch(..., { next: { tags: ['posts'] } })、または'use cache' + cacheTag('posts')など)。
「CSRF対策(proxy / 別origin経由)」や「送信bodyが大きい」とき。
Server Actions自体はNext.js 14でstableだが、設定オプションは現行docsでもexperimental.serverActions配下にある。
// next.config.js
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
bodySizeLimit: '2mb',
},
},
}
allowedOriginsはOriginとHostを比較するCSRF対策に追加originを許可する設定。bodySizeLimitのdefaultは1MBで、大きいpayloadを送るなら調整できる。
X-Forwarded-Host)の比較による追加のCSRF対策がある「readではなく、書き込みや副作用を起こす操作」の文脈。
use serverで定義されるサーバー関数の広い呼び名<form action> / formAction / startTransitionなどのaction文脈で使うもの<form action=...>や<button formAction=...>は、自動的にTransitionと統合される。
「route / component / functionをcache可能として扱いたい」とき。
use cacheはCache Components featureのdirectiveで、使うにはnext.config.tsでcacheComponents: trueを有効化する。
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
use cacheはファイル / component / async functionの先頭に置ける。まずは関数単位で使うと理解しやすい。
export async function getCachedData() {
'use cache'
return db.user.findMany()
}
「部分的に先に表示して、残りを後から差し込みたい」とき。
App Routerでは、loading.jsで意味のあるloading UIを作り、route segmentのcontentがstreamingされる間にinstant loading stateを表示できる。
典型例:
loading.jsは同じfolderのpage.jsと子孫を自動で<Suspense>境界に包む。さらに細かく制御したい場合は、自前の<Suspense>境界も追加できる。
「同一request内の重複取得を避けたい」とき。
Reactのcacheはdata fetchやcomputationの結果をmemoizeするAPIで、React Server Componentsでのみ使う。
import { cache } from 'react'
const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } })
})
「layoutとpageが同じdataに触る」「複数componentが同じ参照dataを読む」などで役立つ。cacheはserver requestをまたいで永続化されず、server requestをまたぐとinvalidateされる。
Promise.allなどを使うuseEffect / browser APIが必要なときだけClient Componentuse serverのServer FunctionはClientからも呼べるが、設計の主眼はmutationclient -> use server のreadは可能だが第一選択ではないrefresh / revalidatePath / revalidateTag(tag, 'max') / updateTagを要件で使い分けるimport 'server-only'で守るuseFormStatus / useActionState / useOptimisticを活用loading.js、重複取得はReact.cacheで抑制development
Use when searching the web or reading online documentation. Prefer DuckDuckGo for search and read documents through npx curl.md instead of raw HTML.
testing
Use when writing or editing tests. Tests should be ordered by near-normal, normal, then abnormal cases where applicable, and test names must be Japanese behavior descriptions from a reviewer/user perspective.
development
GoF/オブジェクト指向デザインパターンを関数型プログラミング(pure functions, higher-order functions, ADT, composition, immutability, effect boundaries)でシンプルに整理・設計・リファクタリングする。Strategy/Factory/Adapter/ObserverなどGoF全23パターンのFP置き換え、適用判断、具体事例を提示する必要があるときに使う。
tools
Use when committing, pushing, or preparing PRs. Defines the user's commit workflow, message style discovery, review handoff, and branch/worktree push requirements.