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 を使用した読み込み UIerror.tsx- エラーバウンダリーを使用したエラー UInot-found.tsx- 404 UItemplate.tsx- レイアウトに似ていますが、ナビゲーション時に再レンダリングするroute.ts- API エンドポイント(ルートハンドラー)
共存(Colocation):
- コンポーネント、テスト、その他のファイルは
app/に共存できます page.tsxとroute.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 で移行またはビルドする場合、以下を確認してください:
-
構造:
app/ディレクトリが存在する- ルート
layout.tsxが<html>と<body>を持つ - 各ルートが
page.tsxファイルを持つ
-
メタデータ:
- App Router で
next/headをインポートしていない - ページまたはレイアウトからメタデータをエクスポートしている
- メタデータが
Metadata型で正しく型付けされている
- App Router で
-
ナビゲーション:
next/linkのLinkコンポーネントを使用している- 内部ナビゲーションにプレーンな
<a>タグを使用していない
-
クリーンアップ:
pages/ディレクトリに残りのページファイルがない_app.tsxと_document.tsxが削除されている- 古いメタデータパターンが削除されている
クイックリファレンス
ファイル構造マッピング
| Pages Router | App Router | 目的 |
|---|---|---|
pages/index.tsx | app/page.tsx | ホームルート |
pages/about.tsx | app/about/page.tsx | About ルート |
pages/[id].tsx | app/[id]/page.tsx | 動的ルート |
pages/_app.tsx | app/layout.tsx | グローバルレイアウト |
pages/_document.tsx | app/layout.tsx | HTML 構造 |
pages/api/hello.ts | app/api/hello/route.ts | API ルート |
一般的なコマンド
# 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
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/wsimmonds/claude-nextjs-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。