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
|
+
> 🇳🇱 Dit is de Nederlandse vertaling. [Engelse versie](../nextjs.md).
|
|
2
|
+
|
|
3
|
+
# Next.js Skill
|
|
4
|
+
|
|
5
|
+
## Wanneer te activeren
|
|
6
|
+
- Een Next.js-applicatie bouwen met de App Router
|
|
7
|
+
- Kiezen tussen Server Components en Client Components
|
|
8
|
+
- Server Actions schrijven voor formulierinzendingen en mutaties
|
|
9
|
+
- Route handlers instellen (API-endpoints in App Router)
|
|
10
|
+
- Authenticatie implementeren met NextAuth of een JWT-patroon
|
|
11
|
+
- Middleware configureren voor redirects en auth-guards
|
|
12
|
+
- Datafetching optimaliseren met React `cache()` en `unstable_cache`
|
|
13
|
+
- Parallelle routes, onderscheppende routes of routegroepen gebruiken
|
|
14
|
+
|
|
15
|
+
## Wanneer NIET te gebruiken
|
|
16
|
+
- Pages Router-projecten — de patronen verschillen aanzienlijk
|
|
17
|
+
- Pure SPA's zonder server-rendering (gebruik Vite + React)
|
|
18
|
+
- NestJS of Express backends — gebruik de NestJS skill
|
|
19
|
+
- Statische sites zonder dynamische data (gebruik Astro)
|
|
20
|
+
|
|
21
|
+
## Instructies
|
|
22
|
+
|
|
23
|
+
### App Router-directorystructuur
|
|
24
|
+
```
|
|
25
|
+
app/
|
|
26
|
+
├── (auth)/ # Routegroep — geen URL-segment
|
|
27
|
+
│ ├── login/
|
|
28
|
+
│ │ └── page.tsx
|
|
29
|
+
│ └── layout.tsx # Auth-specifieke layout
|
|
30
|
+
├── (dashboard)/
|
|
31
|
+
│ ├── dashboard/
|
|
32
|
+
│ │ ├── page.tsx # Server Component standaard
|
|
33
|
+
│ │ └── loading.tsx # Suspense-grens UI
|
|
34
|
+
│ └── layout.tsx
|
|
35
|
+
├── api/
|
|
36
|
+
│ └── webhooks/
|
|
37
|
+
│ └── stripe/
|
|
38
|
+
│ └── route.ts # Route Handler
|
|
39
|
+
├── layout.tsx # Root layout (vereist)
|
|
40
|
+
└── page.tsx # Startpagina
|
|
41
|
+
components/
|
|
42
|
+
├── ui/ # Presentationeel (kan server of client zijn)
|
|
43
|
+
└── forms/ # Altijd client components (useState/events)
|
|
44
|
+
lib/
|
|
45
|
+
├── auth.ts
|
|
46
|
+
├── db.ts
|
|
47
|
+
└── actions/ # Server Actions
|
|
48
|
+
└── user.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Server vs. Client Components
|
|
52
|
+
```tsx
|
|
53
|
+
// Server Component (standaard) — draait op server, wordt nooit naar client gestuurd
|
|
54
|
+
// Kan: await fetch, DB lezen, omgevingsvariabelen benaderen
|
|
55
|
+
// Kan niet: useState, useEffect, browser API's, event handlers
|
|
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 — voeg 'use client'-directive toe
|
|
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
|
+
Regel: gebruik standaard Server Components. Voeg `'use client'` alleen toe wanneer je interactiviteit, browser API's of React hooks nodig hebt.
|
|
72
|
+
|
|
73
|
+
### Datafetchingpatronen
|
|
74
|
+
```tsx
|
|
75
|
+
// Server Component — directe async/await, geen useEffect, geen 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
|
+
// Deduplicatie — React cache() wikkelt een functie zodat meerdere components
|
|
82
|
+
// die het in één render aanroepen één fetch delen
|
|
83
|
+
import { cache } from 'react'
|
|
84
|
+
export const getUser = cache(async (id: string) => {
|
|
85
|
+
return db.user.findUnique({ where: { id } })
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Statische generatie met revalidatie
|
|
89
|
+
async function getProducts() {
|
|
90
|
+
const res = await fetch('https://api.example.com/products', {
|
|
91
|
+
next: { revalidate: 3600 }, // ISR — elke uur revalideren
|
|
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
|
+
// In een 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">Opslaan</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 (root-niveau)
|
|
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
|
+
### Parallelle en onderscheppende routes
|
|
173
|
+
```
|
|
174
|
+
app/
|
|
175
|
+
├── @modal/ # Parallelle route — geeft naast hoofdinhoud weer
|
|
176
|
+
│ └── (.)photo/ # Onderscheppende route — onderschept /photo/[id]
|
|
177
|
+
│ └── [id]/
|
|
178
|
+
│ └── page.tsx
|
|
179
|
+
├── photo/
|
|
180
|
+
│ └── [id]/
|
|
181
|
+
│ └── page.tsx
|
|
182
|
+
└── layout.tsx # Accepteert { children, modal } als props
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Fout- en laadgrenzen
|
|
186
|
+
```tsx
|
|
187
|
+
// app/dashboard/loading.tsx — getoond tijdens Suspense
|
|
188
|
+
export default function Loading() {
|
|
189
|
+
return <DashboardSkeleton />
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// app/dashboard/error.tsx — foutgrens voor dit 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}>Opnieuw proberen</button>
|
|
199
|
+
</div>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Omgevingsvariabelen
|
|
205
|
+
- `NEXT_PUBLIC_*` — blootgesteld aan de browser
|
|
206
|
+
- Alle andere — alleen server (nooit toegankelijk in Client Components)
|
|
207
|
+
- Importeer nooit een alleen-server omgevingsvariabele in een Client Component — het retourneert stilletjes `undefined`
|
|
208
|
+
|
|
209
|
+
## Voorbeeld
|
|
210
|
+
|
|
211
|
+
**Gebruiker:** Voeg een gepagineerde blogberichtenpagina toe op `/blog` die ophaalt vanuit PostgreSQL, met een "Nieuw bericht"-modal die opent op `/blog/new` maar niet wegnavigeert van de berichtenlijst.
|
|
212
|
+
|
|
213
|
+
**Verwachte output:**
|
|
214
|
+
- `app/blog/page.tsx` — Server Component, haalt berichten op met `db.post.findMany`, geeft `<PostList>` + `<Link href="/blog/new">` weer
|
|
215
|
+
- `app/@modal/(.)blog/new/page.tsx` — Onderscheppende route die een `<NewPostModal>` Client Component toont
|
|
216
|
+
- `app/blog/new/page.tsx` — Volledige paginafallback voor directe navigatie
|
|
217
|
+
- `app/layout.tsx` — Bijgewerkt om `modal`-parallelle routesleuf te accepteren en naast `children` weer te geven
|
|
218
|
+
- `lib/actions/post.ts` — `createPost` Server Action met Zod-validatie + `revalidatePath('/blog')`
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
> **Werk met ons:** Claudient wordt ondersteund door [Uitbreiden](https://uitbreiden.com/) — we bouwen AI-producten en B2B-oplossingen met ontwikkelaarsgemeenschappen. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../django.md).
|
|
2
|
+
|
|
3
|
+
# Django Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- Ein Django-Projekt mit ORM-Modellen, Migrationen und Views aufbauen
|
|
7
|
+
- Django REST Framework (DRF) Serializer, Viewsets und Router einrichten
|
|
8
|
+
- Benutzerdefinierte Modell-Manager oder QuerySet-Methoden schreiben
|
|
9
|
+
- Django-Signale für entkoppelte Nebeneffekte verwenden
|
|
10
|
+
- Celery für asynchrone Aufgaben in einem Django-Projekt einrichten
|
|
11
|
+
- Das Django-Admin anpassen
|
|
12
|
+
- Tests mit `django.test.TestCase` oder `pytest-django` schreiben
|
|
13
|
+
|
|
14
|
+
## Wann NICHT verwenden
|
|
15
|
+
- Async-first APIs — stattdessen FastAPI Skill verwenden
|
|
16
|
+
- Microservices, die Django's ORM oder Admin nicht benötigen
|
|
17
|
+
- Einfache Skripte oder CLIs — einfaches Python oder Typer
|
|
18
|
+
- Wenn das Projekt bereits FastAPI oder Flask verwendet
|
|
19
|
+
|
|
20
|
+
## Anweisungen
|
|
21
|
+
|
|
22
|
+
### Projekt-Layout
|
|
23
|
+
```
|
|
24
|
+
project_name/
|
|
25
|
+
├── manage.py
|
|
26
|
+
├── config/
|
|
27
|
+
│ ├── settings/
|
|
28
|
+
│ │ ├── base.py
|
|
29
|
+
│ │ ├── development.py
|
|
30
|
+
│ │ └── production.py
|
|
31
|
+
│ ├── urls.py
|
|
32
|
+
│ └── wsgi.py
|
|
33
|
+
├── apps/
|
|
34
|
+
│ └── users/
|
|
35
|
+
│ ├── models.py
|
|
36
|
+
│ ├── serializers.py
|
|
37
|
+
│ ├── views.py
|
|
38
|
+
│ ├── urls.py
|
|
39
|
+
│ ├── admin.py
|
|
40
|
+
│ ├── managers.py
|
|
41
|
+
│ └── tests/
|
|
42
|
+
└── requirements/
|
|
43
|
+
├── base.txt
|
|
44
|
+
├── development.txt
|
|
45
|
+
└── production.txt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Einstellungen aufteilen
|
|
49
|
+
```python
|
|
50
|
+
# config/settings/base.py
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
53
|
+
|
|
54
|
+
INSTALLED_APPS = [
|
|
55
|
+
"django.contrib.admin",
|
|
56
|
+
"django.contrib.auth",
|
|
57
|
+
"django.contrib.contenttypes",
|
|
58
|
+
"rest_framework",
|
|
59
|
+
"apps.users",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
AUTH_USER_MODEL = "users.User" # Immer von Anfang an ein benutzerdefiniertes User-Modell setzen
|
|
63
|
+
|
|
64
|
+
# config/settings/production.py
|
|
65
|
+
from .base import *
|
|
66
|
+
DEBUG = False
|
|
67
|
+
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
|
|
68
|
+
DATABASES = {"default": env.db("DATABASE_URL")}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Benutzerdefiniertes User-Modell
|
|
72
|
+
```python
|
|
73
|
+
# apps/users/models.py — vor der ersten Migration einrichten, danach nie mehr ändern
|
|
74
|
+
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
|
75
|
+
from django.db import models
|
|
76
|
+
from .managers import UserManager
|
|
77
|
+
|
|
78
|
+
class User(AbstractBaseUser, PermissionsMixin):
|
|
79
|
+
email = models.EmailField(unique=True)
|
|
80
|
+
is_staff = models.BooleanField(default=False)
|
|
81
|
+
is_active = models.BooleanField(default=True)
|
|
82
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
83
|
+
|
|
84
|
+
USERNAME_FIELD = "email"
|
|
85
|
+
REQUIRED_FIELDS = []
|
|
86
|
+
objects = UserManager()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Benutzerdefinierter Manager
|
|
90
|
+
```python
|
|
91
|
+
# apps/users/managers.py
|
|
92
|
+
from django.contrib.auth.base_user import BaseUserManager
|
|
93
|
+
|
|
94
|
+
class UserManager(BaseUserManager):
|
|
95
|
+
def create_user(self, email: str, password: str, **extra_fields):
|
|
96
|
+
if not email:
|
|
97
|
+
raise ValueError("Email is required")
|
|
98
|
+
email = self.normalize_email(email)
|
|
99
|
+
user = self.model(email=email, **extra_fields)
|
|
100
|
+
user.set_password(password)
|
|
101
|
+
user.save()
|
|
102
|
+
return user
|
|
103
|
+
|
|
104
|
+
def create_superuser(self, email: str, password: str, **extra_fields):
|
|
105
|
+
extra_fields.setdefault("is_staff", True)
|
|
106
|
+
extra_fields.setdefault("is_superuser", True)
|
|
107
|
+
return self.create_user(email, password, **extra_fields)
|
|
108
|
+
|
|
109
|
+
def active(self):
|
|
110
|
+
return self.get_queryset().filter(is_active=True)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### DRF-Serializer
|
|
114
|
+
```python
|
|
115
|
+
# apps/users/serializers.py
|
|
116
|
+
from rest_framework import serializers
|
|
117
|
+
from .models import User
|
|
118
|
+
|
|
119
|
+
class UserCreateSerializer(serializers.ModelSerializer):
|
|
120
|
+
password = serializers.CharField(write_only=True, min_length=8)
|
|
121
|
+
|
|
122
|
+
class Meta:
|
|
123
|
+
model = User
|
|
124
|
+
fields = ["id", "email", "password"]
|
|
125
|
+
|
|
126
|
+
def create(self, validated_data: dict) -> User:
|
|
127
|
+
return User.objects.create_user(**validated_data)
|
|
128
|
+
|
|
129
|
+
class UserSerializer(serializers.ModelSerializer):
|
|
130
|
+
class Meta:
|
|
131
|
+
model = User
|
|
132
|
+
fields = ["id", "email", "created_at"]
|
|
133
|
+
read_only_fields = ["id", "created_at"]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### DRF-ViewSets
|
|
137
|
+
```python
|
|
138
|
+
# apps/users/views.py
|
|
139
|
+
from rest_framework import viewsets, permissions, status
|
|
140
|
+
from rest_framework.decorators import action
|
|
141
|
+
from rest_framework.response import Response
|
|
142
|
+
from .models import User
|
|
143
|
+
from .serializers import UserSerializer, UserCreateSerializer
|
|
144
|
+
|
|
145
|
+
class UserViewSet(viewsets.ModelViewSet):
|
|
146
|
+
queryset = User.objects.active()
|
|
147
|
+
permission_classes = [permissions.IsAuthenticated]
|
|
148
|
+
|
|
149
|
+
def get_serializer_class(self):
|
|
150
|
+
if self.action == "create":
|
|
151
|
+
return UserCreateSerializer
|
|
152
|
+
return UserSerializer
|
|
153
|
+
|
|
154
|
+
def get_permissions(self):
|
|
155
|
+
if self.action == "create":
|
|
156
|
+
return [permissions.AllowAny()]
|
|
157
|
+
return super().get_permissions()
|
|
158
|
+
|
|
159
|
+
@action(detail=False, methods=["get"])
|
|
160
|
+
def me(self, request):
|
|
161
|
+
return Response(UserSerializer(request.user).data)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Router-Setup
|
|
165
|
+
```python
|
|
166
|
+
# apps/users/urls.py
|
|
167
|
+
from rest_framework.routers import DefaultRouter
|
|
168
|
+
from .views import UserViewSet
|
|
169
|
+
|
|
170
|
+
router = DefaultRouter()
|
|
171
|
+
router.register("users", UserViewSet, basename="user")
|
|
172
|
+
urlpatterns = router.urls
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Signale
|
|
176
|
+
```python
|
|
177
|
+
# apps/users/signals.py — Signale nur für wirklich entkoppelte Nebeneffekte verwenden
|
|
178
|
+
from django.db.models.signals import post_save
|
|
179
|
+
from django.dispatch import receiver
|
|
180
|
+
from .models import User
|
|
181
|
+
|
|
182
|
+
@receiver(post_save, sender=User)
|
|
183
|
+
def send_welcome_email(sender, instance: User, created: bool, **kwargs):
|
|
184
|
+
if created:
|
|
185
|
+
send_email_task.delay(instance.email, "welcome")
|
|
186
|
+
|
|
187
|
+
# apps/users/apps.py
|
|
188
|
+
class UsersConfig(AppConfig):
|
|
189
|
+
name = "apps.users"
|
|
190
|
+
def ready(self):
|
|
191
|
+
import apps.users.signals # noqa: F401
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Celery
|
|
195
|
+
```python
|
|
196
|
+
# config/celery.py
|
|
197
|
+
from celery import Celery
|
|
198
|
+
import os
|
|
199
|
+
|
|
200
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
|
|
201
|
+
app = Celery("project_name")
|
|
202
|
+
app.config_from_object("django.conf:settings", namespace="CELERY")
|
|
203
|
+
app.autodiscover_tasks()
|
|
204
|
+
|
|
205
|
+
# apps/users/tasks.py
|
|
206
|
+
from config.celery import app
|
|
207
|
+
|
|
208
|
+
@app.task(bind=True, max_retries=3)
|
|
209
|
+
def send_email_task(self, to_email: str, template: str):
|
|
210
|
+
try:
|
|
211
|
+
# E-Mail senden
|
|
212
|
+
pass
|
|
213
|
+
except Exception as exc:
|
|
214
|
+
raise self.retry(exc=exc, countdown=60)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Admin-Anpassung
|
|
218
|
+
```python
|
|
219
|
+
# apps/users/admin.py
|
|
220
|
+
from django.contrib import admin
|
|
221
|
+
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
|
222
|
+
from .models import User
|
|
223
|
+
|
|
224
|
+
@admin.register(User)
|
|
225
|
+
class UserAdmin(BaseUserAdmin):
|
|
226
|
+
list_display = ["email", "is_active", "is_staff", "created_at"]
|
|
227
|
+
list_filter = ["is_active", "is_staff"]
|
|
228
|
+
search_fields = ["email"]
|
|
229
|
+
ordering = ["-created_at"]
|
|
230
|
+
fieldsets = (
|
|
231
|
+
(None, {"fields": ("email", "password")}),
|
|
232
|
+
("Permissions", {"fields": ("is_active", "is_staff", "is_superuser", "groups")}),
|
|
233
|
+
)
|
|
234
|
+
add_fieldsets = (
|
|
235
|
+
(None, {"fields": ("email", "password1", "password2")}),
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### QuerySet-Optimierung
|
|
240
|
+
```python
|
|
241
|
+
# Immer select_related für FK-Felder, prefetch_related für M2M/Reverse FK
|
|
242
|
+
posts = Post.objects.select_related("author").prefetch_related("tags").filter(published=True)
|
|
243
|
+
|
|
244
|
+
# only() oder defer() für große Modelle verwenden, wenn nur bestimmte Felder benötigt werden
|
|
245
|
+
emails = User.objects.filter(is_active=True).only("email")
|
|
246
|
+
|
|
247
|
+
# values() für schreibgeschützte Aggregationen verwenden — überspringt ORM-Objektkonstruktion
|
|
248
|
+
counts = Order.objects.values("status").annotate(count=Count("id"))
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Tests
|
|
252
|
+
```python
|
|
253
|
+
# pytest-django-Stil
|
|
254
|
+
import pytest
|
|
255
|
+
from rest_framework.test import APIClient
|
|
256
|
+
|
|
257
|
+
@pytest.fixture
|
|
258
|
+
def api_client():
|
|
259
|
+
return APIClient()
|
|
260
|
+
|
|
261
|
+
@pytest.fixture
|
|
262
|
+
def authenticated_client(api_client, user):
|
|
263
|
+
api_client.force_authenticate(user=user)
|
|
264
|
+
return api_client
|
|
265
|
+
|
|
266
|
+
@pytest.mark.django_db
|
|
267
|
+
def test_create_user(api_client):
|
|
268
|
+
resp = api_client.post("/api/users/", {"email": "a@b.com", "password": "strongpass"})
|
|
269
|
+
assert resp.status_code == 201
|
|
270
|
+
assert resp.data["email"] == "a@b.com"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Beispiel
|
|
274
|
+
|
|
275
|
+
**Benutzer:** Ein `Post`-Modell zu einem Django-Projekt mit DRF hinzufügen, einschließlich Listen-/Erstell-/Abruf-Endpunkte, paginierte Ergebnisse und Filter nach `published=True`.
|
|
276
|
+
|
|
277
|
+
**Erwartete Ausgabe:**
|
|
278
|
+
- `models.py` — `Post` mit `title`, `body`, `author` (FK zu User), `published`, `created_at`
|
|
279
|
+
- `serializers.py` — `PostSerializer` mit schreibgeschütztem `author` (verschachtelt), beschreibbarem `title`/`body`/`published`
|
|
280
|
+
- `views.py` — `PostViewSet` mit `queryset` gefiltert auf `published=True` für nicht authentifizierte Benutzer, `IsAuthenticatedOrReadOnly`-Berechtigung, `PageNumberPagination`
|
|
281
|
+
- `urls.py` — Router registriert bei `/api/posts/`
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. [uitbreiden.com](https://uitbreiden.com/)
|