AI生成コードのApp Routerミスを防ぐには、CLAUDE.mdルール・AGENTS.mdによるバージョン正確なドキュメント参照・next-devtools-mcpのリアルタイムエラー検知の3層防御が有効だ。
Claude Code × Next.js 16(App Router)の組み合わせで開発すると、特有のハマりパターンがある。ページファイルに "use client" を付ける、Server ComponentからRoute Handlerを呼ぶ、Next.js 15では動いていた同期 cookies() が16で壊れる——よくある落とし穴だ。この記事で紹介する3層セットアップを組めば、これらのミスはほぼゼロにできる。
この記事では、Claude Codeがやりがちな7大ミス、CLAUDE.mdで防ぐ方法、そしてnext-devtools-mcpによるリアルタイムエラーフィードバックの仕組みを解説する。
なぜClaude CodeはApp Routerを間違えるのか
根本的な原因はシンプルだ。 Claudeの学習データにはApp RouterよりPages Routerのコードが圧倒的に多い。 Pages Routerは7年間(2016〜2023)デフォルトだった。App Routerが安定版になったのはNext.js 13.4(2023年5月)——わずか3年前だ。
つまり、Claude Codeの「デフォルトの癖」は以下のようなパターンに傾く:
getServerSideProps(async Server Componentsではなく)next/routerのuseRouter(next/navigationではなく)- すべてをRoute Handlerで処理(Server Actionsではなく)
useEffectでデータ取得(Server Componentの直接アクセスではなく)
CLAUDE.mdは有効だが、それだけでは不十分だ。セッション開始時はルールに従っていても、コンテキストが埋まるにつれて古いパターンに戻ることがある。
本当の解決策は3層の防御だ:
- CLAUDE.md ——セッション開始時にClaudeが読む明示的なルール
- AGENTS.md ——
node_modules内のNext.jsドキュメントを参照させ、バージョンに正確な情報を提供 - next-devtools-mcp ——ビルドエラーや型エラーをリアルタイムでClaudeにフィードバック
AI生成コードで頻出するNext.jsの7大ミス
Vercelの公式ブログ、コミュニティの報告、そして32blogでの3ヶ月間のClaude Code × Next.js実開発から集めた。
1. Server ComponentからRoute Handlerを呼ぶ
// NG — 不要なネットワークラウンドトリップ
// app/users/page.tsx (Server Component)
export default async function UsersPage() {
const res = await fetch("http://localhost:3000/api/users");
const users = await res.json();
return <UserList users={users} />;
}
// OK — Server Componentで直接データベースにアクセス
// app/users/page.tsx (Server Component)
import { db } from "@/lib/db";
export default async function UsersPage() {
const users = await db.user.findMany();
return <UserList users={users} />;
}
Server Componentはサーバー上で実行される。データベース、ファイルシステム、内部APIに直接アクセスできる。自分自身のRoute Handlerを呼ぶのは、自分にHTTPリクエストを投げる無駄な処理だ。
2. "use client" の乱用
// NG — ページ全体がClient Componentになる
"use client";
export default function DashboardPage() {
// ここにあるものすべてがブラウザに送られる
}
// OK — インタラクティブな末端だけをClient Componentにする
// app/dashboard/page.tsx (Server Component — ディレクティブなし)
import { InteractiveChart } from "./interactive-chart";
export default async function DashboardPage() {
const data = await fetchDashboardData();
return (
<div>
<h1>Dashboard</h1>
<StaticSummary data={data} />
<InteractiveChart data={data} />
</div>
);
}
"use client" は最小のインタラクティブな末端コンポーネントにだけ付ける。Claude Codeはインタラクティブ要素が少しでもあると、ページレベルのファイルに付けがちだ。
3. Suspenseバウンダリの配置ミス
// NG — Suspenseが間違ったコンポーネントをラップしている
export default function Page() {
return <AsyncDataComponent />;
}
async function AsyncDataComponent() {
const data = await fetchData();
return (
<Suspense fallback={<Loading />}>
<DataView data={data} />
</Suspense>
);
}
// OK — Suspenseは非同期コンポーネントを外側からラップする
export default function Page() {
return (
<Suspense fallback={<Loading />}>
<AsyncDataComponent />
</Suspense>
);
}
async function AsyncDataComponent() {
const data = await fetchData();
return <DataView data={data} />;
}
Suspenseは非同期処理を トリガーする コンポーネントを、親レベルからラップしなければならない。非同期コンポーネントの内側に置いても意味がない。
4. Client Componentでのmetadataエクスポート
// NG — Client Componentではmetadataが無視される
"use client";
export const metadata = { title: "Dashboard" };
// OK — metadataはServer Component(page.tsxまたはlayout.tsx)に書く
// app/dashboard/page.tsx ("use client" なし)
export const metadata = { title: "Dashboard" };
metadata エクスポートはServer Componentでのみ機能する。Claudeが "use client" をページファイルに追加すると、metadataはエラーメッセージなしで静かに動かなくなる。
5. Server Actionsでrevalidationが漏れる
// NG — ミューテーション後にUIが更新されない
"use server";
export async function createPost(formData: FormData) {
await db.post.create({ data: { title: formData.get("title") as string } });
// UIは古いデータを表示し続ける
}
// OK — 影響するパスを再検証する
"use server";
import { revalidatePath } from "next/cache";
export async function createPost(formData: FormData) {
await db.post.create({ data: { title: formData.get("title") as string } });
revalidatePath("/posts");
}
6. try/catch内でredirect()を使う
// NG — redirectは内部でthrowするので、try/catchに捕まる
"use server";
export async function loginAction(formData: FormData) {
try {
await authenticate(formData);
redirect("/dashboard"); // NEXT_REDIRECTがthrowされ、下のcatchに捕まる
} catch (error) {
return { error: "Login failed" }; // リダイレクトが発生しない
}
}
// OK — redirectをtry/catchの外に出す
"use server";
export async function loginAction(formData: FormData) {
let success = false;
try {
await authenticate(formData);
success = true;
} catch (error) {
return { error: "Login failed" };
}
if (success) redirect("/dashboard");
}
redirect() は内部で NEXT_REDIRECT という特殊なエラーをthrowする仕組みだ。try/catch内にあると、リダイレクトが飲み込まれてしまう。
7. Request APIへの同期アクセス(Next.js 16)
// NG — Next.js 16では同期アクセスが削除された
import { cookies } from "next/headers";
export default function Page() {
const cookieStore = cookies(); // Next.js 16ではビルドエラー
}
// OK — 非同期アクセスが必須
import { cookies } from "next/headers";
export default async function Page() {
const cookieStore = await cookies();
}
Next.js 16では cookies()、headers()、params、searchParams への同期アクセスが完全に削除された。Next.js 15では非推奨警告だったが、16ではビルドエラーになる。Claude Codeは古い学習データから同期バージョンを頻繁に生成する。
Next.js 15から16への移行では、この問題にハマるケースが多い。codemod(npx @next/codemod@latest next-async-request-api .)で大半は対応できるが、Claude Codeが同期パターンを再生成し続けるのでCLAUDE.mdにルールを明記するまで終わらない。
Next.jsプロジェクト用CLAUDE.md設定
上記のミスを防ぐ実用的なCLAUDE.mdテンプレート。CLAUDE.md設計の詳細は「Claude Codeコンテキスト管理:CLAUDE.md設計パターン」も参照してほしい。
# Project: Next.js 16 App Router
## Tech Stack
- Next.js 16.1 + TypeScript strict + Tailwind CSS v4
- App Router only. No Pages Router patterns
- React Server Components by default
## Critical Rules
- NEVER add "use client" to page.tsx or layout.tsx unless the entire page must be interactive
- NEVER call Route Handlers from Server Components — access data directly
- ALWAYS use async/await for cookies(), headers(), params, searchParams
- ALWAYS place Suspense boundaries OUTSIDE the async component
- ALWAYS call revalidatePath() or revalidateTag() in Server Actions after mutations
- NEVER put redirect() inside try/catch blocks
## Data Fetching
- Server Components: direct database/API access (no fetch to own Route Handlers)
- Client Components: Server Actions for mutations, Route Handlers only for third-party webhooks
- Cache: use the "use cache" directive for explicit caching
## Reference
- Read AGENTS.md for bundled Next.js docs location
- When unsure about an API, check node_modules/next/dist/docs/ first
AGENTS.mdとバンドルドキュメント
Next.js 16.2から、完全なNext.jsドキュメントがMarkdownファイルとして node_modules/next/dist/docs/ に同梱されるようになった。自動生成される AGENTS.md は、AIツールにコード生成前にこのドキュメントを参照するよう指示する。それ以前のバージョンでは npx @next/codemod@latest agents-md で生成できる。
「古いパターン」ミスを防ぐ最も効果的な方法だ。ClaudeのNext.js APIの記憶に頼るのではなく、インストール済みバージョンの実際のドキュメントを読ませる。Vercelの内部テストでは、バンドルドキュメント方式でNext.js評価の合格率が100%になったそうだ(オンデマンド取得では79%)。
next-devtools-mcp — Next.js 16内蔵のAIブリッジ
Next.js 16には /_next/mcp エンドポイントが内蔵されており、開発サーバーの状態をAIツールに公開する。next-devtools-mcpパッケージがClaude Codeをこのエンドポイントに接続する。
セットアップ
プロジェクトルートの .mcp.json に追加する:
{
"mcpServers": {
"next-devtools": {
"command": "npx",
"args": ["-y", "next-devtools-mcp@latest"]
}
}
}
開発サーバー(npm run dev)を起動すれば、Claude Codeが自動的に接続する。
提供されるツール
| ツール | 機能 |
|---|---|
get_errors | ビルドエラー、ランタイムエラー、型エラーをリアルタイムで返す |
get_logs | 開発サーバーのコンソール出力にアクセス |
get_page_metadata | ルート構造、コンポーネントタイプ、ページごとのレンダリングモード |
get_project_metadata | プロジェクト設定、Next.jsバージョン、開発サーバーURL |
get_server_action_by_id | 特定のServer Actionのソースファイルを特定 |
なぜ重要なのか
MCPがなければ、フィードバックループは「Claude がコードを書く → あなたがビルドエラーに気づく → エラーを貼り付ける → Claudeが修正する」だ。next-devtools-mcpがあれば、Claudeがエラーを 即座に 検知し、あなたが気づく前に自己修正できる。
上記の7大ミスに対してとくに効果的だ。Suspenseの配置ミスは目に見えるエラーを出さないこともあるが、get_page_metadata で予期しないレンダリング動作として表面化する。
Claude Code × Next.jsの実践ワークフロー
日常的なNext.js開発でClaude Codeを使うワークフロー。
1. セッションのセットアップ
# バックグラウンドで開発サーバーを起動
npm run dev
# Claude Codeを起動
claude
Claude Codeがnext-devtools-mcpに自動接続する。/context を実行してMCPが読み込まれているか確認しよう。スラッシュコマンドに慣れていない場合は「Claude Codeコマンド チートシート」を参照。
2. 機能開発サイクル
- 欲しい機能を説明する
- Claudeがコードを生成——CLAUDE.mdが7大ミスを防止
- next-devtools-mcpがエラーをリアルタイムでフィードバック
- Claudeがエラーを貼り付けなくても自己修正
- 生成されたコードのロジックの正しさをレビュー
3. コミット前の検証
PostToolUse Hookでファイル編集後に型チェックを自動実行する:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit --pretty 2>&1 | head -20"
}
]
}
]
}
}
4. コンテキスト管理
- 1つの機能に長時間取り組むとき →
/compact - 別の機能に切り替えるとき →
/clear - 複数セッションにまたがる作業 → session-handoff.md
コンテキスト管理の詳細は「Claude Codeが忘れる原因と対策 完全ガイド」を参照。コンテキストウィンドウが溢れる問題には「Claude Codeのコンテキストウィンドウ超過を解決する方法」が参考になる。
よくある質問
Claude CodeはPages Routerでも使える?
もちろん使える。この記事の問題はApp Router特有のものだ。Pages Routerのプロジェクトなら、Claudeの学習データがPages Routerに偏っている分、むしろデフォルトの挙動が正確になる。
3層(CLAUDE.md、AGENTS.md、MCP)全部必要?
それぞれ別の問題を捕まえる。CLAUDE.mdは既知のミスを予防し、AGENTS.mdはバージョン精度を保証し、MCPはClaude自身が予測できないランタイムエラーを検知する。CLAUDE.mdだけから始めて段階的に追加するのもアリだが、僕の経験では3つ全部揃ったときに生産性が一段上がった。
next-devtools-mcpは他のAIコーディングツールでも使える?
使える。/_next/mcp エンドポイントはオープンなModel Context Protocolを使っているので、MCP対応ツールなら何でも接続できる。Cursor、Windsurf、GitHub Copilot(MCP対応版)でも同じセットアップが使える。
next-devtools-mcpのトークン消費はどれくらい?
MCPツール呼び出しはコンテキストトークンを消費する。実際のところ get_errors はコンパクトなJSONを返すので、1回あたり200〜500トークン程度だ。ビルドエラーを手動で貼り付けるよりずっと少ない。/context で使用量を監視して、不要なMCPサーバーは切断しよう。
コンテキストが埋まるとCLAUDE.mdのルールは無視される?
起こりうる。CLAUDE.mdはセッション開始時に読み込まれるが、コンテキストが膨らむと初期の指示の影響力が下がる。だからこそ3層アプローチが重要で、CLAUDE.mdのルールが薄れてもAGENTS.mdとMCPが独立したガードレールとして機能する。定期的に /compact でコンテキストを圧縮するのも効果的だ。
Next.js 15でもこの設定は使える?
CLAUDE.mdのルールはミス#7(非同期Request API)を除いてNext.js 15にも適用できる。15では非推奨警告だけだったから。node_modules/next/dist/docs/ のバンドルドキュメントはNext.js 16.2+の機能だが、AGENTS.mdはNext.jsのcodemodで手動生成できる。
CLAUDE.mdとAGENTS.mdの違いは?
CLAUDE.mdはClaude Code専用で、プロジェクトルール・規約・制約を書く。セッション開始時にClaude Codeが読む。AGENTS.mdはツール非依存で、どのAIコーディングツールにもリファレンスドキュメントの場所を教える。Next.jsプロジェクトではAGENTS.mdがバンドルドキュメントを指し、CLAUDE.mdが「"use client" をページファイルに付けるな」のような行動ルールを定義する。
まとめ
Claude CodeはNext.js開発に非常に強力だ——適切なガードレールを設定すれば。
| レイヤー | 役割 | セットアップ |
|---|---|---|
| CLAUDE.md | App Routerの7大ミスを防止 | 5分 |
| AGENTS.md | バージョン正確なNext.jsドキュメントでClaudeを根拠づける | create-next-appで自動 |
| next-devtools-mcp | リアルタイムエラーフィードバックループ | 2分 |
| Hooks | 編集後に型チェックを自動実行 | 5分 |
パターンはシンプルだ。CLAUDE.mdで制約し、バンドルドキュメントで根拠を与え、MCPで検証する。この3層を正しく設定すれば、Claude Codeは2026年のコードベースに2023年のパターンを生成しなくなる。
関連記事: