claudient 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
> 🇫🇷 This is the French translation. [English version](../nextjs.md).
|
|
2
|
+
|
|
3
|
+
# Compétence Next.js
|
|
4
|
+
|
|
5
|
+
## Quand activer
|
|
6
|
+
- Construire une application Next.js avec l'App Router
|
|
7
|
+
- Décider entre les Server Components et les Client Components
|
|
8
|
+
- Rédiger des Server Actions pour les soumissions de formulaires et les mutations
|
|
9
|
+
- Configurer des route handlers (endpoints API dans l'App Router)
|
|
10
|
+
- Implémenter l'authentification avec NextAuth ou un pattern JWT
|
|
11
|
+
- Configurer le middleware pour les redirections et les guards d'auth
|
|
12
|
+
- Optimiser la récupération de données avec `cache()` et `unstable_cache` de React
|
|
13
|
+
- Utiliser les routes parallèles, les routes d'interception ou les groupes de routes
|
|
14
|
+
|
|
15
|
+
## Quand NE PAS utiliser
|
|
16
|
+
- Projets Pages Router — les patterns diffèrent significativement
|
|
17
|
+
- SPAs pures sans rendu serveur (utiliser Vite + React)
|
|
18
|
+
- Backends NestJS ou Express — utiliser la compétence NestJS
|
|
19
|
+
- Sites statiques sans données dynamiques (utiliser Astro)
|
|
20
|
+
|
|
21
|
+
## Instructions
|
|
22
|
+
|
|
23
|
+
### Structure des répertoires App Router
|
|
24
|
+
```
|
|
25
|
+
app/
|
|
26
|
+
├── (auth)/ # Groupe de routes — pas de segment URL
|
|
27
|
+
│ ├── login/
|
|
28
|
+
│ │ └── page.tsx
|
|
29
|
+
│ └── layout.tsx # Layout spécifique à l'auth
|
|
30
|
+
├── (dashboard)/
|
|
31
|
+
│ ├── dashboard/
|
|
32
|
+
│ │ ├── page.tsx # Server Component par défaut
|
|
33
|
+
│ │ └── loading.tsx # UI de la boundary Suspense
|
|
34
|
+
│ └── layout.tsx
|
|
35
|
+
├── api/
|
|
36
|
+
│ └── webhooks/
|
|
37
|
+
│ └── stripe/
|
|
38
|
+
│ └── route.ts # Route Handler
|
|
39
|
+
├── layout.tsx # Layout racine (requis)
|
|
40
|
+
└── page.tsx # Page d'accueil
|
|
41
|
+
components/
|
|
42
|
+
├── ui/ # Présentationnels (peuvent être server ou client)
|
|
43
|
+
└── forms/ # Toujours des client components (useState/événements)
|
|
44
|
+
lib/
|
|
45
|
+
├── auth.ts
|
|
46
|
+
├── db.ts
|
|
47
|
+
└── actions/ # Server Actions
|
|
48
|
+
└── user.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Server Components vs. Client Components
|
|
52
|
+
```tsx
|
|
53
|
+
// Server Component (défaut) — s'exécute sur le serveur, jamais envoyé au client
|
|
54
|
+
// Peut : await fetch, lire la DB, accéder aux variables d'environnement
|
|
55
|
+
// Ne peut pas : useState, useEffect, APIs navigateur, gestionnaires d'événements
|
|
56
|
+
export default async function UserProfile({ id }: { id: string }) {
|
|
57
|
+
const user = await db.user.findUnique({ where: { id } })
|
|
58
|
+
return <div>{user.name}</div>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Client Component — ajouter la directive 'use client'
|
|
62
|
+
'use client'
|
|
63
|
+
import { useState } from 'react'
|
|
64
|
+
|
|
65
|
+
export function LikeButton({ initialCount }: { initialCount: number }) {
|
|
66
|
+
const [count, setCount] = useState(initialCount)
|
|
67
|
+
return <button onClick={() => setCount(c => c + 1)}>{count} likes</button>
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Règle : par défaut Server Components. Ajouter `'use client'` uniquement quand vous avez besoin d'interactivité, d'APIs navigateur ou de hooks React.
|
|
72
|
+
|
|
73
|
+
### Patterns de récupération de données
|
|
74
|
+
```tsx
|
|
75
|
+
// Server Component — async/await direct, pas de useEffect, pas de useState
|
|
76
|
+
export default async function PostList() {
|
|
77
|
+
const posts = await db.post.findMany({ where: { published: true } })
|
|
78
|
+
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Déduplication — React cache() enveloppe une fonction pour que plusieurs composants
|
|
82
|
+
// l'appelant dans un même rendu partagent une seule requête
|
|
83
|
+
import { cache } from 'react'
|
|
84
|
+
export const getUser = cache(async (id: string) => {
|
|
85
|
+
return db.user.findUnique({ where: { id } })
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Génération statique avec revalidation
|
|
89
|
+
async function getProducts() {
|
|
90
|
+
const res = await fetch('https://api.example.com/products', {
|
|
91
|
+
next: { revalidate: 3600 }, // ISR — revalider toutes les heures
|
|
92
|
+
})
|
|
93
|
+
return res.json()
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Server Actions
|
|
98
|
+
```tsx
|
|
99
|
+
// lib/actions/user.ts
|
|
100
|
+
'use server'
|
|
101
|
+
import { revalidatePath } from 'next/cache'
|
|
102
|
+
import { z } from 'zod'
|
|
103
|
+
|
|
104
|
+
const UpdateSchema = z.object({
|
|
105
|
+
name: z.string().min(1),
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
export async function updateUser(formData: FormData) {
|
|
109
|
+
const parsed = UpdateSchema.safeParse({ name: formData.get('name') })
|
|
110
|
+
if (!parsed.success) return { error: parsed.error.flatten() }
|
|
111
|
+
|
|
112
|
+
await db.user.update({ where: { id: getCurrentUserId() }, data: parsed.data })
|
|
113
|
+
revalidatePath('/dashboard/profile')
|
|
114
|
+
return { success: true }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Dans un Server Component :
|
|
118
|
+
export default function ProfileForm({ user }: { user: User }) {
|
|
119
|
+
return (
|
|
120
|
+
<form action={updateUser}>
|
|
121
|
+
<input name="name" defaultValue={user.name} />
|
|
122
|
+
<button type="submit">Save</button>
|
|
123
|
+
</form>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Route Handlers
|
|
129
|
+
```ts
|
|
130
|
+
// app/api/webhooks/stripe/route.ts
|
|
131
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
132
|
+
import Stripe from 'stripe'
|
|
133
|
+
|
|
134
|
+
export async function POST(req: NextRequest) {
|
|
135
|
+
const body = await req.text()
|
|
136
|
+
const sig = req.headers.get('stripe-signature')!
|
|
137
|
+
|
|
138
|
+
let event: Stripe.Event
|
|
139
|
+
try {
|
|
140
|
+
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!)
|
|
141
|
+
} catch {
|
|
142
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (event.type === 'checkout.session.completed') {
|
|
146
|
+
await handleCheckoutCompleted(event.data.object)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return NextResponse.json({ received: true })
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Middleware
|
|
154
|
+
```ts
|
|
155
|
+
// middleware.ts (au niveau racine)
|
|
156
|
+
import { NextResponse } from 'next/server'
|
|
157
|
+
import type { NextRequest } from 'next/server'
|
|
158
|
+
|
|
159
|
+
export function middleware(request: NextRequest) {
|
|
160
|
+
const token = request.cookies.get('auth-token')
|
|
161
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
162
|
+
return NextResponse.redirect(new URL('/login', request.url))
|
|
163
|
+
}
|
|
164
|
+
return NextResponse.next()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const config = {
|
|
168
|
+
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Routes parallèles et d'interception
|
|
173
|
+
```
|
|
174
|
+
app/
|
|
175
|
+
├── @modal/ # Route parallèle — s'affiche à côté du contenu principal
|
|
176
|
+
│ └── (.)photo/ # Route d'interception — intercepte /photo/[id]
|
|
177
|
+
│ └── [id]/
|
|
178
|
+
│ └── page.tsx
|
|
179
|
+
├── photo/
|
|
180
|
+
│ └── [id]/
|
|
181
|
+
│ └── page.tsx
|
|
182
|
+
└── layout.tsx # Accepte { children, modal } comme props
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Boundaries d'erreur et de chargement
|
|
186
|
+
```tsx
|
|
187
|
+
// app/dashboard/loading.tsx — affiché pendant Suspense
|
|
188
|
+
export default function Loading() {
|
|
189
|
+
return <DashboardSkeleton />
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// app/dashboard/error.tsx — boundary d'erreur pour ce segment
|
|
193
|
+
'use client'
|
|
194
|
+
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
|
|
195
|
+
return (
|
|
196
|
+
<div>
|
|
197
|
+
<p>{error.message}</p>
|
|
198
|
+
<button onClick={reset}>Retry</button>
|
|
199
|
+
</div>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Variables d'environnement
|
|
205
|
+
- `NEXT_PUBLIC_*` — exposées au navigateur
|
|
206
|
+
- Toutes les autres — côté serveur uniquement (jamais accessibles dans les Client Components)
|
|
207
|
+
- Ne jamais importer une variable d'environnement server-only dans un Client Component — elle retourne `undefined` silencieusement
|
|
208
|
+
|
|
209
|
+
## Exemple
|
|
210
|
+
|
|
211
|
+
**Utilisateur :** Ajouter une page de blog paginée à `/blog` qui récupère depuis PostgreSQL, avec une modale "Nouvel article" qui s'ouvre à `/blog/new` sans naviguer hors de la liste des articles.
|
|
212
|
+
|
|
213
|
+
**Sortie attendue :**
|
|
214
|
+
- `app/blog/page.tsx` — Server Component, récupère les articles avec `db.post.findMany`, affiche `<PostList>` + `<Link href="/blog/new">`
|
|
215
|
+
- `app/@modal/(.)blog/new/page.tsx` — Route d'interception affichant un Client Component `<NewPostModal>`
|
|
216
|
+
- `app/blog/new/page.tsx` — Page de repli pour la navigation directe
|
|
217
|
+
- `app/layout.tsx` — Mis à jour pour accepter le slot `modal` de la route parallèle et l'afficher aux côtés de `children`
|
|
218
|
+
- `lib/actions/post.ts` — Server Action `createPost` avec validation Zod + `revalidatePath('/blog')`
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
> **Travaillez avec nous :** Claudient est soutenu par [Uitbreiden](https://uitbreiden.com/) — nous construisons des produits IA et des solutions B2B avec des communautés de développeurs. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# NestJS Skill
|
|
2
|
+
|
|
3
|
+
## When to activate
|
|
4
|
+
- Building a NestJS application (modules, controllers, services)
|
|
5
|
+
- Setting up guards, interceptors, pipes, and exception filters
|
|
6
|
+
- Integrating TypeORM or Prisma with NestJS
|
|
7
|
+
- Implementing CQRS with commands, queries, and events
|
|
8
|
+
- Setting up microservices (TCP, Redis, RabbitMQ transport)
|
|
9
|
+
- Writing unit and e2e tests with Jest and Supertest
|
|
10
|
+
- Generating OpenAPI docs with `@nestjs/swagger`
|
|
11
|
+
|
|
12
|
+
## When NOT to use
|
|
13
|
+
- Next.js API routes or Fastify standalone — different framework
|
|
14
|
+
- Simple Express scripts — NestJS overhead isn't justified
|
|
15
|
+
- Lambda functions where cold start time is critical
|
|
16
|
+
|
|
17
|
+
## Instructions
|
|
18
|
+
|
|
19
|
+
### Module structure
|
|
20
|
+
```
|
|
21
|
+
src/
|
|
22
|
+
├── app.module.ts # Root module
|
|
23
|
+
├── main.ts # Bootstrap
|
|
24
|
+
├── common/
|
|
25
|
+
│ ├── decorators/
|
|
26
|
+
│ ├── filters/
|
|
27
|
+
│ ├── guards/
|
|
28
|
+
│ ├── interceptors/
|
|
29
|
+
│ └── pipes/
|
|
30
|
+
├── config/
|
|
31
|
+
│ └── configuration.ts # ConfigService setup
|
|
32
|
+
└── modules/
|
|
33
|
+
└── users/
|
|
34
|
+
├── users.module.ts
|
|
35
|
+
├── users.controller.ts
|
|
36
|
+
├── users.service.ts
|
|
37
|
+
├── dto/
|
|
38
|
+
│ ├── create-user.dto.ts
|
|
39
|
+
│ └── update-user.dto.ts
|
|
40
|
+
├── entities/
|
|
41
|
+
│ └── user.entity.ts
|
|
42
|
+
└── users.service.spec.ts
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Bootstrap
|
|
46
|
+
```ts
|
|
47
|
+
// main.ts
|
|
48
|
+
import { NestFactory } from '@nestjs/core'
|
|
49
|
+
import { AppModule } from './app.module'
|
|
50
|
+
import { ValidationPipe } from '@nestjs/common'
|
|
51
|
+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
|
|
52
|
+
|
|
53
|
+
async function bootstrap() {
|
|
54
|
+
const app = await NestFactory.create(AppModule)
|
|
55
|
+
|
|
56
|
+
app.useGlobalPipes(new ValidationPipe({
|
|
57
|
+
whitelist: true, // strip unknown fields
|
|
58
|
+
forbidNonWhitelisted: true,
|
|
59
|
+
transform: true, // auto-transform payloads to DTO classes
|
|
60
|
+
}))
|
|
61
|
+
|
|
62
|
+
const config = new DocumentBuilder()
|
|
63
|
+
.setTitle('API')
|
|
64
|
+
.setVersion('1.0')
|
|
65
|
+
.addBearerAuth()
|
|
66
|
+
.build()
|
|
67
|
+
SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, config))
|
|
68
|
+
|
|
69
|
+
await app.listen(3000)
|
|
70
|
+
}
|
|
71
|
+
bootstrap()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Module, Controller, Service
|
|
75
|
+
```ts
|
|
76
|
+
// users.module.ts
|
|
77
|
+
@Module({
|
|
78
|
+
imports: [TypeOrmModule.forFeature([User]), JwtModule.register({})],
|
|
79
|
+
controllers: [UsersController],
|
|
80
|
+
providers: [UsersService],
|
|
81
|
+
exports: [UsersService],
|
|
82
|
+
})
|
|
83
|
+
export class UsersModule {}
|
|
84
|
+
|
|
85
|
+
// users.controller.ts
|
|
86
|
+
@ApiTags('users')
|
|
87
|
+
@ApiBearerAuth()
|
|
88
|
+
@UseGuards(JwtAuthGuard)
|
|
89
|
+
@Controller('users')
|
|
90
|
+
export class UsersController {
|
|
91
|
+
constructor(private readonly usersService: UsersService) {}
|
|
92
|
+
|
|
93
|
+
@Post()
|
|
94
|
+
@HttpCode(HttpStatus.CREATED)
|
|
95
|
+
create(@Body() dto: CreateUserDto): Promise<UserResponseDto> {
|
|
96
|
+
return this.usersService.create(dto)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Get(':id')
|
|
100
|
+
findOne(@Param('id', ParseUUIDPipe) id: string): Promise<UserResponseDto> {
|
|
101
|
+
return this.usersService.findOneOrFail(id)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// users.service.ts
|
|
106
|
+
@Injectable()
|
|
107
|
+
export class UsersService {
|
|
108
|
+
constructor(
|
|
109
|
+
@InjectRepository(User)
|
|
110
|
+
private readonly userRepo: Repository<User>,
|
|
111
|
+
) {}
|
|
112
|
+
|
|
113
|
+
async create(dto: CreateUserDto): Promise<User> {
|
|
114
|
+
const exists = await this.userRepo.findOneBy({ email: dto.email })
|
|
115
|
+
if (exists) throw new ConflictException('Email already in use')
|
|
116
|
+
const user = this.userRepo.create({
|
|
117
|
+
...dto,
|
|
118
|
+
password: await bcrypt.hash(dto.password, 10),
|
|
119
|
+
})
|
|
120
|
+
return this.userRepo.save(user)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async findOneOrFail(id: string): Promise<User> {
|
|
124
|
+
const user = await this.userRepo.findOneBy({ id })
|
|
125
|
+
if (!user) throw new NotFoundException(`User ${id} not found`)
|
|
126
|
+
return user
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### DTOs with class-validator
|
|
132
|
+
```ts
|
|
133
|
+
// dto/create-user.dto.ts
|
|
134
|
+
import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator'
|
|
135
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
|
136
|
+
|
|
137
|
+
export class CreateUserDto {
|
|
138
|
+
@ApiProperty({ example: 'user@example.com' })
|
|
139
|
+
@IsEmail()
|
|
140
|
+
email: string
|
|
141
|
+
|
|
142
|
+
@ApiProperty({ minLength: 8 })
|
|
143
|
+
@IsString()
|
|
144
|
+
@MinLength(8)
|
|
145
|
+
password: string
|
|
146
|
+
|
|
147
|
+
@ApiPropertyOptional()
|
|
148
|
+
@IsOptional()
|
|
149
|
+
@IsString()
|
|
150
|
+
name?: string
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Guards
|
|
155
|
+
```ts
|
|
156
|
+
// common/guards/jwt-auth.guard.ts
|
|
157
|
+
@Injectable()
|
|
158
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
159
|
+
handleRequest<T>(err: Error, user: T, info: Error): T {
|
|
160
|
+
if (err || !user) {
|
|
161
|
+
throw err || new UnauthorizedException(info?.message)
|
|
162
|
+
}
|
|
163
|
+
return user
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// common/guards/roles.guard.ts
|
|
168
|
+
@Injectable()
|
|
169
|
+
export class RolesGuard implements CanActivate {
|
|
170
|
+
constructor(private reflector: Reflector) {}
|
|
171
|
+
|
|
172
|
+
canActivate(context: ExecutionContext): boolean {
|
|
173
|
+
const roles = this.reflector.getAllAndOverride<Role[]>('roles', [
|
|
174
|
+
context.getHandler(),
|
|
175
|
+
context.getClass(),
|
|
176
|
+
])
|
|
177
|
+
if (!roles) return true
|
|
178
|
+
const { user } = context.switchToHttp().getRequest()
|
|
179
|
+
return roles.some(role => user.roles?.includes(role))
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Interceptors
|
|
185
|
+
```ts
|
|
186
|
+
// common/interceptors/transform.interceptor.ts
|
|
187
|
+
@Injectable()
|
|
188
|
+
export class TransformInterceptor<T>
|
|
189
|
+
implements NestInterceptor<T, { data: T; timestamp: string }>
|
|
190
|
+
{
|
|
191
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
192
|
+
return next.handle().pipe(
|
|
193
|
+
map(data => ({ data, timestamp: new Date().toISOString() }))
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### CQRS
|
|
200
|
+
```ts
|
|
201
|
+
// Install: @nestjs/cqrs
|
|
202
|
+
|
|
203
|
+
// commands/create-user.command.ts
|
|
204
|
+
export class CreateUserCommand {
|
|
205
|
+
constructor(public readonly dto: CreateUserDto) {}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// commands/create-user.handler.ts
|
|
209
|
+
@CommandHandler(CreateUserCommand)
|
|
210
|
+
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
|
|
211
|
+
constructor(private readonly userRepo: UserRepository) {}
|
|
212
|
+
|
|
213
|
+
async execute(command: CreateUserCommand): Promise<User> {
|
|
214
|
+
const user = User.create(command.dto)
|
|
215
|
+
await this.userRepo.save(user)
|
|
216
|
+
return user
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// In service/controller:
|
|
221
|
+
const user = await this.commandBus.execute(new CreateUserCommand(dto))
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Testing
|
|
225
|
+
```ts
|
|
226
|
+
// Unit test
|
|
227
|
+
describe('UsersService', () => {
|
|
228
|
+
let service: UsersService
|
|
229
|
+
let repo: jest.Mocked<Repository<User>>
|
|
230
|
+
|
|
231
|
+
beforeEach(async () => {
|
|
232
|
+
const module = await Test.createTestingModule({
|
|
233
|
+
providers: [
|
|
234
|
+
UsersService,
|
|
235
|
+
{ provide: getRepositoryToken(User), useValue: createMockRepository() },
|
|
236
|
+
],
|
|
237
|
+
}).compile()
|
|
238
|
+
service = module.get(UsersService)
|
|
239
|
+
repo = module.get(getRepositoryToken(User))
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('throws ConflictException for duplicate email', async () => {
|
|
243
|
+
repo.findOneBy.mockResolvedValue(existingUser)
|
|
244
|
+
await expect(service.create(dto)).rejects.toThrow(ConflictException)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// E2E test
|
|
249
|
+
describe('POST /users', () => {
|
|
250
|
+
it('creates a user', () => {
|
|
251
|
+
return request(app.getHttpServer())
|
|
252
|
+
.post('/users')
|
|
253
|
+
.send({ email: 'a@b.com', password: 'password123' })
|
|
254
|
+
.expect(201)
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Example
|
|
260
|
+
|
|
261
|
+
**User:** Add a `Products` module to a NestJS app with TypeORM, including CRUD endpoints, admin-only delete protected by a `RolesGuard`, and OpenAPI docs.
|
|
262
|
+
|
|
263
|
+
**Expected output:**
|
|
264
|
+
- `products.entity.ts` — TypeORM entity with `id` (UUID), `name`, `price` (decimal), `stock`, `createdAt`
|
|
265
|
+
- `dto/create-product.dto.ts` — class-validator DTO with `@ApiProperty` decorators
|
|
266
|
+
- `products.service.ts` — CRUD methods using `Repository<Product>`, throwing `NotFoundException` on missing
|
|
267
|
+
- `products.controller.ts` — all CRUD endpoints, `@UseGuards(JwtAuthGuard, RolesGuard)` + `@Roles(Role.Admin)` on `DELETE`
|
|
268
|
+
- `products.module.ts` — imports `TypeOrmModule.forFeature([Product])`
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
> **Work with us:** Claudient is backed by [Uitbreiden](https://uitbreiden.com/) — we build AI products and B2B solutions with developer communities. [uitbreiden.com](https://uitbreiden.com/)
|