nextjs-code-review について
Next.js App Router のコードを対象に、コンポーネント境界、データ取得、キャッシュ、セキュリティ、本番対応を確認するレビュー用スキルです。重要度別の指摘に、修正例と優先順位を添えて提示します。
主な機能
- 01サーバー・クライアントコンポーネント境界の確認
- 02データ取得とキャッシュ戦略のレビュー
- 03Server Actions の検証とセキュリティ評価
- 04ミドルウェア、ルートハンドラー、メタデータの確認
- 05重要度別の指摘とコード例の提示
活用シーン
- 01マージ前のページ、レイアウト、ルートセグメントのレビュー
- 02Server Actions の認可と入力検証の確認
- 03キャッシュとオンデマンド再検証の評価
- 04移行や機能追加後の App Router コード確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill nextjs-code-reviewインストール後、Claude Code で/nextjs-code-reviewを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/app-router-patterns.mdperformance.mdserver-components.md詳細
Next.js コードレビュー
概要
Next.js App Router のコードを、Server Components、Client Components、Server Actions、キャッシュ戦略、および本番環境への対応基準に照らして評価します。重要度別に分類した、具体的なコード例を含む実行可能な指摘を提示します。アーキテクチャ分析は typescript-software-architect-review エージェントに委任します。
使用する場面
- マージ前に Next.js のページ、レイアウト、ルートセグメントをレビューするとき
- Server Component と Client Component の境界を検証するとき
- Server Actions のセキュリティと正確性を確認するとき
- データ取得のパターン(fetch、cache、再検証)をレビューするとき
- キャッシュ戦略(静的生成、ISR、動的レンダリング)を評価するとき
- ミドルウェアの実装(認証、リダイレクト、リライト)を評価するとき
- API ルートハンドラーがリクエストとレスポンスを適切に処理しているかレビューするとき
- SEO のためにメタデータ設定を検証するとき
- ローディング、エラー、Not Found ページの実装を確認するとき
- Next.js の新機能を実装した後、または Pages Router から移行した後
手順
-
対象範囲を特定する:レビュー対象となる Next.js のルートセグメントとコンポーネントを特定します。
globを使ってpage.tsx、layout.tsx、loading.tsx、error.tsx、route.ts、middleware.tsファイルを探します。 -
コンポーネントの境界を分析する:Server Component と Client Component が適切に分離されているか確認します。
'use client'が必要な箇所だけに、コンポーネントツリーのできるだけ深い位置に配置されているか確認します。Server Components がクライアント専用モジュールをインポートしていないことを確認します。 -
データ取得をレビューする:fetch のパターンを検証します。
cacheとrevalidateオプションが適切か、Promise.allを使ってデータを並行取得しているか、リクエストのウォーターフォールを避けているか確認します。サーバーサイドのデータ取得で機密データがクライアントに公開されていないことを確認します。 -
キャッシュ戦略を評価する:静的レンダリングと動的レンダリングの判断をレビューします。静的生成での
generateStaticParamsの使用、オンデマンド再検証のためのrevalidatePath/revalidateTag、API ルートに適切なキャッシュヘッダーが設定されているか確認します。 -
Server Actions を評価する:フォームアクションのバリデーション(クライアント側とサーバー側の両方)、エラー処理、
useOptimisticによる楽観的更新、セキュリティ(アクションが認可なしに機密性の高い操作を公開していないこと)をレビューします。 -
ミドルウェアを確認する:リクエストのマッチング、認証/認可ロジック、レスポンスの変更、パフォーマンスへの影響についてミドルウェアをレビューします。必要なルートでのみ実行されることを確認します。
-
メタデータと SEO をレビューする:
generateMetadata関数、Open Graph タグ、構造化データ、robots.txt、sitemap.xmlの設定を確認します。コンテンツが変動するページで、動的メタデータが適切に実装されていることを確認します。 -
指摘事項を検証する:最終化する前に、実際のコードを確認して各問題を検証します。パターン違反が存在することを確認し、提案する修正がコードベースに適用できることを確かめ、誤検知を除外します。
-
レビュー報告書を作成する:重要度(Critical、Warning、Suggestion)別の指摘事項、良い点、コード例を含む優先順位付きの推奨事項をまとめた構造化レポートを作成します。
例
例 1:Server/Client Component の境界
// ❌ Bad: Entire page marked as client when only a button needs interactivity
'use client';
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetch(`/api/products/${params.id}`);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<button onClick={() => addToCart(product.id)}>Add to Cart</button>
</div>
);
}
// ✅ Good: Server Component with isolated Client Component
// app/products/[id]/page.tsx (Server Component)
import { AddToCartButton } from './add-to-cart-button';
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await getProduct(id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<AddToCartButton productId={product.id} />
</div>
);
}
// app/products/[id]/add-to-cart-button.tsx (Client Component)
'use client';
export function AddToCartButton({ productId }: { productId: string }) {
return <button onClick={() => addToCart(productId)}>Add to Cart</button>;
}
例 2:データ取得のパターン
// ❌ Bad: Sequential data fetching creates waterfall
export default async function DashboardPage() {
const user = await getUser();
const orders = await getOrders(user.id);
const analytics = await getAnalytics(user.id);
return <Dashboard user={user} orders={orders} analytics={analytics} />;
}
// ✅ Good: Parallel data fetching with proper Suspense boundaries
export default async function DashboardPage() {
const user = await getUser();
const [orders, analytics] = await Promise.all([
getOrders(user.id),
getAnalytics(user.id),
]);
return <Dashboard user={user} orders={orders} analytics={analytics} />;
}
// ✅ Even better: Streaming with Suspense for independent sections
export default async function DashboardPage() {
const user = await getUser();
return (
<div>
<UserHeader user={user} />
<Suspense fallback={<OrdersSkeleton />}>
<OrdersSection userId={user.id} />
</Suspense>
<Suspense fallback={<AnalyticsSkeleton />}>
<AnalyticsSection userId={user.id} />
</Suspense>
</div>
);
}
例 3:Server Actions のセキュリティ
// ❌ Bad: Server Action without validation or authorization
'use server';
export async function deleteUser(id: string) {
await db.user.delete({ where: { id } });
}
// ✅ Good: Server Action with validation, authorization, and error handling
'use server';
import { z } from 'zod';
import { auth } from '@/lib/auth';
import { revalidatePath } from 'next/cache';
const deleteUserSchema = z.object({ id: z.string().uuid() });
export async function deleteUser(rawData: { id: string }) {
const session = await auth();
if (!session || session.user.role !== 'admin') {
throw new Error('Unauthorized');
}
const { id } = deleteUserSchema.parse(rawData);
await db.user.delete({ where: { id } });
revalidatePath('/admin/users');
}
例 4:キャッシュと再検証
// ❌ Bad: No cache control, fetches on every request
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return <PostList posts={posts} />;
}
// ✅ Good: Explicit caching with time-based revalidation
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600, tags: ['blog-posts'] },
}).then(r => r.json());
return <PostList posts={posts} />;
}
// Revalidation in Server Action
'use server';
export async function publishPost(data: FormData) {
await db.post.create({ data: parseFormData(data) });
revalidateTag('blog-posts');
}
例 5:ミドルウェアのレビュー
// ❌ Bad: Middleware runs on all routes including static assets
import { NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('session');
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Missing config.matcher
// ✅ Good: Scoped middleware with proper matcher
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('session');
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};
レビュー出力形式
すべてのコードレビューの指摘事項を、次の形式でまとめます。
1. 概要
全体的な品質スコア(1~10)と主な所見を含む簡潔な概要。
2. 重大な問題(必ず修正)
セキュリティ上の脆弱性、データの露出、または機能の破損につながる問題。
3. 警告(修正を推奨)
ベストプラクティスに違反する、パフォーマンス上の問題を引き起こす、または保守性を低下させる問題。
4. 提案(改善を検討)
コードの整理、パフォーマンス、または開発者体験の改善。
5. 肯定的な所見
適切に実装されたパターンや、評価すべき優れたプラクティスです。
6. 推奨事項
影響の大きい改善について、コード例を添えた優先度順の次のステップです。
ベストプラクティス
'use client'の境界は、可能な限りツリーの深い位置に配置します- サーバーコンポーネントでデータを取得し、初期データのクライアント側での取得は避けます
- 並列でデータを取得し(
Promise.all)、リクエストのウォーターフォールを防ぎます - すべてのルートセグメントに、適切な読み込み状態、エラー状態、未検出状態を実装します
- すべてのサーバーアクションの入力を、Zod などのライブラリで検証します
- 可能な場合は、時間ベースの再検証ではなく
revalidatePath/revalidateTagを使用します config.matcherを使用して、ミドルウェアの適用対象を特定のルートに絞ります- コンテンツが変動する動的ページには
generateMetadataを実装します - パラメーターが既知の静的ページには
generateStaticParamsを使用します - クライアントコンポーネントでサーバー専用コードをインポートせず、
server-onlyパッケージを使用します
制約と警告
- このスキルは Next.js App Router を対象としています。Pages Router のパターンは大きく異なる場合があります
- プロジェクトの Next.js のバージョンに従います。一部の機能はバージョン固有です
- 明示的に依頼されない限り、Pages Router から App Router への移行を提案しないでください
- キャッシュの動作は開発環境と本番環境で異なるため、本番ビルドで検証してください
- サーバーアクションで機密性の高い操作を公開する場合は、適切な認証チェックを必ず実装してください
- 確度の高い問題に焦点を当て、スタイルの好みに関する誤検知を避けてください
参考資料
詳細なレビュー用チェックリストとパターンのドキュメントについては、references/ ディレクトリを参照してください。
references/app-router-patterns.md— App Router のベストプラクティスとパターンreferences/server-components.md— サーバーコンポーネントとクライアントコンポーネントの境界に関するガイドreferences/performance.md— Next.js のパフォーマンス最適化チェックリスト