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

nextjs-app-router-fundamentals

Next.js 13以降のApp Routerを使った開発をサポートするガイドです。Pages RouterからApp Routerへの移行、レイアウトの作成、ルーティングの実装、メタデータの管理など、Next.js 13+アプリケーション構築全般で活用できます。App Routerへの移行やルーティングパターンの設計が必要なときに自動で機能します。

description の原文を見る

Guide for working with Next.js App Router (Next.js 13+). Use when migrating from Pages Router to App Router, creating layouts, implementing routing, handling metadata, or building Next.js 13+ applications. Activates for App Router migration, layout creation, routing patterns, or Next.js 13+ development tasks.

SKILL.md 本文

Next.js App Router の基礎

概要

Next.js App Router(Next.js 13+)の包括的なガイドを提供します。Pages Router からの移行、ファイルベースのルーティング規約、レイアウト、メタデータ処理、および最新の Next.js パターンをカバーしています。

TypeScript: 決して any 型を使用しないこと

重要なルール: このコードベースでは @typescript-eslint/no-explicit-any が有効です。any を使用するとビルドが失敗します。

❌ 間違い:

function handleSubmit(e: any) { ... }
const data: any[] = [];

✅ 正解:

function handleSubmit(e: React.FormEvent<HTMLFormElement>) { ... }
const data: string[] = [];

Next.js の一般的な型パターン

// Page プロップ
function Page({ params }: { params: { slug: string } }) { ... }
function Page({ searchParams }: { searchParams: { [key: string]: string | string[] | undefined } }) { ... }

// フォームイベント
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { ... }
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { ... }

// Server actions
async function myAction(formData: FormData) { ... }

このスキルを使用する場合

以下の場合にこのスキルを使用してください:

  • Pages Router(pages/ ディレクトリ)から App Router(app/ ディレクトリ)に移行する
  • Next.js 13+ アプリケーションをゼロから構築する
  • レイアウト、テンプレート、ネストされたルーティングを使用する
  • メタデータと SEO 最適化を実装する
  • App Router ルーティング規約でビルドする
  • ルートグループ、並列ルート、またはルートインターセプトの基本を処理する

コアコンセプト

App Router vs Pages Router

Pages Router(レガシー - Next.js 12 以前):

pages/
├── index.tsx              # ルート: /
├── about.tsx              # ルート: /about
├── _app.tsx               # カスタム App コンポーネント
├── _document.tsx          # カスタム Document コンポーネント
└── api/                   # API ルート
    └── hello.ts           # API エンドポイント: /api/hello

App Router(最新 - Next.js 13+):

app/
├── layout.tsx             # ルートレイアウト(必須)
├── page.tsx               # ルート: /
├── about/                 # ルート: /about
│   └── page.tsx
├── blog/
│   ├── layout.tsx         # ネストされたレイアウト
│   └── [slug]/
│       └── page.tsx       # 動的ルート: /blog/:slug
└── api/                   # ルートハンドラー
    └── hello/
        └── route.ts       # API エンドポイント: /api/hello

ファイル規約

App Router の特別なファイル:

  • layout.tsx - セグメントとその子要素のための共有 UI(状態を保持し、再レンダリングしない)
  • page.tsx - ルートのための一意の UI、ルートを公開アクセス可能にする
  • loading.tsx - React Suspense を使用した読み込み UI
  • error.tsx - エラーバウンダリーを使用したエラー UI
  • not-found.tsx - 404 UI
  • template.tsx - レイアウトに似ていますが、ナビゲーション時に再レンダリングする
  • route.ts - API エンドポイント(ルートハンドラー)

共存(Colocation):

  • コンポーネント、テスト、その他のファイルは app/ に共存できます
  • page.tsxroute.ts ファイルのみが公開ルートを作成します
  • その他のファイル(コンポーネント、ユーティリティ、テスト)はルーティング可能ではありません

移行ガイド:Pages Router から App Router へ

ステップ 1:現在の構造を理解する

既存の Pages Router セットアップを確認します:

  • pages/ ディレクトリ構造を読む
  • _app.tsx - グローバル状態、レイアウト、プロバイダーを処理する
  • _document.tsx - HTML 構造をカスタマイズする
  • メタデータの使用を確認する(next/head<Head> コンポーネント)
  • すべてのルートと動的セグメントをリストアップする

ステップ 2:ルートレイアウトを作成する

app/layout.tsx を作成します - すべての App Router アプリケーションで必須

// app/layout.tsx
export const metadata = {
  title: 'My App',
  description: 'App description',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

移行に関する注釈:

  • _document.tsx の HTML 構造を layout.tsx に移動する
  • _app.tsx のグローバルプロバイダー/ラッパーを layout.tsx に移動する
  • <Head> メタデータを metadata のエクスポートに変換する
  • ルートレイアウトは 必ず <html><body> タグを含める必要があります

ステップ 3:ページをルートに移行する

シンプルなページ移行:

// 移行前: pages/index.tsx
import Head from 'next/head';

export default function Home() {
  return (
    <>
      <Head>
        <title>Home Page</title>
      </Head>
      <main>
        <h1>Welcome</h1>
      </main>
    </>
  );
}
// 移行後: app/page.tsx
export default function Home() {
  return (
    <main>
      <h1>Welcome</h1>
    </main>
  );
}

// メタデータを layout.tsx またはここにエクスポートする
export const metadata = {
  title: 'Home Page',
};

ネストされたルート移行:

// 移行前: pages/blog/[slug].tsx
export default function BlogPost() { ... }
// 移行後: app/blog/[slug]/page.tsx
export default function BlogPost() { ... }

ステップ 4:ナビゲーションを更新する

アンカータグを Next.js Link に置き換えます:

// 移行前(App Router では不適切)
<a href="/about">About</a>

// 移行後(正解)
import Link from 'next/link';
<Link href="/about">About</Link>

ステップ 5:Pages ディレクトリをクリーンアップする

移行後:

  • pages/ ディレクトリからすべてのページファイルを削除する
  • API ルートをまだ移行していない場合は pages/api/ を保持する
  • _app.tsx_document.tsx を削除する(機能はレイアウトに移動)
  • 必要に応じて空の pages/ ディレクトリを削除する

メタデータ処理

静的メタデータ

// app/page.tsx または app/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description',
  keywords: ['nextjs', 'react'],
  openGraph: {
    title: 'My Page',
    description: 'Page description',
    images: ['/og-image.jpg'],
  },
};

動的メタデータ

// app/blog/[slug]/page.tsx
export async function generateMetadata({
  params
}: {
  params: { slug: string }
}): Promise<Metadata> {
  const post = await getPost(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
  };
}

レイアウトとネスト

ネストされたレイアウトを作成する

// app/layout.tsx - ルートレイアウト
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  );
}

// app/blog/layout.tsx - ブログレイアウト
export default function BlogLayout({ children }) {
  return (
    <div>
      <BlogSidebar />
      <main>{children}</main>
    </div>
  );
}

レイアウトの動作:

  • レイアウトはナビゲーション全体で状態を保持します
  • レイアウトはルート変更時に再レンダリングされません
  • 親レイアウトは子レイアウトをラップします
  • ルートレイアウトは必須でアプリ全体をラップします

ルーティングパターン

動的ルート

// app/blog/[slug]/page.tsx
export default function BlogPost({
  params
}: {
  params: { slug: string }
}) {
  return <article>Post: {params.slug}</article>;
}

キャッチオールルート

// app/shop/[...slug]/page.tsx - /shop/a、/shop/a/b 等にマッチ
export default function Shop({
  params
}: {
  params: { slug: string[] }
}) {
  return <div>Path: {params.slug.join('/')}</div>;
}

オプションキャッチオール

// app/shop/[[...slug]]/page.tsx - /shop および /shop/a、/shop/a/b にマッチ

ルートグループ

URL に影響を与えずにルートをグループ化します:

app/
├── (marketing)/
│   ├── about/
│   │   └── page.tsx      # /about
│   └── contact/
│       └── page.tsx      # /contact
└── (shop)/
    └── products/
        └── page.tsx      # /products

一般的な移行の落とし穴

落とし穴 1:ルートレイアウトの HTML タグを忘れる

間違い:

export default function RootLayout({ children }) {
  return <div>{children}</div>; // <html> と <body> がない
}

正解:

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

落とし穴 2:App Router で next/head を使用する

間違い:

import Head from 'next/head';

export default function Page() {
  return (
    <>
      <Head><title>Title</title></Head>
      <main>Content</main>
    </>
  );
}

正解:

export const metadata = { title: 'Title' };

export default function Page() {
  return <main>Content</main>;
}

落とし穴 3:Pages ディレクトリを削除しない

ルートを移行した後、古い pages/ ディレクトリのファイルを削除して混乱を避けます。競合するルートがある場合、ビルドが失敗します。

落とし穴 4:page.tsx ファイルがない

page.tsx ファイルがないとルートはアクセス可能ではありません。レイアウトだけではルートを作成しません。

app/
├── blog/
│   ├── layout.tsx   # ルートではない
│   └── page.tsx     # これが /blog をアクセス可能にする

落とし穴 5:Link の使用が正しくない

間違い:

<a href="/about">About</a>  // 機能しますがページ全体がリロードされます

正解:

import Link from 'next/link';
<Link href="/about">About</Link>  // クライアント側のナビゲーション

Server Components vs Client Components

デフォルト:Server Components

app/ 内のすべてのコンポーネントはデフォルトで Server Components です:

// app/page.tsx - Server Component(デフォルト)
export default async function Page() {
  const data = await fetch('https://api.example.com/data');
  const json = await data.json();

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

メリット:

  • async/await を直接使用できます
  • データベース/API への直接アクセス
  • クライアント側の JavaScript がゼロ
  • 自動コード分割

Client Components

以下が必要な場合に 'use client' ディレクティブを使用します:

  • インタラクティブな要素(onClick、onChange など)
  • React フック(useState、useEffect、useContext など)
  • ブラウザ API(window、localStorage など)
  • イベントリスナー
// app/components/Counter.tsx
'use client';

import { useState } from 'react';

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

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

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

Server Component データフェッチング

// app/posts/page.tsx
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 } // 1 時間ごとに再検証
  });
  return res.json();
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

並列データフェッチング

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

  return (/* レンダリング */);
}

generateStaticParams を使用した静的サイト生成

概要

generateStaticParams は Pages Router の getStaticPaths に相当する App Router 版です。動的ルートのページをビルド時に生成します。

基本的な使用法

// app/blog/[id]/page.tsx
export async function generateStaticParams() {
  // 事前レンダリングするパラメータの配列を返す
  return [
    { id: '1' },
    { id: '2' },
    { id: '3' },
  ];
}

export default function BlogPost({
  params
}: {
  params: { id: string }
}) {
  return <article>Blog post {params.id}</article>;
}

重要なポイント:

  • ルートパラメータキーを持つオブジェクトの配列を返す
  • 各オブジェクトはビルド時に事前レンダリングされる 1 つのページを表す
  • 関数をエクスポートし、generateStaticParams という名前である必要があります
  • Server Components でのみ機能します('use client' ディレクティブなし)
  • Pages Router の getStaticPaths に置き換えます

静的パラメータ用データをフェッチする

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());

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

export default async function BlogPost({
  params
}: {
  params: { slug: string }
}) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json());

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

複数の動的セグメント

// app/products/[category]/[id]/page.tsx
export async function generateStaticParams() {
  const categories = await getCategories();

  const params = [];
  for (const category of categories) {
    const products = await getProducts(category.slug);
    for (const product of products) {
      params.push({
        category: category.slug,
        id: product.id,
      });
    }
  }

  return params;
}

export default function ProductPage({
  params
}: {
  params: { category: string; id: string }
}) {
  return <div>Category: {params.category}, Product: {params.id}</div>;
}

動的動作構成

// app/blog/[id]/page.tsx
export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }];
}

// 事前レンダリングされていないパスの動作を制御
export const dynamicParams = true; // デフォルト - ランタイム生成を許可
// export const dynamicParams = false; // 事前レンダリングされていないパスは 404 を返す

export default function BlogPost({
  params
}: {
  params: { id: string }
}) {
  return <article>Post {params.id}</article>;
}

オプション:

  • dynamicParams = true(デフォルト):事前レンダリングされていないパスはオンデマンドで生成される
  • dynamicParams = false:事前レンダリングされていないパスは 404 を返す

一般的なパターン

パターン 1:シンプルな ID ベースのルート

export async function generateStaticParams() {
  return [
    { id: '1' },
    { id: '2' },
    { id: '3' },
  ];
}

パターン 2:API からフェッチ

export async function generateStaticParams() {
  const items = await fetch('https://api.example.com/items').then(r => r.json());
  return items.map(item => ({ id: item.id }));
}

パターン 3:データベースクエリ

export async function generateStaticParams() {
  const posts = await db.post.findMany();
  return posts.map(post => ({ slug: post.slug }));
}

Pages Router からの移行

移行前(Pages Router):

// pages/blog/[id].tsx
export async function getStaticPaths() {
  return {
    paths: [
      { params: { id: '1' } },
      { params: { id: '2' } },
    ],
    fallback: false,
  };
}

export async function getStaticProps({ params }) {
  return { props: { id: params.id } };
}

移行後(App Router):

// app/blog/[id]/page.tsx
export async function generateStaticParams() {
  return [
    { id: '1' },
    { id: '2' },
  ];
}

export const dynamicParams = false; // fallback: false と同等

export default function BlogPost({ params }: { params: { id: string } }) {
  return <div>Post {params.id}</div>;
}

よくある間違いを避ける

❌ 間違い:'use client' を使用する

'use client'; // エラー!generateStaticParams は Server Components でのみ機能します

export async function generateStaticParams() {
  return [{ id: '1' }];
}

❌ 間違い:Pages Router パターンを使用する

export async function getStaticPaths() { // 間違った API です!
  return { paths: [...], fallback: false };
}

❌ 間違い:export キーワードを忘れる

async function generateStaticParams() { // エクスポートが必須です!
  return [{ id: '1' }];
}

✅ 正解:クリーンな Server Component

// app/blog/[id]/page.tsx
// 'use client' ディレクティブなし

export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }];
}

export default function Page({ params }: { params: { id: string } }) {
  return <div>Post {params.id}</div>;
}

重要な実装上の注意:

generateStaticParams を「書く」または「実装する」よう求められた場合:

  • する Edit または Write ツールを使用して実際のファイルを変更する
  • する 既存の page.tsx ファイルに関数を追加する
  • する generateStaticParams に関する TODO コメントを削除する
  • しない Markdown でコードを出力するだけではなく、実装する
  • しない ファイルに書き込まずにコードを表示する

テストと検証

App Router で移行またはビルドする場合、以下を確認してください:

  1. 構造:

    • app/ ディレクトリが存在する
    • ルート layout.tsx<html><body> を持つ
    • 各ルートが page.tsx ファイルを持つ
  2. メタデータ:

    • App Router で next/head をインポートしていない
    • ページまたはレイアウトからメタデータをエクスポートしている
    • メタデータが Metadata 型で正しく型付けされている
  3. ナビゲーション:

    • next/linkLink コンポーネントを使用している
    • 内部ナビゲーションにプレーンな <a> タグを使用していない
  4. クリーンアップ:

    • pages/ ディレクトリに残りのページファイルがない
    • _app.tsx_document.tsx が削除されている
    • 古いメタデータパターンが削除されている

クイックリファレンス

ファイル構造マッピング

Pages RouterApp Router目的
pages/index.tsxapp/page.tsxホームルート
pages/about.tsxapp/about/page.tsxAbout ルート
pages/[id].tsxapp/[id]/page.tsx動的ルート
pages/_app.tsxapp/layout.tsxグローバルレイアウト
pages/_document.tsxapp/layout.tsxHTML 構造
pages/api/hello.tsapp/api/hello/route.tsAPI ルート

一般的なコマンド

# App Router を使用して新しい Next.js アプリを作成
npx create-next-app@latest my-app

# 開発サーバーを実行
npm run dev

# 本番用にビルド
npm run build

# 本番サーバーを開始
npm start

追加リソース

より高度なルーティングパターン(並列ルート、ルートインターセプト、ルートハンドラー)の詳細については、nextjs-advanced-routing スキルを参照してください。

Server コンポーネント vs Client コンポーネントのベストプラクティスおよびアンチパターンについては、nextjs-server-client-components および nextjs-anti-patterns スキルを参照してください。

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

詳細情報

作者
wsimmonds
リポジトリ
wsimmonds/claude-nextjs-skills
ライセンス
MIT
最終更新
不明

Source: https://github.com/wsimmonds/claude-nextjs-skills / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

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