web-meta-framework-nextjs について
Next.js App Router を使う開発者向けの実装パターンガイドです。ルーティング、コンポーネント境界、キャッシュ、Server Actionsを扱い、バージョンごとの差異も説明します。Pages Routerは対象外です。
主な機能
- 01Server・Client コンポーネントの境界
- 02ルーティング、レイアウト、動的セグメント
- 03Server Actionsとデータ更新
- 04キャッシュ無効化と再検証
- 05ストリーミングとメタデータ設定
活用シーン
- 01app/ のルートとレイアウト設計
- 02コンポーネントの実行場所の選択
- 03フォーム操作とキャッシュ更新の実装
- 04ストリーミング表示とSEOメタデータの追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-meta-framework-nextjsインストール後、Claude Code で/web-meta-framework-nextjsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/cookies.mdcore.mdevent-handlers.mdmetadata.mdmutations.mdoptimistic.mdparallel-routes.mdrevalidation.mdroute-groups.mdserver-actions.mdserver-components.mdstreaming.mdversions.md詳細
Next.js App Router のパターン
クイックガイド: Server Components がデフォルトであり、
"use client"はオプトインなので、クライアントコンポーネントは小さく保ち、ツリーの末端に配置してください。読み取りは Server Components で行い、書き込みは Server Actions で行います。Server Actions は公開 HTTP エンドポイントであり、キャッシュも自身で再検証します。以下の回答は、2つのバージョンに関する事実によって変わります。15以降では、params、searchParams、cookies()、headers()は Promise です。16では、middleware.tsがproxy.tsに変わり、revalidateTag()はcacheLifeプロファイルを受け取るようになり、experimental_pprはcacheComponentsに変わりました。
詳細なリソース:
- examples/core.md — ファイル規約、動的ルート、レイアウト、エラー UI と not-found UI、サーバー専用コード
- examples/server-components.md — Server/Client 境界、コンポジション、Suspense によるストリーミング
- examples/metadata.md — タイトルテンプレート、
generateMetadata、Open Graph と Twitter カード - examples/parallel-routes.md —
@slotフォルダー、インターセプトルート、モーダルパターン - examples/route-groups.md — URL を変更せずにセクションごとにレイアウトを設定
- examples/versions.md — PPR、Turbopack、型付きルート、
after()、インストルメンテーション、v16 への移行 - examples/server-actions.md — アクションの定義、フォームアクション、検証、認可、保留状態
- examples/mutations.md — 独立したアクションをまとめて実行
- examples/revalidation.md — パスとタグによる無効化の違い、fetch にタグを付ける方法
- examples/optimistic.md — 即時フィードバックを実現する
useOptimistic - examples/event-handlers.md —
useTransitionを使ってクリックハンドラーからアクションを呼び出す - examples/streaming.md — 長時間実行されるアクションからの進捗状況の更新
- examples/cookies.md — アクション内での Cookie の読み取りと書き込み
- reference.md — 判断ツリー、ルートセグメント設定、フックのシグネチャ、バージョンごとの変更点
該当するパス
- Next.js 16 を使用している場合 — キャッシュはオプトインで、いくつかの API 名が変更されています。キャッシュ、ミドルウェア、または並列ルートのパターンをコピーする前に、examples/versions.md の移行に関する後半部分を読んでください。
- Pages Router(
pages/)を使用している場合 — 異なる規約が適用され、上記のファイルはいずれも Pages Router について説明していません。
<critical_requirements>
Next.js コードを書く前に
コンポーネントは Server Components として記述し、状態、エフェクト、イベントハンドラーが必要な場合にのみ "use client" を追加してください。 このディレクティブによってコードをブラウザーに送るかどうかが決まるため、末端に限定すればバンドルを小さく保ち、シークレットをサーバー上に留められます。
シークレットに触れるモジュールでは server-only をインポートしてください。 そうすればキーがブラウザーに送られるのではなく、問題のあるインポートでビルドが失敗します。
データ量の多いルートには loading.tsx を用意し、独立したセクションにはそれぞれ独自の <Suspense> を設けてください。
loading.tsx はページを自動的に境界で囲みます。セクションごとに境界を設けることで、最も遅いフェッチが最も速いフェッチをブロックするのを防げます。
SEO は metadata オブジェクトまたは generateMetadata で設定してください。 Next.js はこれらのタグを重複排除して順序付けますが、手動の <head> マークアップではそうなりません。
Server Actions には 'use server' を付けてください。ファイルの先頭、または非同期関数の先頭に記述します。 付けない場合、その関数はクライアント向けにバンドルされます。
すべての Server Action で入力を検証し、認可を確認してください。 アクションは ID でアクセスできる公開 HTTP エンドポイントです。そのため、アクションで確認するまでは、呼び出し元の身元もペイロードの形式も検証されていません。
変更後、redirect() を呼び出す前に revalidatePath() または revalidateTag() を呼び出してください。 次のレンダーが読み取るのはキャッシュであり、redirect() は例外をスローするため、その後の処理は実行されません。
params、searchParams、cookies()、headers() は await してください。 15 以降では、これら4つはすべて Promise です。
</critical_requirements>
自動検出: page.tsx、layout.tsx、loading.tsx、error.tsx、not-found.tsx、default.tsx、 template.tsx、"use client"、"use server"、"use cache"、App Router、Server Components、Client Components、generateMetadata、generateStaticParams、parallel routes、intercepting routes、 revalidatePath、revalidateTag、updateTag、useActionState、useFormStatus、useOptimistic、next/form、 next/cache、next/navigation、cacheComponents、cacheLife、cacheTag、PPR、Turbopack、typedRoutes、 instrumentation.ts、proxy.ts、after()
対象範囲:
- レイアウト、読み込み状態、エラーバウンダリーを備えた
app/ディレクトリによるアプリのルーティング - サーバーで実行するコンポーネントと、ブラウザーに送るコンポーネントの判断
- Suspense によるストリーミングと段階的なレンダリング
- Metadata API による SEO と、
generateStaticParamsによる静的生成 - 共有可能な URL を使ったモーダルを含む、並列ルートとインターセプティングルート
- Server Actions によるデータの作成、更新、削除と、その後のキャッシュの無効化
別途扱う内容:
- 永続化 — アクションはデータレイヤーを呼び出します。クライアントやクエリーの形式はここでは定めません
- 認証とセッションの取得 — アクションは呼び出し元を確認し、その結果に基づいて処理します
- スキーマ検証 — アクションはスキーマを使って
FormDataを解析します。どのスキーマライブラリーを使うかは別途選択します - コンポーネントのスタイリング — コンポーネントは
classNameを受け取り、スタイリングの方法は別途決めます - 単一のコンポーネントツリーを超えて保持されるクライアント状態 — Client Components はデータを props として受け取ります
## 設計思想
App Router は React のデフォルトを逆転させます。コンポーネントは明示しない限りサーバーで実行され、クライアント側の JavaScript はオプトインです。これにより、コンポーネントが自身のデータを待機できるため、クライアントとサーバー間のフェッチのウォーターフォールがなくなります。また、バンドルのサイズはページが実際に持つインタラクティブ性に応じたものになります。
次の4つの結果が、ここで紹介するすべてのパターンの基礎となります。
- ストリーミング — 最も遅いクエリが完了するまで待つのではなく、HTML は利用可能になり次第ブラウザーに届きます
- コロケーション — データ取得、メタデータ、UI が同じセグメントに配置されます
- ネストされたレイアウト — レイアウトはセグメント内のナビゲーションをまたいで維持されるため、その状態と DOM は保持されます
- サーバーサイドのミューテーション — サーバーアクションはサーバーで実行され、同じラウンドトリップでキャッシュを再検証するため、UI とデータが同時に更新されます
サーバーアクションは、フォームの送信や UI と連動するミューテーションに適しています。ルートハンドラーは、外部 API の利用者、Webhook、並行して実行する必要がある処理に適しています。クライアントから呼び出されたアクションはキューに追加されます。
useActionState、useFormStatus、useOptimistic は Next.js API ではなく React 19 のフックです。useActionState は Canary 時代の ReactDOM.useFormState に代わるものです。
コアパターン
パターン 1:サーバー/クライアント境界
サーバーコンポーネントでデータを取得して結果を渡し、クライアントコンポーネントはインタラクションだけを担当します。
// app/dashboard/page.tsx — a Server Component, no directive needed
export default async function DashboardPage() {
const user = await getUser(); // runs on the server, ships no JavaScript
return <UserProfile user={user} />; // Client Component, receives data as props
}
クライアントコンポーネントは、サーバーコンポーネントを内部でインポートするのではなく、children 経由で受け取る限り、それらをラップできます。
コード全文:examples/server-components.md
パターン 2:loading.tsx と Suspense によるストリーミング
loading.tsx はルート全体を対象とします。セクションごとに <Suspense> を配置すると、それぞれを個別に表示できます。
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart /> {/* slow query — streams in independently */}
</Suspense>
<Suspense fallback={<InvoicesSkeleton />}>
<LatestInvoices />
</Suspense>
コード全文:examples/server-components.md
パターン 3:ルートファイルの規約
フォルダーがルートを定義し、特別なファイルがそのセグメントと配下すべての動作を定義します。
app/dashboard/
├── layout.tsx persists across navigations within the segment
├── page.tsx the route itself — without it the URL is not reachable
├── loading.tsx wraps page.tsx in a Suspense boundary automatically
├── error.tsx "use client"; receives { error, reset }
└── not-found.tsx rendered when the segment calls notFound()
コード全文:examples/core.md。ファイル一覧の全体は reference.md にあります。
パターン 4:動的セグメント
角括弧の深さによって、単一セグメント、複数セグメント、または任意でなしを選択します。
// app/users/[id]/page.tsx → /users/1 params: { id: string }
// app/docs/[...slug]/page.tsx → /docs/a/b params: { slug: string[] }
// app/shop/[[...slug]]/page.tsx → /shop, /shop/a params: { slug?: string[] }
export default async function UserPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
}
ビルド時に既知の値を事前レンダリングするには、generateStaticParams と組み合わせます。
コード全文:examples/core.md
パターン 5:ルートグループ、並列ルート、インターセプト
URL を変更せずにレイアウトとレンダリングを変更する、3つのフォルダー規約です。
app/
├── (marketing)/layout.tsx route group — one layout per section, no URL segment
├── dashboard/@analytics/ parallel slot — arrives as a prop on dashboard/layout.tsx
├── @modal/(.)photo/[id]/ intercepts /photo/[id] on soft navigation only
└── @modal/default.tsx what a slot renders when nothing matches
各スロットには default.tsx が必要です。Next.js 16 では、これがないとビルドに失敗します。
コード全文:examples/route-groups.md および examples/parallel-routes.md
パターン 6:メタデータ
値が既知の場合は静的オブジェクトをエクスポートし、ルート自身のデータから値を取得する場合は関数をエクスポートします。
export const metadata: Metadata = {
title: { template: "%s | Acme", default: "Acme" },
};
// or, when the title depends on the route's data:
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const post = await getPost((await params).slug); // memoised with the page's own fetch
return {
title: post.title,
openGraph: { title: post.title, type: "article" },
};
}
コード全文:examples/metadata.md
パターン 7:サーバーアクションの定義と呼び出し
ファイルレベルの 'use server' を指定すると、すべてのエクスポートがアクションとしてマークされ、クライアントコンポーネントからインポートできるようになります。
// app/actions.ts
"use server";
export async function createPost(formData: FormData) {
/* ... */
}
// app/posts/new/page.tsx — a plain form, so it submits without JavaScript
<form action={createPost}>...</form>;
action.bind(null, id) は FormData 以外の引数を渡し、拡張機能がなくてもフォームを動作させます。フォーム以外の操作で呼び出す場合は、クリックで UI がブロックされないよう、startTransition 内でアクションを呼び出します。
コード全文:examples/server-actions.md および examples/event-handlers.md
パターン 8:アクション内での検証と認可
攻撃者が回避できない唯一のコードパスがアクション本体であるため、両方のチェックをそこに記述します。
"use server";
export async function deletePost(postId: string) {
const user = await getCurrentUser();
if (!user) throw new Error("Unauthorized");
const post = await getPost(postId);
if (post?.authorId !== user.id) throw new Error("Forbidden");
await deletePostRecord(postId);
revalidatePath("/posts");
}
検証エラーはスローせずに返してください。スローするとエラーバウンダリーに到達し、フォームがクリアされます。
コード全文:examples/server-actions.md
### パターン 9:再検証してからリダイレクトrevalidatePath は既知のルートを対象にし、revalidateTag はデータが表示される場所を問わず対象にします。
"use server";
export async function createPost(formData: FormData) {
// ...mutate
revalidatePath("/posts"); // v16: revalidateTag("posts", "max")
redirect("/posts"); // throws — nothing below this line runs
}
コード全文:examples/revalidation.md
パターン 10:保留中の状態と楽観的な状態
React 19 の 3 つのフックで、待機中の状態を扱えます。フォームの状態を管理するもの、入れ子になった送信ボタンの状態を管理するもの、結果が反映される前に表示するものです。
const [state, formAction, isPending] = useActionState(createPost, initialState);
// useFormStatus() reads the same pending flag, but only from a component *inside* the form
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(state, content: string) => [
...state,
{ id: `temp-${Date.now()}`, content, pending: true },
],
);
コード全文:examples/optimistic.md および examples/server-actions.md
<red_flags>
注意すべき点
実行時に問題が発生します:
- アクションファイルに
'use server'がない場合、関数はクライアント用にバンドルされ、インポートしたものや、それを通じて参照できるシークレットも一緒に含まれます - アクション内に認可チェックがない場合、アクション ID は特定できるため、誰でも呼び出せます
- アクション内に入力値の検証がない場合、認証されていない呼び出し元から届く
FormDataは、解析も型付けもされていません try/catchの中でredirect()を呼び出すと、例外を投げて処理を伝えるため、catchによってナビゲーションが抑止されますparams、searchParams、cookies()、headers()をawaitせずに読み取ると問題が発生します。15 以降、これら 4 つはすべて Promise です"use client"がないerror.tsxでは、resetコールバックにクライアント境界が必要なため問題が発生します"use client"モジュール内でシークレットを読み取ると、その値はブラウザーのバンドルに含まれます- Next.js 16 では、
default.tsxのない並列ルートスロットがあると、ビルドに失敗します - Next.js 16 では、引数が 1 つの
revalidateTag("tag")は使えません。cacheLifeプロファイルが必須になりました
意外な動作:
- フォームをレンダリングするコンポーネント内で
useFormStatus()を呼び出すと、pending: falseのまま変わりません。このフックは、内包しているフォームの状態だけを読み取ります - ミューテーション後に
revalidatePath/revalidateTagを呼び出さないと、UI にはミューテーション前のキャッシュが表示され続けますが、エラーはどこにも表示されません - クライアントから並列で呼び出された Server Actions はキューに入り、1 つずつ実行されます
- 検証に失敗したときに例外を投げると、ユーザーが入力したフォームの内容が消去されます。代わりにエラーを返してください
page.tsxに"use client"を指定すると、サブツリー全体がクライアント側になり、これは有効なものの、すべてのサーバーサイドレンダリングの利点が何も通知されないまま失われます- 15 以降、動的レンダリングでは
fetchはデフォルトでキャッシュされず、GET Route Handler もキャッシュされません - アクション内で Cookie を設定または削除すると、サーバー側で再レンダリングが発生します
その他の注意点、アンチパターンの全一覧、バージョンごとの変更点については、 reference.md を参照してください。
</red_flags>