-
Notifications
You must be signed in to change notification settings - Fork 0
docs: Next.js / SPA 推奨ライブラリを拡充・統一 #527
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,24 +42,74 @@ export default defineConfig({ | |
| } | ||
| ``` | ||
|
|
||
| ## ESLint + Prettier | ||
| ## Biome(Lint + Format) | ||
|
|
||
| ESLint + Prettier の代わりに **Biome を推奨**する。1 ツールで lint + format を高速に実行できる。 | ||
|
|
||
| ```bash | ||
| npm install -D eslint @eslint/js typescript-eslint eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-config-prettier | ||
| npm install -D prettier | ||
| npm install -D --save-exact @biomejs/biome | ||
| npx @biomejs/biome init | ||
| ``` | ||
|
|
||
| **スクリプト**: | ||
| `biome.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json", | ||
| "organizeImports": { | ||
| "enabled": true | ||
| }, | ||
| "formatter": { | ||
| "indentStyle": "space", | ||
| "indentWidth": 2, | ||
| "lineWidth": 100 | ||
| }, | ||
| "linter": { | ||
| "rules": { | ||
| "recommended": true, | ||
| "suspicious": { | ||
| "noConsole": { | ||
| "level": "error", | ||
| "options": { | ||
| "allow": ["error", "warn"] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "files": { | ||
| "ignore": ["dist", "node_modules", "coverage"] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **推奨スクリプト**: | ||
|
|
||
| ```json | ||
| { | ||
| "lint": "eslint .", | ||
| "lint:fix": "eslint . --fix", | ||
| "format": "prettier --write .", | ||
| "format:check": "prettier --check ." | ||
| "check": "biome check .", | ||
| "check:fix": "biome check --write .", | ||
| "lint": "biome lint .", | ||
| "format": "biome format .", | ||
| "format:check": "biome format ." | ||
| } | ||
| ``` | ||
|
|
||
| > `biome check` は lint + format + import 整理を一括実行する。CI では `biome check .` を使う。 | ||
|
|
||
| ### 既存の ESLint + Prettier からの移行 | ||
|
|
||
| ```bash | ||
| npx @biomejs/biome migrate eslint | ||
| npx @biomejs/biome migrate prettier | ||
| ``` | ||
|
|
||
| 移行後、不要になったパッケージと設定ファイルを削除する: | ||
|
|
||
| - `eslint`, `eslint-config-*`, `eslint-plugin-*`, `@eslint/*`, `typescript-eslint` | ||
| - `prettier`, `eslint-config-prettier` | ||
| - `eslint.config.mjs` / `.eslintrc.*` / `.prettierrc*` | ||
|
|
||
| ## CI/CD ワークフロー | ||
|
|
||
| **参考**: `/setup-ci` コマンドで雛形を生成可能。 | ||
|
|
@@ -74,11 +124,12 @@ Lint → Format Check → Test (with coverage) → Build | |
|
|
||
| ## lint-staged | ||
|
|
||
| ```json | ||
| { | ||
| "*.{ts,tsx}": ["eslint --fix", "prettier --write"], | ||
| "*.{json,md,yml}": ["prettier --write"] | ||
| } | ||
| ```js | ||
| // lint-staged.config.js | ||
| module.exports = { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new snippet uses Useful? React with 👍 / 👎. |
||
| '*.{ts,tsx,js,jsx,json,css}': ['biome check --write --no-errors-on-unmatched'], | ||
| '*.{md,yml,yaml}': ['biome format --write --no-errors-on-unmatched'], | ||
| }; | ||
| ``` | ||
|
|
||
| ## CLAUDE.md | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -128,6 +128,144 @@ npx @biomejs/biome migrate prettier | |
| - `prettier`, `eslint-config-prettier` | ||
| - `eslint.config.mjs` / `.eslintrc.*` / `.prettierrc*` | ||
|
|
||
| ## バリデーション & 型安全 | ||
|
|
||
| ### Zod(スキーマバリデーション) | ||
|
|
||
| ```bash | ||
| npm install zod | ||
| ``` | ||
|
|
||
| API レスポンス・フォーム入力・環境変数の検証を一元化する。Supabase の型と組み合わせて使う。 | ||
|
|
||
| **基本的な使い方**: | ||
|
|
||
| ```typescript | ||
| import { z } from 'zod'; | ||
|
|
||
| // スキーマ定義 | ||
| const UserSchema = z.object({ | ||
| id: z.string().uuid(), | ||
| email: z.string().email(), | ||
| name: z.string().min(1).max(100), | ||
| }); | ||
|
|
||
| type User = z.infer<typeof UserSchema>; | ||
|
|
||
| // API Route でのバリデーション | ||
| export async function POST(req: Request) { | ||
| const body = await req.json(); | ||
| const result = UserSchema.safeParse(body); | ||
|
|
||
| if (!result.success) { | ||
| return Response.json({ errors: result.error.flatten() }, { status: 400 }); | ||
| } | ||
|
|
||
| // result.data は型安全 | ||
| const user = result.data; | ||
| } | ||
| ``` | ||
|
|
||
| **Supabase の型と組み合わせる**: | ||
|
|
||
| ```typescript | ||
| import { z } from 'zod'; | ||
| import type { Database } from '@/lib/supabase/types'; | ||
|
|
||
| type Row = Database['public']['Tables']['users']['Row']; | ||
|
|
||
| // DB の型から Zod スキーマを構築 | ||
| const UserInsertSchema = z.object({ | ||
| email: z.string().email(), | ||
| name: z.string().min(1), | ||
| }) satisfies z.ZodType<Partial<Row>>; | ||
| ``` | ||
|
|
||
| ### @t3-oss/env-nextjs(環境変数の型安全化) | ||
|
|
||
| ```bash | ||
| npm install @t3-oss/env-nextjs zod | ||
| ``` | ||
|
|
||
| `.env` の未設定・型ミスをビルド時に検知する。`process.env.XXX` の生アクセスを禁止し、型付き `env` オブジェクト経由に統一する。 | ||
|
|
||
| **`src/env.ts`**: | ||
|
|
||
| ```typescript | ||
| import { createEnv } from '@t3-oss/env-nextjs'; | ||
| import { z } from 'zod'; | ||
|
|
||
| export const env = createEnv({ | ||
| server: { | ||
| SUPABASE_SERVICE_ROLE_KEY: z.string().min(1), | ||
| SENTRY_AUTH_TOKEN: z.string().optional(), | ||
| }, | ||
| client: { | ||
| NEXT_PUBLIC_SUPABASE_URL: z.string().url(), | ||
| NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1), | ||
| NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(), | ||
| }, | ||
| runtimeEnv: { | ||
| SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY, | ||
| SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, | ||
| NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL, | ||
| NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, | ||
| NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| > `next.config.ts` で `import './src/env'` を追加するとビルド時に検証が走る。 | ||
|
|
||
| ## フォーム管理(react-hook-form + Zod) | ||
|
|
||
| ```bash | ||
| npm install react-hook-form @hookform/resolvers zod | ||
| ``` | ||
|
|
||
| フォームバリデーションを Zod スキーマで統一し、型安全なフォームを実装する。 | ||
|
|
||
| **基本的な使い方**: | ||
|
|
||
| ```typescript | ||
| 'use client'; | ||
|
|
||
| import { useForm } from 'react-hook-form'; | ||
| import { zodResolver } from '@hookform/resolvers/zod'; | ||
| import { z } from 'zod'; | ||
|
|
||
| const schema = z.object({ | ||
| email: z.string().email('有効なメールアドレスを入力してください'), | ||
| password: z.string().min(8, '8文字以上で入力してください'), | ||
| }); | ||
|
|
||
| type FormValues = z.infer<typeof schema>; | ||
|
|
||
| export function LoginForm() { | ||
| const { | ||
| register, | ||
| handleSubmit, | ||
| formState: { errors, isSubmitting }, | ||
| } = useForm<FormValues>({ resolver: zodResolver(schema) }); | ||
|
|
||
| const onSubmit = async (data: FormValues) => { | ||
| // data は型安全 | ||
| }; | ||
|
|
||
| return ( | ||
| <form onSubmit={handleSubmit(onSubmit)}> | ||
| <input {...register('email')} /> | ||
| {errors.email && <p>{errors.email.message}</p>} | ||
| <input type="password" {...register('password')} /> | ||
| {errors.password && <p>{errors.password.message}</p>} | ||
| <button type="submit" disabled={isSubmitting}> | ||
| ログイン | ||
| </button> | ||
| </form> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Knip(未使用コード検出) | ||
|
|
||
| 未使用の依存関係・ファイル・export を検出する **Knip を推奨**する。 | ||
|
|
@@ -464,6 +602,38 @@ export async function GET() { | |
|
|
||
| 詳細な設定は [Sentry セットアップガイド](../sentry-setup-guide.md) を参照。 | ||
|
|
||
| ### @vercel/analytics + @vercel/speed-insights(アナリティクス) | ||
|
|
||
| ```bash | ||
| npm install @vercel/analytics @vercel/speed-insights | ||
| ``` | ||
|
|
||
| Vercel デプロイなら追加コスト・設定なしで Core Web Vitals とページビューを収集できる。 | ||
|
|
||
| **`app/layout.tsx` に2行追加するだけ**: | ||
|
|
||
| ```typescript | ||
| import { Analytics } from '@vercel/analytics/react'; | ||
| import { SpeedInsights } from '@vercel/speed-insights/next'; | ||
|
|
||
| export default function RootLayout({ children }: { children: React.ReactNode }) { | ||
| return ( | ||
| <html lang="ja"> | ||
| <body> | ||
| {children} | ||
| <Analytics /> | ||
| <SpeedInsights /> | ||
| </body> | ||
| </html> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| | コンポーネント | 収集データ | | ||
| | ------------------- | ------------------------------------------ | | ||
| | `<Analytics />` | ページビュー・ユニークビジター・リファラー | | ||
| | `<SpeedInsights />` | LCP / FID / CLS 等の Core Web Vitals | | ||
|
|
||
|
Comment on lines
+632
to
+636
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n docs/setup/web-app-nextjs.md | sed -n '520,535p'Repository: keito4/config Length of output: 692 🌐 Web query:
💡 Result:
Citations:
Update Core Web Vitals metrics to current definition Line 529 references Reference: https://web.dev/blog/inp-cwv 🤖 Prompt for AI Agents |
||
| ### ロギング設計指針 | ||
|
|
||
| | ツール | 用途 | 環境 | | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
削除ガイダンスの適用スコープを明記してください
Line 107-112 は ESLint/Prettier 関連ファイルの削除を案内していますが、現状のリポジトリ運用(
eslint.config.mjs/.prettierrc/.husky/pre-commit)と衝突します。この手順が「アプリ側リポジトリ向け」なのか「この config リポジトリにも適用」なのかを明記してください。
🧰 Tools
🪛 LanguageTool
[typographical] ~109-~109: 2つの連続するコンマ
Context: ...,
eslint-config-*,eslint-plugin-*,@eslint/*,typescript-eslint-prettier, `es...(DOUBLE_PUNCTUATION)
🤖 Prompt for AI Agents