32blogby StudioMitsu

Claude Code × Next.js:AI生成コードのApp Routerミスを防ぐ方法

Claude CodeがやりがちなApp Routerの7大ミス、CLAUDE.mdで防ぐ方法、next-devtools-mcpによるリアルタイムエラーフィードバックまで。

13 min read
目次

Claude Codeなら数秒でNext.jsのページを作れる。問題は、そのページが2023年のパターンで書かれているかもしれないことだ。

AIコーディングツールは、Pages Routerのコードや古いReactパターンを大量に含むデータセットで学習している。Claude CodeにNext.js 16の機能を頼むと、 見た目は正しいがApp Routerの規約に違反するコード を生成することがある。

この記事では、Claude CodeがやりがちなApp Routerの7大ミス、CLAUDE.mdで防ぐ設定方法、そしてNext.js 16内蔵のMCPサーバーによるリアルタイムエラーフィードバックの仕組みを解説する。

CLAUDE.mdApp Routerルール制約AGENTS.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/routeruseRouternext/navigation ではなく)
  • すべてをRoute Handlerで処理(Server Actionsではなく)
  • useEffect でデータ取得(Server Componentの直接アクセスではなく)

CLAUDE.mdは有効だが、それだけでは不十分だ。セッション開始時はルールに従っていても、コンテキストが埋まるにつれて古いパターンに戻ることがある。

本当の解決策は3層の防御だ:

  1. CLAUDE.md ——セッション開始時にClaudeが読む明示的なルール
  2. AGENTS.md ——node_modules 内のNext.jsドキュメントを参照させ、バージョンに正確な情報を提供
  3. next-devtools-mcp ——ビルドエラーや型エラーをリアルタイムでClaudeにフィードバック

AI生成コードで頻出するNext.jsの7大ミス

Vercelの公式ブログ、コミュニティの報告、そして3ヶ月間のClaude Code × Next.js実開発から集めた。

1. Server ComponentからRoute Handlerを呼ぶ

typescript
// 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} />;
}
typescript
// 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" の乱用

typescript
// NG — ページ全体がClient Componentになる
"use client";

export default function DashboardPage() {
  // ここにあるものすべてがブラウザに送られる
}
typescript
// 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バウンダリの配置ミス

tsx
// NG — Suspenseが間違ったコンポーネントをラップしている
export default function Page() {
  return <AsyncDataComponent />;
}

async function AsyncDataComponent() {
  const data = await fetchData();
  return (
    <Suspense fallback={<Loading />}>
      <DataView data={data} />
    </Suspense>
  );
}
tsx
// 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エクスポート

typescript
// NG — Client Componentではmetadataが無視される
"use client";

export const metadata = { title: "Dashboard" };
typescript
// 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が漏れる

typescript
// NG — ミューテーション後にUIが更新されない
"use server";

export async function createPost(formData: FormData) {
  await db.post.create({ data: { title: formData.get("title") as string } });
  // UIは古いデータを表示し続ける
}
typescript
// 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()を使う

typescript
// 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" }; // リダイレクトが発生しない
  }
}
typescript
// 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)

typescript
// NG — Next.js 16では同期アクセスが削除された
import { cookies } from "next/headers";

export default function Page() {
  const cookieStore = cookies(); // Next.js 16ではビルドエラー
}
typescript
// OK — 非同期アクセスが必須
import { cookies } from "next/headers";

export default async function Page() {
  const cookieStore = await cookies();
}

Next.js 16では cookies()headers()paramssearchParams への同期アクセスが完全に削除された。Claude Codeは古い学習データから同期バージョンを頻繁に生成する。


Next.jsプロジェクト用CLAUDE.md設定

上記のミスを防ぐ実用的なCLAUDE.mdテンプレート。

markdown
# 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は node_modules/next/dist/docs/ 内にドキュメントを同梱している。自動生成される AGENTS.md は、AIツールにコード生成前にこのドキュメントを参照するよう指示する。学習データの古さに関係なく、インストール済みバージョンに正確な情報を提供できる。

「古いパターン」ミスを防ぐ最も効果的な方法だ。ClaudeのNext.js APIの記憶に頼るのではなく、インストール済みバージョンの実際のドキュメントを読ませる。


next-devtools-mcp — Next.js 16内蔵のAIブリッジ

Next.js 16には /_next/mcp エンドポイントが内蔵されており、開発サーバーの状態をAIツールに公開する。next-devtools-mcp パッケージがClaude Codeをこのエンドポイントに接続する。

セットアップ

プロジェクトルートの .mcp.json に追加する:

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. セッションのセットアップ

bash
# バックグラウンドで開発サーバーを起動
npm run dev

# Claude Codeを起動
claude

Claude Codeがnext-devtools-mcpに自動接続する。/context を実行してMCPが読み込まれているか確認する。

2. 機能開発サイクル

  1. 欲しい機能を説明する
  2. Claudeがコードを生成——CLAUDE.mdが7大ミスを防止
  3. next-devtools-mcpがエラーをリアルタイムでフィードバック
  4. Claudeがエラーを貼り付けなくても自己修正
  5. 生成されたコードのロジックの正しさをレビュー

3. コミット前の検証

PostToolUse Hookでファイル編集後に型チェックを自動実行する:

json
{
  "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はNext.js開発に非常に強力だ——適切なガードレールを設定すれば。

レイヤー役割セットアップ
CLAUDE.mdApp 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年のパターンを生成しなくなる。

関連記事: