mcpField
N
nextjs-app-router

nextjs-app-router

github.com/claude-dev-suite開発4638 回インストール3 日前に更新
リポジトリ

nextjs-app-router について

Next.js App Router(v13+)開発向けのリファレンススキルです。ルーティング、コンポーネント、データ処理、本番運用の指針を開発者に提供します。

主な機能

  1. 01App Routerのファイル規約
  2. 02サーバー/クライアントコンポーネントの指針
  3. 03データ取得とキャッシュのパターン
  4. 04変更処理のためのServer Actions
  5. 05セキュリティとエラー処理の指針
  6. 06ビルドとパフォーマンスのチェックリスト

活用シーン

  1. 01ルートに使うコンポーネントの選択
  2. 02取得データのキャッシュ動作の計画
  3. 03本番環境のセキュリティ設定
  4. 04App Routerの一般的な問題の診断

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill nextjs-app-router

インストール後、Claude Code で/nextjs-app-routerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

advanced.md
quick-ref/
data-fetching.md
file-conventions.md

詳細

Next.js App Router コア

完全なリファレンス:WebSocket の統合、Socket.IO、Server-Sent Events、TanStack Query によるリアルタイム同期、Vercel/Pusher のパターンについては、advanced.md を参照してください。

詳細な知識:包括的なドキュメントを確認するには、technology に nextjs を指定して mcp__documentation__fetch_docs を使用してください。

ファイル規約

ファイル目的
page.tsxルートの UI(ルートに必須)
layout.tsx共有レイアウト。状態を保持します
loading.tsx読み込み中の UI(Suspense)
error.tsxエラーバウンダリー
not-found.tsx404 ページ
route.tsAPI エンドポイント

コンポーネントの種類

サーバーコンポーネント(デフォルト)
// No 'use client' - runs on server
async function Page() {
  const data = await db.query(...); // Direct DB access
  return <div>{data.name}</div>;
}
クライアントコンポーネント
'use client'
import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

データ取得

// Server Component
async function Page() {
  const data = await fetch('https://api.example.com/data', {
    cache: 'force-cache',     // Static (default)
    // cache: 'no-store',     // Dynamic
    // next: { revalidate: 60 } // ISR
  });
  return <Component data={await data.json()} />;
}

サーバーアクション

'use server'
export async function createItem(formData: FormData) {
  await db.items.create({ name: formData.get('name') });
  revalidatePath('/items');
}

判断基準

シナリオ使用するもの
インタラクティブな UI'use client'
データ取得サーバーコンポーネント
フォームの変更処理サーバーアクション
共有状態クライアントコンポーネント

本番環境への準備

セキュリティ設定
// next.config.js - Security headers
const securityHeaders = [
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-XSS-Protection', value: '1; mode=block' },
  { key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
  {
    key: 'Content-Security-Policy',
    value: `default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';`,
  },
];

module.exports = {
  async headers() {
    return [{ source: '/:path*', headers: securityHeaders }];
  },
};
// Server-side secrets (never exposed to client)
// Use server components or API routes
async function SecureComponent() {
  const apiKey = process.env.API_SECRET_KEY; // Server only
  const data = await fetch(url, { headers: { Authorization: apiKey } });
  return <div>{/* render data */}</div>;
}

// Environment variables
// NEXT_PUBLIC_* = exposed to client (careful!)
// Other vars = server-side only
エラー処理
// app/error.tsx - Error boundary
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // Log to error reporting service
    captureException(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}
// app/global-error.tsx - Root error boundary
'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={reset}>Try again</button>
      </body>
    </html>
  );
}
パフォーマンスの最適化
// Dynamic imports for client components
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <Skeleton />,
  ssr: false, // Client-only if needed
});

// Image optimization
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority // For LCP images
  placeholder="blur"
  blurDataURL={blurHash}
/>

// Font optimization
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
キャッシュ戦略
// Static data (cached indefinitely)
async function StaticPage() {
  const data = await fetch(url, { cache: 'force-cache' });
}

// Dynamic data (never cached)
async function DynamicPage() {
  const data = await fetch(url, { cache: 'no-store' });
}

// ISR (revalidate every 60 seconds)
async function ISRPage() {
  const data = await fetch(url, { next: { revalidate: 60 } });
}

// On-demand revalidation
import { revalidatePath, revalidateTag } from 'next/cache';

async function updateData() {
  'use server';
  await db.update(...);
  revalidatePath('/posts');     // Revalidate specific path
  revalidateTag('posts');       // Revalidate by tag
}
ミドルウェアのセキュリティ
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Rate limiting header for upstream proxy
  const response = NextResponse.next();

  // Auth check
  const token = request.cookies.get('session');
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // CORS for API routes
  if (request.nextUrl.pathname.startsWith('/api')) {
    response.headers.set('Access-Control-Allow-Origin', process.env.ALLOWED_ORIGIN!);
  }

  return response;
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
};
監視メトリクス
メトリクスアラートしきい値
Largest Contentful Paint(LCP)> 2.5s
First Input Delay(FID)> 100ms
Cumulative Layout Shift(CLS)> 0.1
Time to First Byte(TTFB)> 800ms
サーバーコンポーネントのレンダー時間> 200ms
ビルドサイズの増加> 10%
ビルドとデプロイ
// next.config.js - Production optimizations
module.exports = {
  output: 'standalone', // For Docker deployments
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'cdn.example.com' },
    ],
  },
  experimental: {
    // Enable if using PPR
    ppr: true,
  },
};
# Production build with analysis
ANALYZE=true npm run build

# Check bundle size
npx @next/bundle-analyzer
チェックリスト
  • セキュリティヘッダーを設定する
  • CSP ポリシーを定義する
  • NEXT_PUBLIC_* 変数にシークレットを含めない
  • ルートレベルにエラーバウンダリーを設置する
  • グローバルエラーバウンダリーを設置する
  • next/image で画像を最適化する
  • next/font でフォントを最適化する
  • 大きなコンポーネントには動的インポートを使う
  • ルートごとにキャッシュ戦略を設定する
  • 認証/レート制限にミドルウェアを使う
  • Core Web Vitals を監視する
  • コンテナー化にはスタンドアロン出力を使う

このスキルを使用しないケース

このスキルは Next.js App Router(v13+)向けです。次の場合は使用しないでください。

  • Next.js を使わない React:代わりに frontend-react スキルを使用してください
  • Next.js Pages Router(v12 以下):移行方法については KB を参照してください
  • Nuxt.js(Vue メタフレームワーク):代わりに nuxt3 スキルを使用してください
  • Remix(React メタフレームワーク):代わりに remix スキルを使用してください
  • SvelteKit:代わりに sveltekit スキルを使用してください
  • Astro:代わりに astro スキルを使用してください
  • API 専用バックエンド:代わりに nestjs または fastapi を検討してください

アンチパターン

アンチパターン問題である理由正しい方法
あらゆる場所で 'use client' を使うサーバーコンポーネントの利点が失われ、バンドルサイズが増加しますインタラクティブなコンポーネントにのみ 'use client' を使用します
クライアントコンポーネントでデータを取得するウォーターフォールが発生し、SSR がなく、SEO に悪影響がありますサーバーコンポーネントで取得するか、サーバーアクションを使用します
loading.tsx を使わないデータ取得中の UX が低下しますルートレベルの読み込み状態に loading.tsx を作成します
NEXT_PUBLIC_* にシークレットを含めるクライアントに公開され、セキュリティリスクになりますサーバー専用の環境変数またはサーバーコンポーネントを使用します
キャッシュ戦略を無視するパフォーマンスが低下するか、古いデータが使われますcache: 'force-cache'、'no-store'、または revalidate を明示的に設定します
エラーバウンダリーがない捕捉されないエラーによりアプリ全体がクラッシュしますルートレベルとルート直下に error.tsx を追加します
cache オプションなしで fetch() を使うキャッシュの動作が予測できません必ず cache または revalidate 戦略を指定します
データ取得に useEffect を使うクライアント側のみで実行され、SSR がありませんasync/await を使ったサーバーコンポーネントを使用します

すぐに解決できるトラブルシューティング

問題考えられる原因解決策
「Server Component では useState を使用できません」'use client' ディレクティブがないファイルの先頭に 'use client' を追加します
ミューテーション後にデータが更新されないキャッシュが再検証されていないServer Action で revalidatePath() または revalidateTag() を使用します
ハイドレーションの不一致エラーサーバーとクライアントでレンダリング結果が異なるデータに一貫性があることを確認し、Date/ランダム値を確認します
コンポーネント内で「cookies にアクセスできません」と表示されるCookies は Server Components/Actions でのみ使用可能ロジックを Server Component または API ルートに移動します
「Dynamic server usage」でビルドが失敗する静的ルートで動的 API を使用しているexport const dynamic = 'force-dynamic' を追加します
画像が最適化されないnext/image を使用していないREADME の画像 を next/image の README の画像 に置き換えます
ページの読み込みが遅いクライアントバンドルが大きい動的インポートを使用し、バンドルアナライザーを確認します
API ルートで CORS エラーが発生するroute.ts にヘッダーがないルートハンドラーに CORS ヘッダーを追加します

リファレンスドキュメント

コメント