Next.js プロダクションスタック
Next.js アプリケーションを構築・保守するための厳選されたデフォルト設定。新しいプロジェクトのスキャフォールディング、または既存プロジェクトへの追加を行う際は、以下の規則に従います。ただし、ユーザーの既存コードベースが明確に異なる方法を採用している場合は、既存のパターンを優先し、その差異を簡単に記録します。
0. スタック概要
| レイヤー | 推奨 (推奨、必須ではない) | 変更が要求された場合のオプション |
|---|---|---|
| フレームワーク | Next.js (App Router), TypeScript, サーバーコンポーネント + サーバーアクション | 不要(これがコア) |
| パッケージマネージャー | pnpm | npm, yarn, bun |
| UI | Tailwind CSS, shadcn/ui, react-icons, Motion (motion.dev) | Radix UI または Mantine, lucide-react, CSS Modules |
| フォーム | React Hook Form + Zod | TanStack Form, Valibot (Zodの代わりに) |
| データベース | PostgreSQL + Drizzle ORM | Prisma, Kysely; 小規模アプリでは MySQL または SQLite |
| 認証 | Better Auth, RBAC | Auth.js, Clerk, Supabase Auth |
| メディア | Cloudinary | UploadThing, S3 + CDN, Vercel Blob |
| テスト | Vitest (単体/結合), Playwright (E2E) | Jest, Cypress |
| モニタリング | Sentry, 構造化ログ | Datadog, Highlight, Axiom + pino |
| Lint/フォーマット | ESLint, Prettier, TypeScript strict | Biome |
| CI/CD | GitHub + GitHub Actions, Vercel, プレビュー環境 | GitLab CI, Netlify, Cloudflare Pages |
この表のパッケージを代替品(例:Drizzleの代わりにPrisma、Better Authの代わりにnext-auth)に無断で入れ替えないでください。ユーザーのプロジェクトですでに別の選択肢が使用されている場合は、それを引き続き使用し、異なる選択肢が混在しないようにその不一致を報告します。
これらのツールは「推奨」です。ユーザーはこれらを好みますが、必須ではありません。
- 以降のセクションで特定のツール(pnpm, Drizzle, Better Auth, Cloudinary, Sentry など)が指定されている場合、このルールに基づく推奨デフォルトとして解釈します。
- 表のツールを変更、置換、またはスキップする前に、まずユーザーに確認します。推奨ツールを維持するか、最後の列(または他のベストプラクティス)の代替案を、それぞれのトレードオフを1行で説明し、あなたの推奨も添えて提示します。
- 代替案を推奨する前に、現在の状態(メンテナンス状況、破壊的変更など)を確認します。記憶だけに頼らないでください。
- 回答なしにツールを変更しないでください。ユーザーが回答するまで、推奨デフォルトを維持します。
1. プロジェクトセットアップ
pnpm create next-app@latestでスキャフォールド — TypeScript、App Router、Tailwind、ESLint をすべて有効にし、src/ディレクトリは拒否してapp/がプロジェクトルートに配置されるようにします(新しいプロジェクトのみ)。- すでに
src/を使用している既存プロジェクト:src/を維持 —src/app/,src/components/,src/lib/,src/actions/。src/ディレクトリがないプロジェクトでのみapp/を使用します。既存のレイアウトとインポートエイリアス(@/*→src/*)に従い、無関係なタスクの一部としてファイルをsrc/から移動しないでください。 - すべてのインストール/実行コマンドには pnpm を使用します(
pnpm add,pnpm dlx,pnpm dev)。npm/yarn のロックファイルを混在させないでください。 pnpm dlx shadcn@latest initで shadcn/ui を初期化し、その後、コンポーネント一式を事前インストールするのではなく、必要に応じてコンポーネントを追加します(pnpm dlx shadcn@latest add button)。- ベースとなるフォルダ構造(
src/なし —app/はプロジェクトルートに配置):
# 新しいプロジェクト (src/ なし)
app/ # ルート、レイアウト、error/loading/not-found バウンダリ
components/ # 共有UI (ui/ = shadcn プリミティブ)
lib/ # db, auth, バリデーション, ユーティリティ, レート制限, ロガー
actions/ # ドメインごとにグループ化されたサーバーアクション
hooks/
types/
drizzle/ # マイグレーション
src/ を使用する既存プロジェクト:src/ の下に同じ構造(src/app/, src/components/, src/lib/, src/actions/)— フォルダ名は同じで、プレフィックスのみが変わります。
2. TypeScript & コード品質
- tsconfig で
strict: true、anyはそれを正当化するコメントなしでは使用不可、@ts-ignoreは関連する issue や理由なしでは使用不可。 - ESLint(next/core-web-vitals + typescript-eslint)と Prettier が設定され、コードが完了と見なされる前にパスしていること — 想定せずに実行します。
- エクスポートされた関数とサーバーアクションには、明示的な戻り値の型を優先します。
- サーバーコンポーネントをデフォルトとして維持します。インタラクティブ性、ブラウザAPI、またはフックが必要な場合にのみ
"use client"を追加し、その境界を可能な限り低く(リーフレベルに)押し下げます。 - ファイル/モジュールごとに1つの責務。ルートのサーバーアクションは、
actions/またはルートの隣にある_actions.tsファイルに配置し、巨大なページファイルにインラインで記述しないでください。 - アーキテクチャの規則:ロジックの種類ごとに1つの場所に保ちます — データアクセスは
lib/db、ミューテーションはactions/、共有UIはcomponents/、横断的なヘルパーはlib/。プロジェクトの既存の命名規則(ファイルの大文字小文字、コンポーネントの命名)に従い、途中で新しい規則を導入しないでください。 - コードレビュー:すべての重要な変更はPRと同様に扱います — 明確な差分、lint/型チェック/テストのパスをレビュー依頼前に確認し、何が変わったか、なぜ変わったかの説明を含めます。レビュー時(人間またはAI支援)、認証チェックの欠落、未処理のエラー、規則からの逸脱をチェックします。単に実行できるかどうかだけではありません。
3. レンダリング、データ、パフォーマンス
- 静的 vs 動的レンダリング:デフォルトで静的。ルートが本当にリクエストごとのデータを必要とする場合にのみ、動的(
export const dynamic = 'force-dynamic',cookies(),headers())を選択します。 - データフェッチ:サーバーコンポーネントで直接フェッチします。ミューテーションにはサーバーアクションを使用し、コンシューマーが外部でない限り、クライアント側のフェッチからAPIルートへの使用は避けます。
- ストリーミングとSuspense:遅い/ストリーミング可能なセクションを
<Suspense>でラップし、実際のスケルトンフォールバック(単なるスピナーではない)を配置して、ページの残りの部分をすぐにレンダリングしてストリーミングできるようにします。これはloading.tsxがルートレベルで提供するものと同じです。 - キャッシュと再検証:高価な読み取りには
use cache/unstable_cache(Nextのバージョンによる)を使用します。ミューテーション後は、revalidatePath/revalidateTagで正確に無効化し、古さを「修正」するためにforce-dynamicに頼らないでください。 - プリフェッチ:ユーザーがクリックしそうなリンクをプリフェッチします —
<Link>はこれを自動的に行います。特定の理由なしに無効にしないでください(prefetch={false})。 - レイジーローディングとコード分割:スクロールしなければ見えない場所やめったに使用されないクライアントコンポーネントは
next/dynamicでレイジーロードします。1ページでのみ必要なライブラリからの大きなクライアントバンドルは避けます。 - 画像とフォントの最適化:ラスター画像には常に
next/imageを使用し(明示的なwidth/heightまたはfill+ サイズ指定されたコンテナ)、フォントにはnext/fontを使用します — 生の<img>タグやGoogle Fontsへの<link>は絶対に使用しません。 - Core Web Vitals:LCP、CLS、INPを実際の制約として扱います — レイアウトシフトを引き起こす後から読み込まれるコンテンツ、サイズ未指定のメディア、同期ブロッキングスクリプトは許可されません。
4. UI
- スタイリングにはTailwind。shadcn/ui コンポーネントを基本プリミティブとして使用し、プロジェクトのテーマトークン(CSS変数)を介してカスタマイズします — ファイル全体に散らばった一回限りのインラインオーバーライドは避けます。
- アイコンには react-icons(プロジェクトごとに1つのアイコンセットを選択、例:
react-icons/luまたは/hi2、一貫性を保つ)。 - アニメーションには Motion(motion.dev、旧 Framer Motion)。アニメーション化されたコンポーネントはクライアントサイドで小さく保ち、
prefers-reduced-motionを尊重します。 - デフォルトでレスポンシブデザイン(モバイルファーストのTailwindブレークポイント)。デスクトップ専用のレイアウトを出荷しないでください。
5. フォームとバリデーション
- フォーム/データ形状ごとに1つのZodスキーマを定義します。同じスキーマをクライアントバリデーション(
@hookform/resolvers/zod経由)とサーバーアクション内のサーバーサイドバリデーションの両方で再利用します — クライアントのみのバリデーションを信頼しないでください。 - React Hook Form(
useForm)がクライアント状態を管理します。送信時にサーバーアクションを呼び出し、アクションの構造化された戻り値からフィールドエラーを表面化させます(一般的なエラーで行き詰まるスローされた例外ではない)。 - 起動時に
process.env自体をZodスキーマ(lib/env.ts)でバリデーションし、欠落または不正な設定が最初の使用時ではなくすぐに失敗するようにします。
6. データベース (PostgreSQL + Drizzle)
- スキーマは
lib/db/schema.ts(またはドメインごとに分割)に配置します。マイグレーションはdrizzle-kit generateで生成し、drizzle-kit migrateで適用します — 生成されたマイグレーションファイルを手動で編集しないでください。 - サーバーレス/エッジコンテキストではプール接続(例:ドライバーのプールまたはNeon/Supabaseプーリングなどのプロバイダー経由)を使用します。プールなしでリクエストごとに新しいクライアントを開かないでください。
- クエリ最適化とN+1防止:Drizzleのリレーショナルクエリ(
db.query.x.findMany({ with: {...} }))または明示的な結合を使用して、行ごとにループしてクエリを実行する代わりに、必要な列のみを選択し、select *スタイルの全行フェッチは避けます。無制限の結果セットを返す可能性のあるクエリはページネーション/制限します。 WHERE/ORDER BY/ 結合で使用される列にインデックスを作成します。後付けではなく、スキーマに追加します。- 本番データベースのバックアップ/リストア手順を文書化して維持します(最低限、管理対象プロバイダーのスナップショット)。ユーザーのセットアップにこれがない場合は指摘します。
7. 認証とセキュリティ
- 認証には Better Auth(セッション、OAuthプロバイダー、必要に応じてメール/パスワード)。パスワードハッシュやセッショントークンを手動で実装しないでください。
- RBAC:ロール/権限を明示的にモデル化します(
roleカラムと小さな権限チェックヘルパー)。UIを隠すだけでなく、サーバーアクション/データアクセス層で認可を強制します。隠しボタンはアクセス制御ではありません。 - 機密性の高いエンドポイント(認証、フォーム送信、高価な処理)をレート制限します。例:Upstash Redis または同等のトークンバケット/スライディングウィンドウ制限。
- セキュリティヘッダーを設定します(
next.config.jsのヘッダーまたはミドルウェア経由):Content-Security-Policy、X-Frame-Options、Referrer-Policy、Permissions-Policy、Strict-Transport-Security。 - クッキー:すべての認証/セッションクッキーで
httpOnly、secure、sameSite: 'lax'(可能な場合は'strict')。 - すべての外部入力(フォームデータ、クエリパラメータ、Webhookペイロード)を、ビジネスロジックまたはデータベースに到達する前に、境界でZodを使用してバリデーションします。
- シークレット、トークン、資格情報を含む完全なリクエストボディをログに記録しないでください。
8. SEO
- ルートごとに Metadata API(
generateMetadata/ 静的metadataエクスポート)を使用します — タイトル、説明、Open Graph、Twitterカード。 - ハードコードされた古いリストではなく、実際のルートデータから生成された
app/sitemap.tsとapp/robots.tsを提供します。 - メタデータの
alternates.canonicalを使用して正規URLを設定します。特に、複数のクエリ文字列バリアントを介して到達可能なルートの場合。 - 有益なコンテンツタイプ(記事、製品、パンくずリスト、組織)に対して、
type="application/ld+json"のスクリプトタグを使用してJSON-LD構造化データを追加します。
9. アクセシビリティ
- セマンティックHTMLを優先(
button、nav、main、header、label)— ARIAはセマンティックHTMLではカバーできない実際のギャップを埋めるためにのみ使用します。 - すべての要素がキーボードのみで到達可能かつ操作可能であること。フォーカス状態を可視化します(置き換えなしに
:focus-visibleのアウトラインを削除しないでください)。 - すべての画像に意味のある
altテキストがあること(純粋に装飾的な画像の場合は空のalt=""のみ許可)。 - すべてのフォーム入力に実際に関連付けられた
<label>があること(プレースホルダーのみではない)。 - テキストとインタラクティブ要素に対して、最低でもWCAG AAに照らしてカラーコントラストをチェックします。
10. エラーハンドリングとローディング状態
失敗する可能性がある、または時間がかかる可能性のあるすべてのルートセグメントには、以下が必要です:
error.tsx— 再試行アクションを備えたセグメントレベルのエラーバウンダリ。global-error.tsx—error.tsxがキャッチできないエラーに対するトップレベルのフォールバック。not-found.tsx— 404エラー用(データルックアップからのnotFound()呼び出しを含む)。loading.tsx— Suspenseフォールバック。最終的なレイアウトに一致する実際のスケルトンにし、単なるスピナーにしないでください。
11. テスト
- 単体テスト(純粋関数、スキーマバリデーション、ユーティリティロジック)とコンポーネントテスト(React Testing Library)には Vitest —
*.test.ts(x)または__tests__/の下に配置します。 - エンドツーエンドフローには Playwright:認証、重要なユーザージャーニー、フォーム送信。プレビュー環境またはローカルビルドに対して実行し、本番環境に対しては実行しません。
- 新しいサーバーアクションとバリデーションスキーマには、少なくとも基本的なテストを含める必要があります。重要なビジネスロジックをデフォルトでテストなしのままにしないでください。
12. モニタリングとロギング
- エラー追跡には Sentry(クライアントとサーバーの両方、インストルメンテーションファイル +
sentry.client.config/sentry.server.config)。デプロイ時にソースマップをアップロードします。 - 構造化ロギング(利用可能な場合はリクエストID、ルート、ユーザーIDを含むJSONログ)。アドホックな
console.logの代わりに使用します。 - 本番環境で Core Web Vitals を追跡します(Vercel Analytics または Sentry Performance)。デプロイされたURLのアップタイム監視を設定します。
13. DevOps
- 従来のスコープ付きコミットを含むGit。保護された
mainブランチ。 - すべてのPRに対するGitHub Actions CI:インストール → lint → 型チェック → 単体テスト → ビルド(大規模な変更にはE2Eを追加)。
- Vercel にデプロイ。すべてのPRはマージ前にプレビュー環境を取得します。
- 環境変数はVercelで環境ごとに管理し、§5のZod envスキーマを介して起動時にバリデーションします。
14. メディア
- ユーザーがアップロードする/動的なメディアには Cloudinary:署名付きのサーバー発行のアップロードパラメータを介してアップロード(APIシークレットをクライアント側に公開しない)、Cloudinaryの変換URLを介して提供し、サイズ変更を再実装しないでください。
15. Claudeのための作業規則
このスタックを使用するプロジェクトで機能追加を依頼された場合:
- 新しいものを作成する前に、既存のスキーマ/アクション/コンポーネントを確認します — 拡張し、重複させないでください。
- 新しいDBフィールド →
schema.tsを更新し、マイグレーションを生成します。SQLを手動で編集しないでください。 - 新しいフォーム → Zodスキーマ → RHFフォーム → サーバーサイドの再検証と認可チェックを備えたサーバーアクション。
- 新しいルート → 静的か動的かを意図的に決定します。
loading.tsx/error.tsxがない場合は追加します。メタデータを追加します。 - ミューテーション後は、影響を受ける特定のパス/タグを再検証します — アプリ全体をまとめて再検証しないでください。
- コードを提示する前に、厳密モードに対して lint/型チェックを頭の中で実行します。
any、未チェックの入力、または認証チェックの欠落を黙って出荷するのではなく、報告します。 - セクション0のツールを導入、置換、またはスキップする前に、オプションを添えてユーザーに確認します(セクション0の「推奨、必須ではない」ルールを参照)。
16. コンパニオンファイル
AGENTS.md— プロジェクトのワークフロールール(メインフロー、フィーチャーフロー、承認ゲート...)