Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 152品質スコア 86/100

Next.js App Router & Server Components

Next.js 15でApp Router、Server Components、Client Components、Server Actions、ストリーミングを使用してアプリケーションを構築できます。ページの作成、データ取得の処理、ルートの実装、パフォーマンスの最適化が必要な場面で活用します。

description の原文を見る

Build Next.js 15 applications using App Router, Server Components, Client Components, Server Actions, and streaming. Apply when creating pages, handling data fetching, implementing routes, or optimizing performance.

SKILL.md 本文

Next.js App Router & Server Components

App Router、Server Components、パフォーマンス最適化を使用したシステマティックなNext.js 15開発。

概要

このスキルは以下を強制します:

  • デフォルトではServer Components(必要な場合のみClient Componentsを使用)
  • 適切なデータフェッチングパターン
  • App Routerを使用したファイルベースのルーティング
  • ミューテーション用のServer Actions
  • ストリーミングとSuspenseバウンダリ
  • パフォーマンス最適化
  • ルートハンドラーとミドルウェア

Next.jsページの構築、ルートの実装、またはパフォーマンス最適化の際に適用します。

Server vs Client Components

デフォルト: Server Components(ディレクティブ不要) 必要な場合: Client Components('use client'を追加)

判定フロー

コンポーネントがブラウザAPIを必要とするか?
├─ YES → Client Component ('use client')
└─ NO → Server Component (デフォルト)

コンポーネントがイベントハンドラーを必要とするか?
├─ YES → Client Component
└─ NO → Server Component

コンポーネントがstateまたはeffectsを必要とするか?
├─ YES → Client Component
└─ NO → Server Component

コンポーネントがデータをフェッチするか?
├─ YES → Server Component (推奨)
└─ NO → その他の条件を確認

Server Components(デフォルト)

メリット

  • サーバーのみで実行
  • ブラウザへ送信されるJavaScriptはゼロ
  • データベースへの直接アクセス
  • シークレットへのアクセス
  • SEOに有利
  • 初期ロード高速化

// app/users/page.tsx
// 'use client'がない = Server Component
import { db } from '@/lib/db';

export default async function UsersPage() {
  // 直接データベースクエリ(サーバーのみ)
  const users = await db.user.findMany();

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Server ComponentsでのデータフェッチングServer Components

// ✅ 良い: awaitを使用した非同期コンポーネント
export default async function Page() {
  const data = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 }  // 1時間キャッシュ
  });
  const result = await data.json();

  return <div>{result.title}</div>;
}

// ✅ 良い: 並列データフェッチング
export default async function Page() {
  const [users, posts] = await Promise.all([
    fetch('https://api.example.com/users').then(r => r.json()),
    fetch('https://api.example.com/posts').then(r => r.json())
  ]);

  return (
    <div>
      <Users data={users} />
      <Posts data={posts} />
    </div>
  );
}

// ❌ 悪い: Server ComponentでuseEffectを使用
export default function Page() {
  useEffect(() => {
    // これはServer Componentsで動作しません!
    fetchData();
  }, []);
}

Client Components

いつ使用するか

以下が必要な場合は'use client'を追加します:

  • イベントハンドラー(onClick、onChangeなど)
  • State(useState、useReducer)
  • Effects(useEffect、useLayoutEffect)
  • ブラウザAPI(localStorage、window、navigator)
  • 上記を使用するカスタムフック
  • インタラクティブなUI(モーダル、ドロップダウン、バリデーション付きフォーム)

// app/components/Counter.tsx
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

ServerとClientの組み合わせ

// app/page.tsx (Server Component)
import { Counter } from './components/Counter';  // Client Component

export default async function Page() {
  const data = await fetch('https://api.example.com/stats');
  const stats = await data.json();

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Server-rendered stats: {stats.total}</p>
      
      {/* Server ComponentにリンクされたClient Component */}
      <Counter />
    </div>
  );
}

ファイルベースのルーティング

App Routerの構造

app/
├── layout.tsx          # ルートレイアウト(すべてのページをラップ)
├── page.tsx            # ホームページ (/)
├── about/
│   └── page.tsx        # アバウトページ (/about)
├── blog/
│   ├── page.tsx        # ブログリスト (/blog)
│   └── [slug]/
│       └── page.tsx    # ブログポスト (/blog/post-1)
├── dashboard/
│   ├── layout.tsx      # ネストされたレイアウト
│   ├── page.tsx        # ダッシュボード (/dashboard)
│   └── settings/
│       └── page.tsx    # 設定 (/dashboard/settings)
└── api/
    └── users/
        └── route.ts    # APIルート (/api/users)

レイアウト

ルートレイアウト(必須):

// app/layout.tsx
export default function RootLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <nav>Navigation</nav>
        {children}
        <footer>Footer</footer>
      </body>
    </html>
  );
}

ネストされたレイアウト:

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <aside>Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

動的ルート

// app/blog/[slug]/page.tsx
export default async function BlogPost({
  params
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// ビルド時に静的パラメータを生成
export async function generateStaticParams() {
  const posts = await getAllPosts();

  return posts.map(post => ({
    slug: post.slug
  }));
}

キャッチオールルート

// app/docs/[...slug]/page.tsx
export default function DocsPage({
  params
}: {
  params: { slug: string[] };
}) {
  // /docs/a/b/c → params.slug = ['a', 'b', 'c']
  return <div>Docs: {params.slug.join('/')}</div>;
}

Server Actions

フォームミューテーション

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';

export async function createUser(formData: FormData) {
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;

  // バリデーション
  if (!name || !email) {
    return { error: 'Name and email required' };
  }

  // ユーザー作成
  await db.user.create({
    data: { name, email }
  });

  // キャッシュを再検証
  revalidatePath('/users');

  return { success: true };
}

Server Actionsの使用

// app/users/page.tsx
import { createUser } from './actions';

export default function UsersPage() {
  return (
    <form action={createUser}>
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" />
      <button type="submit">Create User</button>
    </form>
  );
}

Client Componentとの併用

// app/components/UserForm.tsx
'use client';

import { createUser } from '../actions';
import { useFormState } from 'react-dom';

export function UserForm() {
  const [state, formAction] = useFormState(createUser, null);

  return (
    <form action={formAction}>
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" />
      
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">User created!</p>}
      
      <button type="submit">Create User</button>
    </form>
  );
}

ストリーミングとSuspense

SuspenseでのストリーミングStreaming with Suspense

// app/dashboard/page.tsx
import { Suspense } from 'react';

async function SlowComponent() {
  await new Promise(resolve => setTimeout(resolve, 3000));
  return <div>Loaded after 3 seconds</div>;
}

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* 即座にレンダリング */}
      <p>This loads immediately</p>
      
      {/* 準備ができたらストリーム */}
      <Suspense fallback={<div>Loading...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

ローディング状態

// app/dashboard/loading.tsx
export default function Loading() {
  return (
    <div>
      <p>Loading dashboard...</p>
    </div>
  );
}

データフェッチングパターン

キャッシング戦略

// ✅ 良い: キャッシュ(デフォルト)
const data = await fetch('https://api.example.com/data');

// ✅ 良い: 1時間ごとに再検証
const data = await fetch('https://api.example.com/data', {
  next: { revalidate: 3600 }
});

// ✅ 良い: キャッシュなし(常に新鮮)
const data = await fetch('https://api.example.com/data', {
  cache: 'no-store'
});

// ✅ 良い: タグ付きキャッシュ(タグで再検証)
const data = await fetch('https://api.example.com/data', {
  next: { tags: ['users'] }
});

// 特定タグを再検証
import { revalidateTag } from 'next/cache';
revalidateTag('users');

ルートハンドラー(APIルート)

// app/api/users/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const users = await db.user.findMany();
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  
  const user = await db.user.create({
    data: body
  });
  
  return NextResponse.json(user, { status: 201 });
}

動的ルートハンドラー

// app/api/users/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const user = await db.user.findUnique({
    where: { id: params.id }
  });

  if (!user) {
    return NextResponse.json(
      { error: 'User not found' },
      { status: 404 }
    );
  }

  return NextResponse.json(user);
}

ミドルウェア

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 認証を確認
  const token = request.cookies.get('token');

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*']
};

アンチパターン

// ❌ 悪い: Client Componentがデータフェッチング
'use client';
export default function Page() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data').then(r => r.json()).then(setData);
  }, []);
}

// ✅ 良い: Server ComponentがデータをフェッチServer Component fetches data
export default async function Page() {
  const data = await fetch('/api/data').then(r => r.json());
  return <div>{data}</div>;
}

// ❌ 悪い: ページ全体がClient Component
'use client';
export default function Page() {
  return <div>...</div>;
}

// ✅ 良い: インタラクティブな部分のみがClient
export default function Page() {
  return (
    <div>
      <StaticContent />
      <InteractiveButton />  {/* これは'use client' */}
    </div>
  );
}

// ❌ 悪い: Server ComponentをClient Componentに渡す
<ClientComponent>
  <ServerComponent />
</ClientComponent>

// ✅ 良い: childrenプロップとして渡す
<ClientComponent>
  {children}  {/* Server Componentがchildrenとして渡される */}
</ClientComponent>

デプロイ前の確認

  • デフォルトではServer Componentsを使用
  • 必要な場合のみ'use client'を使用
  • Server Componentsでデータをフェッチ
  • ミューテーションにはServer Actionsを使用
  • Suspenseバウンダリでストリーミング
  • キャッシング戦略を設定
  • 認証・リダイレクト用ミドルウェア
  • ローディング状態とエラー状態
  • API用ルートハンドラー
  • 不要なクライアントバンドルがない

プロジェクト標準との統合

Next.jsのベストプラクティスを強制:

  • パフォーマンス最適化(ストリーミング、キャッシング)
  • セキュリティ(Server Componentsはシークレットを隠す)
  • 型安全性(全体を通じてTypeScript)
  • 不要なクライアントJavaScriptがない

リソース


最終更新: 2026年1月24日 互換性: Claude Opus 4.5, Claude Code v2.x ステータス: 本番環境対応

2026年1月アップデート: このスキルはClaude Opus 4.5およびClaude Code v2.xと互換性があります。複雑なタスクの場合は、徹底的な分析のためにeffort: highパラメータを使用してください。

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
ThamJiaHe
リポジトリ
ThamJiaHe/claude-code-handbook
ライセンス
MIT
最終更新
2026/4/19

Source: https://github.com/ThamJiaHe/claude-code-handbook / ライセンス: MIT

本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: ThamJiaHe · ThamJiaHe/claude-code-handbook · ライセンス: MIT