fix: les 6
This commit is contained in:
@@ -1,234 +1,58 @@
|
||||
# Les 5: TypeScript voor React
|
||||
# Les 5: Next.js — Het React Framework (Part 1)
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 4-9)
|
||||
**Deel 2: Technical Foundations** (Les 4-8)
|
||||
|
||||
## Beschrijving
|
||||
Verdieping in TypeScript met focus op React-patronen. Studenten leren generics, utility types, en hoe je React components, hooks, events en API calls correct typt. Voorbereiding op Les 6 waar ze met Next.js aan de slag gaan.
|
||||
|
||||
**Voorkennis:** Les 4 (TypeScript Fundamentals) — basic types, interfaces, union types, type aliases, functies typen.
|
||||
Introductie Next.js voor React developers. App Router, routing, server/client components, data fetching. Hands-on: QuickPoll app Part 1 (stap 0-3) klassikaal bouwen.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~45 min theorie)
|
||||
|
||||
### Generics (20 min)
|
||||
- Waarom generics? Herbruikbare, type-safe code
|
||||
- `Array<T>`, `Promise<T>` — generics die ze al kennen
|
||||
- Eigen generics schrijven: `function getFirst<T>(items: T[]): T`
|
||||
- Generics met constraints: `<T extends { id: string }>`
|
||||
- `keyof` operator: `function getValue<T, K extends keyof T>(obj: T, key: K): T[K]`
|
||||
|
||||
```typescript
|
||||
// Generic functie
|
||||
function wrapInArray<T>(value: T): T[] {
|
||||
return [value];
|
||||
}
|
||||
|
||||
wrapInArray("hello"); // string[]
|
||||
wrapInArray(42); // number[]
|
||||
|
||||
// Met constraint
|
||||
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
||||
return obj[key];
|
||||
}
|
||||
```
|
||||
- Waarom Next.js? React is een library, Next.js is het framework
|
||||
- Create-next-app setup met TypeScript + Tailwind
|
||||
- App Router: folder-based routing (page.tsx = route)
|
||||
- Layouts: root layout, nested layouts
|
||||
- Dynamic Routes: [id] met Promise-based params (Next.js 15)
|
||||
- Server Components vs Client Components
|
||||
- "use client" directive
|
||||
- Data Fetching in async Server Components
|
||||
- Server Actions introductie ("use server")
|
||||
- Route Groups ((marketing))
|
||||
- Project structuur best practices
|
||||
|
||||
---
|
||||
|
||||
### Utility Types (15 min)
|
||||
- `Partial<T>` — alle properties optioneel (handig voor updates)
|
||||
- `Pick<T, K>` — selecteer specifieke properties
|
||||
- `Omit<T, K>` — alles behalve specifieke properties
|
||||
- `Record<K, V>` — key-value mapping
|
||||
- Praktisch voorbeeld: `updateUser(id: string, data: Partial<User>)`
|
||||
## Lesopdracht (120 min, klassikaal)
|
||||
|
||||
```typescript
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
age: number;
|
||||
}
|
||||
### QuickPoll App Part 1 — samen met Tim
|
||||
|
||||
// Partial: voor update functies
|
||||
function updateUser(id: string, updates: Partial<User>): User { ... }
|
||||
updateUser("1", { name: "Tim" }); // alleen name updaten
|
||||
- **Stap 0:** Setup (create-next-app, npm install, dev server)
|
||||
- **Stap 1:** Layout met navigatie (Tailwind styling)
|
||||
- **Stap 2:** Homepage met polls lijst (server component)
|
||||
- **Stap 3:** API route GET single poll (dynamic route, 404 handling)
|
||||
|
||||
// Omit: voor create functies (id wordt server-side gegenereerd)
|
||||
type CreateUserInput = Omit<User, "id">;
|
||||
|
||||
// Pick: voor specifieke views
|
||||
type UserPreview = Pick<User, "id" | "name">;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### React Props Typen (20 min)
|
||||
- Interface voor component props
|
||||
- Children typen met `React.ReactNode`
|
||||
- Callback props: `onClick: () => void`, `onChange: (value: string) => void`
|
||||
- Spread props en prop forwarding
|
||||
- Default values met destructuring
|
||||
|
||||
```typescript
|
||||
interface CardProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
variant?: "default" | "highlighted";
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function Card({ title, children, variant = "default", onClose }: CardProps) {
|
||||
return (
|
||||
<div className={`card card-${variant}`}>
|
||||
<h2>{title}</h2>
|
||||
{onClose && <button onClick={onClose}>×</button>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### useState & useEffect Typen (15 min)
|
||||
- Type inference bij useState: `useState(0)` → number
|
||||
- Explicit types: `useState<User | null>(null)`
|
||||
- Arrays: `useState<Product[]>([])`
|
||||
- useEffect met async patterns
|
||||
|
||||
```typescript
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [loading, setLoading] = useState(false); // inference: boolean
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setLoading(true);
|
||||
const response = await fetch("/api/users");
|
||||
const data: User[] = await response.json();
|
||||
setUsers(data);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Event Handlers Typen (10 min)
|
||||
- `React.ChangeEvent<HTMLInputElement>`
|
||||
- `React.FormEvent<HTMLFormElement>`
|
||||
- `React.MouseEvent<HTMLButtonElement>`
|
||||
- Tip: hover in Cursor om het juiste event type te vinden
|
||||
|
||||
```typescript
|
||||
function SearchForm() {
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
console.log("Searching:", query);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input value={query} onChange={handleChange} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### API Responses & Async Typen (15 min)
|
||||
- `Promise<T>` voor async functies
|
||||
- API response types definiëren
|
||||
- Error handling met types
|
||||
- Fetch wrapper met generics
|
||||
|
||||
```typescript
|
||||
interface ApiResponse<T> {
|
||||
data: T;
|
||||
status: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Gebruik
|
||||
const { data: users } = await fetchApi<User[]>("/api/users");
|
||||
const { data: product } = await fetchApi<Product>("/api/products/1");
|
||||
```
|
||||
Huiswerk: Stap 0-3 zelfstandig afmaken
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Cursor (Student Plan)
|
||||
- Next.js 15
|
||||
- Cursor
|
||||
- TypeScript
|
||||
- React (via CDN of Vite)
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (75 min)
|
||||
|
||||
### Typed React Dashboard
|
||||
|
||||
Studenten bouwen een kleine React-app met volledig getypte components:
|
||||
|
||||
**Opdracht:** Bouw een Product Dashboard met:
|
||||
|
||||
1. **`ProductCard` component** — props: Product interface, onAddToCart callback
|
||||
2. **`ProductList` component** — props: Product[], filterCategory (union type)
|
||||
3. **`SearchBar` component** — props: query string, onChange handler (getypt event)
|
||||
4. **`useProducts` custom hook** — fetch products, return `{ products, loading, error }`
|
||||
5. **Alle types in een apart `types.ts` bestand**
|
||||
|
||||
**Vereisten:**
|
||||
- Geen `any` toegestaan
|
||||
- Alle event handlers correct getypt
|
||||
- useState met expliciete types waar nodig
|
||||
- Minstens 1 generic functie (bijv. een `sortBy<T>` of `filterBy<T>`)
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Extend het Dashboard
|
||||
|
||||
Bouw voort op de lesopdracht:
|
||||
|
||||
1. **Shopping Cart** toevoegen met getypte state (`CartItem[]`)
|
||||
2. **API simulatie** — maak een `fetchProducts()` functie met `Promise<Product[]>`
|
||||
3. **Utility types gebruiken** — `Partial<Product>` voor updates, `Omit<Product, "id">` voor create
|
||||
4. **Bonus: Generic `DataTable<T>` component** — werkt met elke array van objecten
|
||||
|
||||
### Deliverable
|
||||
- Werkend React project met TypeScript
|
||||
- Alle components volledig getypt
|
||||
- `npm run check` = 0 errors
|
||||
- Tailwind CSS
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Generics schrijven en toepassen
|
||||
- Utility types gebruiken (Partial, Pick, Omit, Record)
|
||||
- React component props correct typen
|
||||
- useState en useEffect met types gebruiken
|
||||
- Event handlers typen (ChangeEvent, FormEvent, MouseEvent)
|
||||
- Async functies en API responses typen met Promise<T>
|
||||
- Een custom hook schrijven met correcte return types
|
||||
- Uitleggen wat Next.js toevoegt aan React
|
||||
- Een Next.js project opzetten met App Router
|
||||
- Verschil tussen Server en Client Components
|
||||
- File-based routing gebruiken
|
||||
- Dynamic routes met parameters maken
|
||||
- Data fetchen in Server Components
|
||||
|
||||
@@ -1,365 +1,59 @@
|
||||
# Les 6: Next.js Fundamentals 1 - SSR & Routing
|
||||
# Les 6: Next.js — QuickPoll Vervolg (Part 2)
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 5-9)
|
||||
**Deel 2: Technical Foundations** (Les 4-8)
|
||||
|
||||
## Beschrijving
|
||||
Introductie tot Next.js voor React developers. Leer wat Server-Side Rendering is, hoe de App Router werkt, en bouw je eerste Next.js applicatie met meerdere pagina's.
|
||||
Vervolg Next.js: API Routes, Middleware, Deployment. Hands-on: QuickPoll app Part 2 (stap 4-7) klassikaal afbouwen en deployen.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~30-40 min)
|
||||
|
||||
### Wat is Next.js?
|
||||
|
||||
**React met superpowers:**
|
||||
- React = library voor UI components
|
||||
- Next.js = framework dat React complete maakt
|
||||
|
||||
**Next.js voegt toe:**
|
||||
- Routing (geen extra library nodig)
|
||||
- Server-Side Rendering (SEO, performance)
|
||||
- API routes (backend in je frontend project)
|
||||
- Optimalisaties (images, fonts, bundling)
|
||||
- Recap Les 5 + Q&A
|
||||
- API Route Handlers dieper: GET, POST met NextResponse, request body parsen
|
||||
- Middleware: src/middleware.ts, matcher config, use cases
|
||||
- Environment Variables: .env.local, NEXT_PUBLIC_ prefix
|
||||
- Loading, Error, Not-Found special files
|
||||
- next/image, next/link optimalisaties
|
||||
- Metadata type voor SEO
|
||||
- Deployment op Vercel (git push → auto deploy)
|
||||
- Cursor/AI workflow: .cursorrules, Cmd+K, Cmd+L
|
||||
|
||||
---
|
||||
|
||||
### Server-Side Rendering (SSR) vs Client-Side Rendering (CSR)
|
||||
## Lesopdracht (120 min, klassikaal)
|
||||
|
||||
**Client-Side Rendering (gewone React):**
|
||||
```
|
||||
1. Browser vraagt pagina
|
||||
2. Server stuurt lege HTML + JavaScript
|
||||
3. JavaScript laadt in browser
|
||||
4. JavaScript rendert de pagina
|
||||
5. Gebruiker ziet content
|
||||
```
|
||||
**Probleem:** Lege pagina tot JS klaar is. Google ziet ook lege pagina.
|
||||
### QuickPoll App Part 2 — samen met Tim
|
||||
|
||||
**Server-Side Rendering (Next.js):**
|
||||
```
|
||||
1. Browser vraagt pagina
|
||||
2. Server rendert HTML met content
|
||||
3. Browser toont direct de content
|
||||
4. JavaScript laadt (hydration)
|
||||
5. Pagina wordt interactief
|
||||
```
|
||||
**Voordeel:** Direct content zichtbaar, beter voor SEO.
|
||||
- **Stap 4:** API POST vote route (validation, votePoll)
|
||||
- **Stap 5:** Poll detail pagina (server component met data)
|
||||
- **Stap 6:** VoteForm (client component, fetch, results display)
|
||||
- **Stap 7:** Loading, error, not-found states
|
||||
- **Bonus:** Create poll pagina
|
||||
- **Deploy naar Vercel**
|
||||
|
||||
---
|
||||
|
||||
### De App Router
|
||||
|
||||
Next.js 13+ gebruikt de "App Router" met file-based routing:
|
||||
|
||||
```
|
||||
app/
|
||||
├── page.tsx → /
|
||||
├── about/
|
||||
│ └── page.tsx → /about
|
||||
├── products/
|
||||
│ ├── page.tsx → /products
|
||||
│ └── [id]/
|
||||
│ └── page.tsx → /products/123
|
||||
└── layout.tsx → Wrapper voor alle pagina's
|
||||
```
|
||||
|
||||
**De regel:** Elke `page.tsx` wordt een route!
|
||||
|
||||
---
|
||||
|
||||
### Project Aanmaken
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest mijn-app
|
||||
|
||||
# Antwoorden:
|
||||
# ✔ TypeScript? → Yes
|
||||
# ✔ ESLint? → Yes
|
||||
# ✔ Tailwind CSS? → Yes
|
||||
# ✔ `src/` directory? → Yes
|
||||
# ✔ App Router? → Yes
|
||||
# ✔ Customize import alias? → No
|
||||
|
||||
cd mijn-app
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open `http://localhost:3000` - je app draait!
|
||||
|
||||
---
|
||||
|
||||
### Pagina's Maken
|
||||
|
||||
**Simpele pagina (`app/about/page.tsx`):**
|
||||
```tsx
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="text-3xl font-bold">Over Ons</h1>
|
||||
<p>Welkom op de about pagina!</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Dat is alles! Ga naar `/about` en je ziet je pagina.
|
||||
|
||||
---
|
||||
|
||||
### Layouts
|
||||
|
||||
Layouts wrappen pagina's en blijven behouden tijdens navigatie.
|
||||
|
||||
**Root Layout (`app/layout.tsx`):**
|
||||
```tsx
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="nl">
|
||||
<body>
|
||||
<header className="p-4 bg-blue-500 text-white">
|
||||
<nav>Mijn App</nav>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
<footer className="p-4 bg-gray-200">
|
||||
© 2024
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nested Layouts
|
||||
|
||||
Je kunt layouts nesten voor secties:
|
||||
|
||||
```
|
||||
app/
|
||||
├── layout.tsx → Root layout
|
||||
├── page.tsx → Homepage
|
||||
└── dashboard/
|
||||
├── layout.tsx → Dashboard layout (sidebar)
|
||||
└── page.tsx → Dashboard pagina
|
||||
```
|
||||
|
||||
**Dashboard Layout (`app/dashboard/layout.tsx`):**
|
||||
```tsx
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="flex">
|
||||
<aside className="w-64 bg-gray-100 p-4">
|
||||
<nav>Sidebar hier</nav>
|
||||
</aside>
|
||||
<div className="flex-1">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dynamic Routes
|
||||
|
||||
Voor pagina's met parameters zoals `/products/123`:
|
||||
|
||||
**Folder structuur:**
|
||||
```
|
||||
app/products/[id]/page.tsx
|
||||
```
|
||||
|
||||
**De pagina:**
|
||||
```tsx
|
||||
interface Props {
|
||||
params: { id: string }
|
||||
}
|
||||
|
||||
export default function ProductPage({ params }: Props) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Product {params.id}</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Nu werkt `/products/1`, `/products/abc`, etc.
|
||||
|
||||
---
|
||||
|
||||
### Link Component
|
||||
|
||||
Gebruik `Link` voor client-side navigatie (snel, geen page reload):
|
||||
|
||||
```tsx
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Navigation() {
|
||||
return (
|
||||
<nav className="flex gap-4">
|
||||
<Link href="/">Home</Link>
|
||||
<Link href="/about">About</Link>
|
||||
<Link href="/products">Products</Link>
|
||||
<Link href="/products/123">Product 123</Link>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Niet doen:**
|
||||
```tsx
|
||||
// ❌ Dit werkt maar is langzamer
|
||||
<a href="/about">About</a>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Special Files
|
||||
|
||||
Next.js heeft speciale bestanden:
|
||||
|
||||
| File | Doel |
|
||||
|------|------|
|
||||
| `page.tsx` | De pagina content |
|
||||
| `layout.tsx` | Wrapper, blijft behouden |
|
||||
| `loading.tsx` | Loading state |
|
||||
| `error.tsx` | Error boundary |
|
||||
| `not-found.tsx` | 404 pagina |
|
||||
|
||||
**Loading state (`app/products/loading.tsx`):**
|
||||
```tsx
|
||||
export default function Loading() {
|
||||
return <div>Laden...</div>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Metadata (SEO)
|
||||
|
||||
Voeg metadata toe per pagina:
|
||||
|
||||
```tsx
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Over Ons | Mijn App',
|
||||
description: 'Lees meer over ons bedrijf',
|
||||
}
|
||||
|
||||
export default function AboutPage() {
|
||||
return <h1>Over Ons</h1>
|
||||
}
|
||||
```
|
||||
Huiswerk: App afmaken, deployen op Vercel, bonus features
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Next.js 14
|
||||
- Next.js 15
|
||||
- Cursor
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- OpenCode/WebStorm
|
||||
- Vercel
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
|
||||
### Bouw Multi-Page Next.js App
|
||||
|
||||
**Deel 1: Project Setup (20 min)**
|
||||
|
||||
1. `npx create-next-app@latest my-shop` (TypeScript + Tailwind + App Router)
|
||||
2. Open in editor
|
||||
3. `npm run dev`
|
||||
4. Verifieer: `localhost:3000` werkt
|
||||
|
||||
**Deel 2: Pagina's Maken (40 min)**
|
||||
|
||||
Maak deze pagina's:
|
||||
1. `/` - Homepage met welkomstbericht
|
||||
2. `/about` - Over ons pagina
|
||||
3. `/products` - Producten overzicht
|
||||
4. `/contact` - Contact pagina
|
||||
|
||||
Elke pagina moet unieke content hebben.
|
||||
|
||||
**Deel 3: Layout met Navigatie (30 min)**
|
||||
|
||||
1. Maak Header component met navigatie (gebruik Link)
|
||||
2. Maak Footer component
|
||||
3. Voeg beide toe aan root layout
|
||||
4. Test: navigatie werkt zonder page reload
|
||||
|
||||
**Deel 4: Dynamic Route (30 min)**
|
||||
|
||||
1. Maak `/products/[id]` route
|
||||
2. Toon product ID op de pagina
|
||||
3. Maak links naar `/products/1`, `/products/2`, `/products/3`
|
||||
4. Test: alle links werken
|
||||
|
||||
**Bonus:** Voeg `loading.tsx` toe aan products folder
|
||||
|
||||
### Deliverable
|
||||
- Werkende Next.js app met 4+ pagina's
|
||||
- Navigatie met Link component
|
||||
- Dynamic route die werkt
|
||||
- Deploy naar Vercel
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Uitbreiden en Verdiepen
|
||||
|
||||
**Deel 1: Nested Layout (45 min)**
|
||||
|
||||
1. Maak `/dashboard` section met eigen layout
|
||||
2. Dashboard layout heeft sidebar
|
||||
3. Maak `/dashboard/settings` en `/dashboard/profile` pagina's
|
||||
4. Test: sidebar blijft bij navigatie binnen dashboard
|
||||
|
||||
**Deel 2: Special Files (45 min)**
|
||||
|
||||
1. Maak `loading.tsx` voor products (toon "Laden...")
|
||||
2. Maak `error.tsx` voor products (toon error message)
|
||||
3. Maak `not-found.tsx` in app root (custom 404)
|
||||
4. Test elk van deze states
|
||||
|
||||
**Deel 3: Metadata (30 min)**
|
||||
|
||||
1. Voeg metadata toe aan elke pagina (title, description)
|
||||
2. Bekijk in browser: `<head>` toont juiste meta tags
|
||||
3. Test met Lighthouse: SEO score
|
||||
|
||||
### Deliverable
|
||||
- App met nested layouts
|
||||
- Special files (loading, error, not-found)
|
||||
- Metadata op elke pagina
|
||||
- Screenshot van Lighthouse SEO score
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Uitleggen wat Next.js toevoegt aan React
|
||||
- Het verschil tussen SSR en CSR beschrijven
|
||||
- Een Next.js project opzetten met App Router
|
||||
- Pagina's maken met file-based routing
|
||||
- Layouts gebruiken voor herhalende elementen
|
||||
- Dynamic routes maken met parameters
|
||||
- Link component gebruiken voor navigatie
|
||||
- Special files toepassen (loading, error, not-found)
|
||||
- Metadata toevoegen voor SEO
|
||||
- API Route Handlers schrijven (GET, POST)
|
||||
- Middleware implementeren
|
||||
- Environment variables gebruiken
|
||||
- Special files (loading, error, not-found) toepassen
|
||||
- Een Next.js app deployen op Vercel
|
||||
- Cursor/AI workflow toepassen
|
||||
|
||||
@@ -1,412 +1,372 @@
|
||||
# Les 7: Next.js Fundamentals 2 - API Routes & Data Fetching
|
||||
# Les 7: Database Principles
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 5-9)
|
||||
**Deel 2: Technical Foundations** (Les 4-8)
|
||||
|
||||
## Beschrijving
|
||||
Leer data fetching in Next.js: Server Components, Client Components, API routes en React Query. Bouw een volledig werkende app met data.
|
||||
Leer de basisprincipes van relationele databases voordat we Supabase gaan gebruiken. Begrijp tabellen, relaties, keys en normalisatie - essentiële kennis voor elke developer.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
|
||||
### Server Components vs Client Components
|
||||
### Wat is een Relationele Database?
|
||||
|
||||
**Server Components (default in Next.js):**
|
||||
- Renderen op de server
|
||||
- Geen JavaScript naar de browser
|
||||
- Kunnen direct data fetchen (async/await)
|
||||
- Kunnen NIET: useState, useEffect, event handlers
|
||||
**Een database is:** Een georganiseerde verzameling van data.
|
||||
|
||||
**Client Components:**
|
||||
- Renderen in de browser
|
||||
- JavaScript naar de browser
|
||||
- Kunnen interactief zijn
|
||||
- Markeer met `'use client'` bovenaan
|
||||
**Relationeel betekent:** Data is opgeslagen in tabellen die aan elkaar gerelateerd zijn.
|
||||
|
||||
**Vergelijk het met Excel:**
|
||||
- Database = Excel workbook
|
||||
- Tabel = Excel sheet
|
||||
- Kolom = Excel kolom (field)
|
||||
- Rij = Excel rij (record)
|
||||
|
||||
---
|
||||
|
||||
### Wanneer Wat?
|
||||
### Tabellen, Kolommen en Rijen
|
||||
|
||||
```
|
||||
Server Component → Data tonen, geen interactie
|
||||
Client Component → Interactie nodig (forms, buttons, state)
|
||||
```
|
||||
**Voorbeeld: Users tabel**
|
||||
|
||||
| id | name | email | created_at |
|
||||
|----|------|-------|------------|
|
||||
| 1 | Tim | tim@email.com | 2024-01-15 |
|
||||
| 2 | Anna | anna@email.com | 2024-01-16 |
|
||||
| 3 | Jan | jan@email.com | 2024-01-17 |
|
||||
|
||||
**Terminologie:**
|
||||
- **Tabel:** users
|
||||
- **Kolommen:** id, name, email, created_at
|
||||
- **Rijen:** 3 records (Tim, Anna, Jan)
|
||||
- **Cell:** Eén specifieke waarde (bijv. "tim@email.com")
|
||||
|
||||
---
|
||||
|
||||
### Data Types
|
||||
|
||||
Elke kolom heeft een type:
|
||||
|
||||
| Type | Beschrijving | Voorbeeld |
|
||||
|------|--------------|-----------|
|
||||
| `text` / `varchar` | Tekst | "Tim", "Hello world" |
|
||||
| `integer` / `int` | Hele getallen | 1, 42, -5 |
|
||||
| `decimal` / `numeric` | Decimalen | 19.99, 3.14 |
|
||||
| `boolean` | True/False | true, false |
|
||||
| `timestamp` | Datum + tijd | 2024-01-15 14:30:00 |
|
||||
| `uuid` | Unieke identifier | a1b2c3d4-e5f6-... |
|
||||
|
||||
**Kies het juiste type:**
|
||||
- Prijs? → `decimal` (niet `integer`, want centen)
|
||||
- Is actief? → `boolean`
|
||||
- Naam? → `text`
|
||||
- Aantal? → `integer`
|
||||
|
||||
---
|
||||
|
||||
### Primary Keys
|
||||
|
||||
**Wat:** Een kolom die elke rij UNIEK identificeert.
|
||||
|
||||
**Regels:**
|
||||
- Moet uniek zijn per rij
|
||||
- Mag nooit NULL zijn
|
||||
- Verandert nooit
|
||||
|
||||
**Voorbeeld:**
|
||||
```tsx
|
||||
// Server Component - data ophalen
|
||||
async function ProductList() {
|
||||
const products = await fetchProducts() // Direct fetchen!
|
||||
return <ul>{products.map(p => <li>{p.name}</li>)}</ul>
|
||||
}
|
||||
```
|
||||
users
|
||||
------
|
||||
id (PRIMARY KEY) | name | email
|
||||
1 | Tim | tim@email.com
|
||||
2 | Anna | anna@email.com
|
||||
```
|
||||
|
||||
// Client Component - interactie
|
||||
'use client'
|
||||
function AddToCartButton({ productId }) {
|
||||
const [added, setAdded] = useState(false)
|
||||
return <button onClick={() => setAdded(true)}>Add</button>
|
||||
}
|
||||
**Waarom niet `email` als primary key?**
|
||||
- Emails kunnen veranderen
|
||||
- `id` is stabiel en snel
|
||||
|
||||
---
|
||||
|
||||
### Foreign Keys
|
||||
|
||||
**Wat:** Een kolom die verwijst naar de primary key van een andere tabel.
|
||||
|
||||
**Voorbeeld: Posts tabel**
|
||||
```
|
||||
posts
|
||||
------
|
||||
id | title | user_id (FOREIGN KEY → users.id)
|
||||
1 | "Mijn blog" | 1
|
||||
2 | "Hello world" | 1
|
||||
3 | "Tips" | 2
|
||||
```
|
||||
|
||||
**Wat zegt dit?**
|
||||
- Post 1 en 2 zijn van user 1 (Tim)
|
||||
- Post 3 is van user 2 (Anna)
|
||||
|
||||
---
|
||||
|
||||
### Relatie Types
|
||||
|
||||
**One-to-Many (1:N)** - Meest voorkomend!
|
||||
```
|
||||
Eén user → meerdere posts
|
||||
Eén category → meerdere products
|
||||
```
|
||||
|
||||
**One-to-One (1:1)** - Zeldzaam
|
||||
```
|
||||
Eén user → één profile
|
||||
```
|
||||
|
||||
**Many-to-Many (N:N)** - Via tussentabel
|
||||
```
|
||||
Posts ↔ Tags (een post heeft meerdere tags, een tag heeft meerdere posts)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Data Fetching in Server Components
|
||||
### One-to-Many Voorbeeld
|
||||
|
||||
Simpelweg `async/await` gebruiken:
|
||||
```
|
||||
users posts
|
||||
------ ------
|
||||
id | name id | title | user_id
|
||||
1 | Tim ←────────── 1 | "Blog 1" | 1
|
||||
2 | Anna ←────┬───── 2 | "Blog 2" | 1
|
||||
└───── 3 | "Tips" | 2
|
||||
```
|
||||
|
||||
```tsx
|
||||
// app/products/page.tsx
|
||||
interface Product {
|
||||
id: number
|
||||
name: string
|
||||
price: number
|
||||
}
|
||||
**Lees:** Tim heeft 2 posts, Anna heeft 1 post.
|
||||
|
||||
async function getProducts(): Promise<Product[]> {
|
||||
const res = await fetch('https://api.example.com/products')
|
||||
return res.json()
|
||||
}
|
||||
---
|
||||
|
||||
export default async function ProductsPage() {
|
||||
const products = await getProducts()
|
||||
### Many-to-Many met Tussentabel
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Producten</h1>
|
||||
<ul>
|
||||
{products.map(product => (
|
||||
<li key={product.id}>
|
||||
{product.name} - €{product.price}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
posts post_tags tags
|
||||
------ --------- ------
|
||||
id | title post_id | tag_id id | name
|
||||
1 | "React tips" 1 | 1 1 | "react"
|
||||
2 | "CSS guide" 1 | 2 2 | "frontend"
|
||||
2 | 2 3 | "css"
|
||||
2 | 3
|
||||
```
|
||||
|
||||
**Lees:**
|
||||
- Post 1 heeft tags: react, frontend
|
||||
- Post 2 heeft tags: frontend, css
|
||||
|
||||
---
|
||||
|
||||
### Normalisatie Basics
|
||||
|
||||
**Probleem: Data duplicatie**
|
||||
```
|
||||
orders (SLECHT)
|
||||
------
|
||||
id | customer_name | customer_email | product_name | price
|
||||
1 | Tim | tim@email.com | Laptop | 999
|
||||
2 | Tim | tim@email.com | Phone | 699
|
||||
3 | Anna | anna@email.com | Laptop | 999
|
||||
```
|
||||
|
||||
**Problemen:**
|
||||
- Tim's email staat 2x (als hij verandert: 2 plekken updaten)
|
||||
- "Laptop" en prijs staan 2x
|
||||
|
||||
---
|
||||
|
||||
### Genormaliseerde Versie
|
||||
|
||||
```
|
||||
users products orders
|
||||
------ -------- ------
|
||||
id | name | email id | name | price id | user_id | product_id
|
||||
1 | Tim | tim@... 1 | Laptop | 999 1 | 1 | 1
|
||||
2 | Anna | anna@... 2 | Phone | 699 2 | 1 | 2
|
||||
3 | 2 | 1
|
||||
```
|
||||
|
||||
**Voordelen:**
|
||||
- Elk gegeven staat 1x
|
||||
- Update op 1 plek
|
||||
- Minder opslagruimte
|
||||
|
||||
---
|
||||
|
||||
### NULL Values
|
||||
|
||||
**NULL = "geen waarde" (niet 0, niet "")**
|
||||
|
||||
```
|
||||
users
|
||||
------
|
||||
id | name | phone
|
||||
1 | Tim | 0612345678
|
||||
2 | Anna | NULL ← Geen telefoon bekend
|
||||
```
|
||||
|
||||
**Wanneer NULL toestaan?**
|
||||
- Optionele velden (phone, description)
|
||||
- Niet bij verplichte velden (name, email)
|
||||
|
||||
---
|
||||
|
||||
### Defaults
|
||||
|
||||
**Automatische waarde als je niks opgeeft:**
|
||||
|
||||
```
|
||||
todos
|
||||
------
|
||||
id | title | completed | created_at
|
||||
| | DEFAULT: false | DEFAULT: now()
|
||||
```
|
||||
|
||||
Bij `INSERT INTO todos (title) VALUES ('Test')`:
|
||||
```
|
||||
id | title | completed | created_at
|
||||
1 | Test | false | 2024-01-15 10:30:00
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### API Routes (Route Handlers)
|
||||
### Database Schema Tekenen
|
||||
|
||||
Bouw je eigen API in Next.js:
|
||||
**Tools:** draw.io, Excalidraw, pen en papier
|
||||
|
||||
**Folder structuur:**
|
||||
**Conventie:**
|
||||
```
|
||||
app/
|
||||
└── api/
|
||||
└── products/
|
||||
└── route.ts → /api/products
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ users │ │ posts │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ id (PK) │───┐ │ id (PK) │
|
||||
│ name │ │ │ title │
|
||||
│ email │ └────→│ user_id (FK) │
|
||||
│ created_at │ │ content │
|
||||
└──────────────┘ │ created_at │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
**GET request (`app/api/products/route.ts`):**
|
||||
```typescript
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
const products = [
|
||||
{ id: 1, name: 'Laptop', price: 999 },
|
||||
{ id: 2, name: 'Phone', price: 699 },
|
||||
]
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json(products)
|
||||
}
|
||||
```
|
||||
|
||||
**POST request:**
|
||||
```typescript
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json()
|
||||
|
||||
const newProduct = {
|
||||
id: Date.now(),
|
||||
name: body.name,
|
||||
price: body.price,
|
||||
}
|
||||
|
||||
products.push(newProduct)
|
||||
|
||||
return NextResponse.json(newProduct, { status: 201 })
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 'use client' Directive
|
||||
|
||||
Wanneer je interactie nodig hebt:
|
||||
|
||||
```tsx
|
||||
'use client' // MOET bovenaan!
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Count: {count}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### React Query (TanStack Query)
|
||||
|
||||
**Waarom React Query?**
|
||||
- Automatische caching
|
||||
- Loading en error states
|
||||
- Refetching (focus, interval)
|
||||
- Optimistic updates
|
||||
|
||||
**Installatie:**
|
||||
```bash
|
||||
npm install @tanstack/react-query
|
||||
```
|
||||
|
||||
**Setup Provider (`app/providers.tsx`):**
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const [queryClient] = useState(() => new QueryClient())
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**In Layout:**
|
||||
```tsx
|
||||
import { Providers } from './providers'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### useQuery - Data Ophalen
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
interface Product {
|
||||
id: number
|
||||
name: string
|
||||
price: number
|
||||
}
|
||||
|
||||
async function fetchProducts(): Promise<Product[]> {
|
||||
const res = await fetch('/api/products')
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function ProductList() {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['products'],
|
||||
queryFn: fetchProducts,
|
||||
})
|
||||
|
||||
if (isLoading) return <div>Laden...</div>
|
||||
if (error) return <div>Error: {error.message}</div>
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{data?.map(product => (
|
||||
<li key={product.id}>{product.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### useMutation - Data Wijzigen
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
interface NewProduct {
|
||||
name: string
|
||||
price: number
|
||||
}
|
||||
|
||||
async function createProduct(product: NewProduct) {
|
||||
const res = await fetch('/api/products', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(product),
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function AddProductForm() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: createProduct,
|
||||
onSuccess: () => {
|
||||
// Invalidate and refetch
|
||||
queryClient.invalidateQueries({ queryKey: ['products'] })
|
||||
},
|
||||
})
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
mutation.mutate({ name: 'New Product', price: 99 })
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? 'Toevoegen...' : 'Voeg toe'}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Combineren: Server + Client
|
||||
|
||||
```tsx
|
||||
// app/products/page.tsx (Server Component)
|
||||
import { ProductList } from './product-list'
|
||||
import { AddProductForm } from './add-product-form'
|
||||
|
||||
export default function ProductsPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Producten</h1>
|
||||
<AddProductForm /> {/* Client Component */}
|
||||
<ProductList /> {/* Client Component */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
PK = Primary Key
|
||||
FK = Foreign Key
|
||||
Pijl = Relatie richting
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Next.js 14
|
||||
- React Query (TanStack Query)
|
||||
- TypeScript
|
||||
- OpenCode/WebStorm
|
||||
- Pen en papier / Excalidraw / draw.io
|
||||
- Supabase Table Editor (vooruitblik)
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
|
||||
### Bouw CRUD App met API Routes
|
||||
### Database Design Oefening
|
||||
|
||||
**Deel 1: API Routes (40 min)**
|
||||
**Deel 1: Blog Database Ontwerpen (45 min)**
|
||||
|
||||
1. Maak `app/api/products/route.ts`
|
||||
2. Implementeer GET (alle producten)
|
||||
3. Implementeer POST (product toevoegen)
|
||||
4. Test met browser/Postman: `/api/products`
|
||||
Ontwerp een database voor een blog met:
|
||||
- Users (kunnen posts schrijven)
|
||||
- Posts (hebben een auteur)
|
||||
- Comments (op posts, door users)
|
||||
|
||||
**Deel 2: React Query Setup (20 min)**
|
||||
Voor elke tabel:
|
||||
1. Teken de tabel met kolommen
|
||||
2. Bepaal data types
|
||||
3. Markeer Primary Keys
|
||||
4. Markeer Foreign Keys
|
||||
5. Teken de relaties
|
||||
|
||||
1. Installeer `@tanstack/react-query`
|
||||
2. Maak `app/providers.tsx`
|
||||
3. Wrap app in `QueryClientProvider`
|
||||
**Deel 2: Normalisatie Oefening (30 min)**
|
||||
|
||||
**Deel 3: Data Tonen met useQuery (30 min)**
|
||||
Gegeven deze "slechte" tabel:
|
||||
|
||||
1. Maak `ProductList` Client Component
|
||||
2. Gebruik `useQuery` om data te fetchen
|
||||
3. Toon loading state
|
||||
4. Toon error state
|
||||
5. Render product lijst
|
||||
```
|
||||
library
|
||||
-------
|
||||
book_title | author_name | author_email | borrower_name | borrowed_date
|
||||
"1984" | "Orwell" | orwell@... | "Tim" | 2024-01-15
|
||||
"1984" | "Orwell" | orwell@... | "Anna" | 2024-01-10
|
||||
"Dune" | "Herbert" | herbert@... | "Tim" | 2024-01-12
|
||||
```
|
||||
|
||||
**Deel 4: Data Toevoegen met useMutation (30 min)**
|
||||
Normaliseer naar aparte tabellen:
|
||||
1. Welke entiteiten zie je?
|
||||
2. Maak aparte tabellen
|
||||
3. Voeg relaties toe
|
||||
|
||||
1. Maak `AddProductForm` Client Component
|
||||
2. Gebruik `useMutation` voor POST
|
||||
3. Invalidate query na success
|
||||
4. Toon "Adding..." state
|
||||
**Deel 3: Eindproject Schema (45 min)**
|
||||
|
||||
Ontwerp het database schema voor jouw eindproject:
|
||||
1. Welke entiteiten heb je nodig?
|
||||
2. Teken elke tabel met kolommen
|
||||
3. Bepaal relaties
|
||||
4. Documenteer je keuzes
|
||||
|
||||
### Deliverable
|
||||
- Werkende API routes (GET, POST)
|
||||
- ProductList met useQuery
|
||||
- AddProductForm met useMutation
|
||||
- Loading en error states
|
||||
- Blog database schema (tekening)
|
||||
- Genormaliseerde library database
|
||||
- Eindproject database schema
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Volledige CRUD Interface
|
||||
### Verdieping en Voorbereiding
|
||||
|
||||
**Deel 1: PUT en DELETE Routes (45 min)**
|
||||
**Deel 1: Eindproject Schema Uitwerken (1 uur)**
|
||||
|
||||
1. Maak `app/api/products/[id]/route.ts`
|
||||
2. Implementeer PUT (update product)
|
||||
3. Implementeer DELETE (verwijder product)
|
||||
4. Test beide endpoints
|
||||
Werk je database schema volledig uit:
|
||||
|
||||
**Deel 2: Update Functionaliteit (45 min)**
|
||||
1. **Per tabel:**
|
||||
- Naam
|
||||
- Alle kolommen met data types
|
||||
- Primary key
|
||||
- Foreign keys
|
||||
- Defaults
|
||||
- Nullable fields
|
||||
|
||||
1. Maak edit form in ProductList
|
||||
2. Gebruik useMutation voor PUT
|
||||
3. Inline editing OF modal
|
||||
4. Invalidate query na success
|
||||
2. **Documenteer:**
|
||||
- Waarom deze structuur?
|
||||
- Welke relaties?
|
||||
- Eventuele alternatieve overwegingen
|
||||
|
||||
**Deel 3: Delete Functionaliteit (30 min)**
|
||||
**Deel 2: Supabase Account (30 min)**
|
||||
|
||||
1. Voeg delete button toe per product
|
||||
2. Gebruik useMutation voor DELETE
|
||||
3. Voeg confirmation dialog toe
|
||||
4. Invalidate query na success
|
||||
Bereid je voor op volgende les:
|
||||
1. Maak account op [supabase.com](https://supabase.com)
|
||||
2. Verken de interface
|
||||
3. Bekijk de Table Editor
|
||||
|
||||
**Bonus:** Optimistic Updates
|
||||
- Product direct uit UI verwijderen
|
||||
- Rollback als server faalt
|
||||
**Deel 3: Reflectie (30 min)**
|
||||
|
||||
Beantwoord deze vragen (kort):
|
||||
1. Wat is het verschil tussen primary en foreign key?
|
||||
2. Waarom normaliseren we data?
|
||||
3. Wanneer gebruik je one-to-many vs many-to-many?
|
||||
4. Welke tabellen heeft jouw eindproject nodig?
|
||||
|
||||
### Deliverable
|
||||
- Complete CRUD API (GET, POST, PUT, DELETE)
|
||||
- UI voor alle operaties
|
||||
- Error handling
|
||||
- Optimistic updates (bonus)
|
||||
- Volledig uitgewerkt database schema voor eindproject
|
||||
- Supabase account aangemaakt
|
||||
- Reflectie vragen beantwoord
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Uitleggen wanneer Server vs Client Components
|
||||
- De 'use client' directive correct gebruiken
|
||||
- Data fetchen in Server Components met async/await
|
||||
- API routes maken met Route Handlers
|
||||
- GET en POST requests implementeren
|
||||
- React Query installeren en configureren
|
||||
- useQuery gebruiken voor data fetching
|
||||
- useMutation gebruiken voor data mutations
|
||||
- Loading en error states afhandelen
|
||||
- Query invalidation toepassen
|
||||
- Uitleggen wat een relationele database is
|
||||
- Tabellen, kolommen en rijen beschrijven
|
||||
- De juiste data types kiezen
|
||||
- Primary keys en hun doel uitleggen
|
||||
- Foreign keys en relaties begrijpen
|
||||
- One-to-many en many-to-many relaties herkennen
|
||||
- Het probleem van data duplicatie identificeren
|
||||
- Een database normaliseren
|
||||
- NULL values en defaults begrijpen
|
||||
- Een database schema ontwerpen en tekenen
|
||||
|
||||
@@ -1,372 +1,305 @@
|
||||
# Les 8: Database Principles
|
||||
# Les 8: Supabase: Auth & CRUD
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 5-9)
|
||||
**Deel 2: Technical Foundations** (Les 4-8)
|
||||
|
||||
## Beschrijving
|
||||
Leer de basisprincipes van relationele databases voordat we Supabase gaan gebruiken. Begrijp tabellen, relaties, keys en normalisatie - essentiële kennis voor elke developer.
|
||||
Supabase Authentication en CRUD operaties. Implementeer email/password auth, JWT tokens, Row Level Security (RLS) policies, realtime subscriptions en volledige CRUD functionaliteit in je full-stack app.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
|
||||
### Wat is een Relationele Database?
|
||||
### Wat is Supabase?
|
||||
|
||||
**Een database is:** Een georganiseerde verzameling van data.
|
||||
**Supabase = Database + Auth in één**
|
||||
- PostgreSQL database (gratis tier: 500MB)
|
||||
- Ingebouwde authenticatie
|
||||
- Real-time subscriptions
|
||||
- File storage
|
||||
- Auto-generated API
|
||||
|
||||
**Relationeel betekent:** Data is opgeslagen in tabellen die aan elkaar gerelateerd zijn.
|
||||
|
||||
**Vergelijk het met Excel:**
|
||||
- Database = Excel workbook
|
||||
- Tabel = Excel sheet
|
||||
- Kolom = Excel kolom (field)
|
||||
- Rij = Excel rij (record)
|
||||
**Waarom Supabase voor beginners:**
|
||||
- Geen eigen server nodig
|
||||
- Visuele Table Editor (geen SQL kennis nodig)
|
||||
- Simpele JavaScript SDK
|
||||
- Gratis tier is ruim voldoende
|
||||
|
||||
---
|
||||
|
||||
### Tabellen, Kolommen en Rijen
|
||||
### Supabase Project Aanmaken
|
||||
|
||||
**Voorbeeld: Users tabel**
|
||||
**Stap 1:** Ga naar [supabase.com](https://supabase.com) en maak account
|
||||
|
||||
| id | name | email | created_at |
|
||||
|----|------|-------|------------|
|
||||
| 1 | Tim | tim@email.com | 2024-01-15 |
|
||||
| 2 | Anna | anna@email.com | 2024-01-16 |
|
||||
| 3 | Jan | jan@email.com | 2024-01-17 |
|
||||
**Stap 2:** Klik "New Project"
|
||||
- Naam: `todo-app`
|
||||
- Database Password: (bewaar deze!)
|
||||
- Region: `West EU (Frankfurt)` (dichtst bij NL)
|
||||
|
||||
**Terminologie:**
|
||||
- **Tabel:** users
|
||||
- **Kolommen:** id, name, email, created_at
|
||||
- **Rijen:** 3 records (Tim, Anna, Jan)
|
||||
- **Cell:** Eén specifieke waarde (bijv. "tim@email.com")
|
||||
**Stap 3:** Wacht ~2 minuten tot project klaar is
|
||||
|
||||
**Stap 4:** Ga naar Settings → API en kopieer:
|
||||
- `Project URL`
|
||||
- `anon public` key
|
||||
|
||||
---
|
||||
|
||||
### Data Types
|
||||
### Je Database Schema Implementeren
|
||||
|
||||
Elke kolom heeft een type:
|
||||
In Les 7 heb je een database schema ontworpen. Nu gaan we dat implementeren!
|
||||
|
||||
| Type | Beschrijving | Voorbeeld |
|
||||
|------|--------------|-----------|
|
||||
| `text` / `varchar` | Tekst | "Tim", "Hello world" |
|
||||
| `integer` / `int` | Hele getallen | 1, 42, -5 |
|
||||
| `decimal` / `numeric` | Decimalen | 19.99, 3.14 |
|
||||
| `boolean` | True/False | true, false |
|
||||
| `timestamp` | Datum + tijd | 2024-01-15 14:30:00 |
|
||||
| `uuid` | Unieke identifier | a1b2c3d4-e5f6-... |
|
||||
**In Supabase Dashboard → Table Editor:**
|
||||
|
||||
**Kies het juiste type:**
|
||||
- Prijs? → `decimal` (niet `integer`, want centen)
|
||||
- Is actief? → `boolean`
|
||||
- Naam? → `text`
|
||||
- Aantal? → `integer`
|
||||
1. Klik "New Table"
|
||||
2. Gebruik je schema uit Les 7
|
||||
3. Voeg kolommen toe met de juiste types
|
||||
4. Definieer Primary Keys en Foreign Keys
|
||||
|
||||
**Voorbeeld: todos tabel**
|
||||
|
||||
| Name | Type | Default | Primary |
|
||||
|------|------|---------|---------|
|
||||
| id | int8 | - | ✓ (auto) |
|
||||
| title | text | - | |
|
||||
| completed | bool | false | |
|
||||
| created_at | timestamptz | now() | |
|
||||
|
||||
---
|
||||
|
||||
### Primary Keys
|
||||
### Environment Variables
|
||||
|
||||
**Wat:** Een kolom die elke rij UNIEK identificeert.
|
||||
**Wat zijn environment variables?**
|
||||
- Configuratie die NIET in je code hoort
|
||||
- API keys, database URLs, secrets
|
||||
- Verschillend per omgeving (lokaal vs productie)
|
||||
|
||||
**Regels:**
|
||||
- Moet uniek zijn per rij
|
||||
- Mag nooit NULL zijn
|
||||
- Verandert nooit
|
||||
|
||||
**Voorbeeld:**
|
||||
```
|
||||
users
|
||||
------
|
||||
id (PRIMARY KEY) | name | email
|
||||
1 | Tim | tim@email.com
|
||||
2 | Anna | anna@email.com
|
||||
**Maak `.env.local` in je project root:**
|
||||
```bash
|
||||
# .env.local - NOOIT committen naar Git!
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx
|
||||
```
|
||||
|
||||
**Waarom niet `email` als primary key?**
|
||||
- Emails kunnen veranderen
|
||||
- `id` is stabiel en snel
|
||||
|
||||
---
|
||||
|
||||
### Foreign Keys
|
||||
|
||||
**Wat:** Een kolom die verwijst naar de primary key van een andere tabel.
|
||||
|
||||
**Voorbeeld: Posts tabel**
|
||||
```
|
||||
posts
|
||||
------
|
||||
id | title | user_id (FOREIGN KEY → users.id)
|
||||
1 | "Mijn blog" | 1
|
||||
2 | "Hello world" | 1
|
||||
3 | "Tips" | 2
|
||||
```
|
||||
|
||||
**Wat zegt dit?**
|
||||
- Post 1 en 2 zijn van user 1 (Tim)
|
||||
- Post 3 is van user 2 (Anna)
|
||||
|
||||
---
|
||||
|
||||
### Relatie Types
|
||||
|
||||
**One-to-Many (1:N)** - Meest voorkomend!
|
||||
```
|
||||
Eén user → meerdere posts
|
||||
Eén category → meerdere products
|
||||
```
|
||||
|
||||
**One-to-One (1:1)** - Zeldzaam
|
||||
```
|
||||
Eén user → één profile
|
||||
```
|
||||
|
||||
**Many-to-Many (N:N)** - Via tussentabel
|
||||
```
|
||||
Posts ↔ Tags (een post heeft meerdere tags, een tag heeft meerdere posts)
|
||||
**Maak ook `.env.example` (WEL committen):**
|
||||
```bash
|
||||
# .env.example - template voor anderen
|
||||
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### One-to-Many Voorbeeld
|
||||
### Supabase SDK Installeren
|
||||
|
||||
```
|
||||
users posts
|
||||
------ ------
|
||||
id | name id | title | user_id
|
||||
1 | Tim ←────────── 1 | "Blog 1" | 1
|
||||
2 | Anna ←────┬───── 2 | "Blog 2" | 1
|
||||
└───── 3 | "Tips" | 2
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
**Lees:** Tim heeft 2 posts, Anna heeft 1 post.
|
||||
**Maak `src/lib/supabase.ts`:**
|
||||
```typescript
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
---
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
|
||||
### Many-to-Many met Tussentabel
|
||||
|
||||
```
|
||||
posts post_tags tags
|
||||
------ --------- ------
|
||||
id | title post_id | tag_id id | name
|
||||
1 | "React tips" 1 | 1 1 | "react"
|
||||
2 | "CSS guide" 1 | 2 2 | "frontend"
|
||||
2 | 2 3 | "css"
|
||||
2 | 3
|
||||
```
|
||||
|
||||
**Lees:**
|
||||
- Post 1 heeft tags: react, frontend
|
||||
- Post 2 heeft tags: frontend, css
|
||||
|
||||
---
|
||||
|
||||
### Normalisatie Basics
|
||||
|
||||
**Probleem: Data duplicatie**
|
||||
```
|
||||
orders (SLECHT)
|
||||
------
|
||||
id | customer_name | customer_email | product_name | price
|
||||
1 | Tim | tim@email.com | Laptop | 999
|
||||
2 | Tim | tim@email.com | Phone | 699
|
||||
3 | Anna | anna@email.com | Laptop | 999
|
||||
```
|
||||
|
||||
**Problemen:**
|
||||
- Tim's email staat 2x (als hij verandert: 2 plekken updaten)
|
||||
- "Laptop" en prijs staan 2x
|
||||
|
||||
---
|
||||
|
||||
### Genormaliseerde Versie
|
||||
|
||||
```
|
||||
users products orders
|
||||
------ -------- ------
|
||||
id | name | email id | name | price id | user_id | product_id
|
||||
1 | Tim | tim@... 1 | Laptop | 999 1 | 1 | 1
|
||||
2 | Anna | anna@... 2 | Phone | 699 2 | 1 | 2
|
||||
3 | 2 | 1
|
||||
```
|
||||
|
||||
**Voordelen:**
|
||||
- Elk gegeven staat 1x
|
||||
- Update op 1 plek
|
||||
- Minder opslagruimte
|
||||
|
||||
---
|
||||
|
||||
### NULL Values
|
||||
|
||||
**NULL = "geen waarde" (niet 0, niet "")**
|
||||
|
||||
```
|
||||
users
|
||||
------
|
||||
id | name | phone
|
||||
1 | Tim | 0612345678
|
||||
2 | Anna | NULL ← Geen telefoon bekend
|
||||
```
|
||||
|
||||
**Wanneer NULL toestaan?**
|
||||
- Optionele velden (phone, description)
|
||||
- Niet bij verplichte velden (name, email)
|
||||
|
||||
---
|
||||
|
||||
### Defaults
|
||||
|
||||
**Automatische waarde als je niks opgeeft:**
|
||||
|
||||
```
|
||||
todos
|
||||
------
|
||||
id | title | completed | created_at
|
||||
| | DEFAULT: false | DEFAULT: now()
|
||||
```
|
||||
|
||||
Bij `INSERT INTO todos (title) VALUES ('Test')`:
|
||||
```
|
||||
id | title | completed | created_at
|
||||
1 | Test | false | 2024-01-15 10:30:00
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Database Schema Tekenen
|
||||
### CRUD Operaties
|
||||
|
||||
**Tools:** draw.io, Excalidraw, pen en papier
|
||||
|
||||
**Conventie:**
|
||||
```
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ users │ │ posts │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ id (PK) │───┐ │ id (PK) │
|
||||
│ name │ │ │ title │
|
||||
│ email │ └────→│ user_id (FK) │
|
||||
│ created_at │ │ content │
|
||||
└──────────────┘ │ created_at │
|
||||
└──────────────┘
|
||||
**C - Create (toevoegen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.insert({ title: 'Nieuwe taak' })
|
||||
```
|
||||
|
||||
PK = Primary Key
|
||||
FK = Foreign Key
|
||||
Pijl = Relatie richting
|
||||
**R - Read (ophalen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
```
|
||||
|
||||
**U - Update (wijzigen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.update({ completed: true })
|
||||
.eq('id', todoId)
|
||||
```
|
||||
|
||||
**D - Delete (verwijderen):**
|
||||
```typescript
|
||||
const { error } = await supabase
|
||||
.from('todos')
|
||||
.delete()
|
||||
.eq('id', todoId)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Authenticatie met Auth UI
|
||||
|
||||
**Installeer auth packages:**
|
||||
```bash
|
||||
npm install @supabase/auth-ui-react @supabase/auth-ui-shared
|
||||
```
|
||||
|
||||
**Login component:**
|
||||
```tsx
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
|
||||
export function LoginForm() {
|
||||
return (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
providers={[]}
|
||||
magicLink={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Huidige user checken:**
|
||||
```typescript
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
|
||||
if (user) {
|
||||
// User is ingelogd
|
||||
console.log('Logged in as:', user.email)
|
||||
} else {
|
||||
// Redirect naar login
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Deployment naar Vercel
|
||||
|
||||
**Stap 1: Push naar GitHub**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Add Supabase integration"
|
||||
git push
|
||||
```
|
||||
|
||||
**Stap 2: Deploy op Vercel**
|
||||
1. Ga naar [vercel.com](https://vercel.com)
|
||||
2. "Add New Project"
|
||||
3. Import je GitHub repo
|
||||
4. **BELANGRIJK:** Voeg Environment Variables toe!
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
5. Klik "Deploy"
|
||||
|
||||
**Stap 3: Supabase Redirect URLs**
|
||||
1. Ga naar Supabase → Authentication → URL Configuration
|
||||
2. Voeg toe bij "Redirect URLs":
|
||||
- `https://jouw-app.vercel.app/**`
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Pen en papier / Excalidraw / draw.io
|
||||
- Supabase Table Editor (vooruitblik)
|
||||
- Supabase
|
||||
- Next.js
|
||||
- OpenCode/WebStorm
|
||||
- Vercel
|
||||
- Git
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
|
||||
### Database Design Oefening
|
||||
### Bouw een Todo App met Supabase
|
||||
|
||||
**Deel 1: Blog Database Ontwerpen (45 min)**
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de database schemas uit Les 7 - wie heeft welke structuur gekozen en waarom?
|
||||
|
||||
Ontwerp een database voor een blog met:
|
||||
- Users (kunnen posts schrijven)
|
||||
- Posts (hebben een auteur)
|
||||
- Comments (op posts, door users)
|
||||
**Deel 1: Supabase Setup (30 min)**
|
||||
|
||||
Voor elke tabel:
|
||||
1. Teken de tabel met kolommen
|
||||
2. Bepaal data types
|
||||
3. Markeer Primary Keys
|
||||
4. Markeer Foreign Keys
|
||||
5. Teken de relaties
|
||||
1. Maak Supabase account en project
|
||||
2. Maak je tabellen via Table Editor (gebaseerd op Les 7 schema)
|
||||
3. Kopieer credentials
|
||||
4. Installeer `@supabase/supabase-js`
|
||||
5. Maak `src/lib/supabase.ts`
|
||||
6. Configureer `.env.local`
|
||||
|
||||
**Deel 2: Normalisatie Oefening (30 min)**
|
||||
Test: `npm run dev` werkt zonder errors
|
||||
|
||||
Gegeven deze "slechte" tabel:
|
||||
**Deel 2: CRUD Interface (1 uur)**
|
||||
|
||||
```
|
||||
library
|
||||
-------
|
||||
book_title | author_name | author_email | borrower_name | borrowed_date
|
||||
"1984" | "Orwell" | orwell@... | "Tim" | 2024-01-15
|
||||
"1984" | "Orwell" | orwell@... | "Anna" | 2024-01-10
|
||||
"Dune" | "Herbert" | herbert@... | "Tim" | 2024-01-12
|
||||
```
|
||||
Bouw UI voor todos:
|
||||
1. Lijst van todos tonen
|
||||
2. Form om nieuwe todo toe te voegen
|
||||
3. Checkbox om todo af te vinken
|
||||
4. Delete button per todo
|
||||
|
||||
Normaliseer naar aparte tabellen:
|
||||
1. Welke entiteiten zie je?
|
||||
2. Maak aparte tabellen
|
||||
3. Voeg relaties toe
|
||||
Gebruik AI hulp voor de components!
|
||||
|
||||
**Deel 3: Eindproject Schema (45 min)**
|
||||
**Deel 3: Authenticatie (30 min)**
|
||||
|
||||
Ontwerp het database schema voor jouw eindproject:
|
||||
1. Welke entiteiten heb je nodig?
|
||||
2. Teken elke tabel met kolommen
|
||||
3. Bepaal relaties
|
||||
4. Documenteer je keuzes
|
||||
1. Installeer auth packages
|
||||
2. Maak login pagina met Auth UI
|
||||
3. Toon alleen app voor ingelogde users
|
||||
4. Test: login met magic link
|
||||
|
||||
### Deliverable
|
||||
- Blog database schema (tekening)
|
||||
- Genormaliseerde library database
|
||||
- Eindproject database schema
|
||||
- Werkende Todo app lokaal
|
||||
- GitHub repository met code
|
||||
- Screenshot van werkende app
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Verdieping en Voorbereiding
|
||||
### Deploy naar Productie + Uitbreiden
|
||||
|
||||
**Deel 1: Eindproject Schema Uitwerken (1 uur)**
|
||||
**Deel 1: Deployment (30 min)**
|
||||
|
||||
Werk je database schema volledig uit:
|
||||
1. Push naar GitHub
|
||||
2. Deploy naar Vercel
|
||||
3. Configureer env vars in Vercel
|
||||
4. Voeg Vercel URL toe aan Supabase Redirect URLs
|
||||
5. Test: app werkt op productie URL!
|
||||
|
||||
1. **Per tabel:**
|
||||
- Naam
|
||||
- Alle kolommen met data types
|
||||
- Primary key
|
||||
- Foreign keys
|
||||
- Defaults
|
||||
- Nullable fields
|
||||
**Deel 2: Features Uitbreiden (1 uur)**
|
||||
|
||||
2. **Documenteer:**
|
||||
- Waarom deze structuur?
|
||||
- Welke relaties?
|
||||
- Eventuele alternatieve overwegingen
|
||||
Voeg toe:
|
||||
1. Filter buttons: Alle / Actief / Voltooid
|
||||
2. Sorteer op datum (nieuwste eerst)
|
||||
3. Loading state tijdens data ophalen
|
||||
4. Error state bij problemen
|
||||
5. Empty state: "Geen todos gevonden"
|
||||
|
||||
**Deel 2: Supabase Account (30 min)**
|
||||
**Deel 3: Polish (30 min)**
|
||||
|
||||
Bereid je voor op volgende les:
|
||||
1. Maak account op [supabase.com](https://supabase.com)
|
||||
2. Verken de interface
|
||||
3. Bekijk de Table Editor
|
||||
|
||||
**Deel 3: Reflectie (30 min)**
|
||||
|
||||
Beantwoord deze vragen (kort):
|
||||
1. Wat is het verschil tussen primary en foreign key?
|
||||
2. Waarom normaliseren we data?
|
||||
3. Wanneer gebruik je one-to-many vs many-to-many?
|
||||
4. Welke tabellen heeft jouw eindproject nodig?
|
||||
1. Styling verbeteren met Tailwind
|
||||
2. Responsive design (mobile friendly)
|
||||
3. Kleine animaties (fade in/out)
|
||||
|
||||
### Deliverable
|
||||
- Volledig uitgewerkt database schema voor eindproject
|
||||
- Supabase account aangemaakt
|
||||
- Reflectie vragen beantwoord
|
||||
- Deployed app op Vercel (werkende URL!)
|
||||
- Alle features werken in productie
|
||||
- Screenshot van productie app
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Uitleggen wat een relationele database is
|
||||
- Tabellen, kolommen en rijen beschrijven
|
||||
- De juiste data types kiezen
|
||||
- Primary keys en hun doel uitleggen
|
||||
- Foreign keys en relaties begrijpen
|
||||
- One-to-many en many-to-many relaties herkennen
|
||||
- Het probleem van data duplicatie identificeren
|
||||
- Een database normaliseren
|
||||
- NULL values en defaults begrijpen
|
||||
- Een database schema ontwerpen en tekenen
|
||||
- Een Supabase project aanmaken en configureren
|
||||
- Database schema implementeren via Table Editor
|
||||
- Environment variables correct beheren
|
||||
- De Supabase client installeren en configureren
|
||||
- CRUD operaties uitvoeren met de Supabase SDK
|
||||
- Authenticatie implementeren met Auth UI
|
||||
- Deployen naar Vercel met environment variables
|
||||
- Database principles uit Les 7 toepassen in de praktijk
|
||||
|
||||
@@ -1,305 +1,126 @@
|
||||
# Les 9: Supabase Basics
|
||||
# Les 9: Full-Stack Mini Project
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 5-9)
|
||||
**Deel 3: Integration & AI Tooling** (Les 9-12)
|
||||
|
||||
## Beschrijving
|
||||
Leer werken met Supabase: een complete backend-as-a-service met database en authenticatie. Pas je database schema uit Les 8 toe en bouw je eerste full-stack app.
|
||||
Combineer alles wat je geleerd hebt (TypeScript, Next.js, Supabase) in een kleine werkende applicatie. Dit is je eerste "echte" full-stack project en voorbereiding op het werken met AI tools.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
|
||||
### Wat is Supabase?
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de Supabase ervaringen uit Les 8 - welke uitdagingen kwamen jullie tegen bij authenticatie en RLS?
|
||||
|
||||
**Supabase = Database + Auth in één**
|
||||
- PostgreSQL database (gratis tier: 500MB)
|
||||
- Ingebouwde authenticatie
|
||||
- Real-time subscriptions
|
||||
- File storage
|
||||
- Auto-generated API
|
||||
### Doel van deze les
|
||||
|
||||
**Waarom Supabase voor beginners:**
|
||||
- Geen eigen server nodig
|
||||
- Visuele Table Editor (geen SQL kennis nodig)
|
||||
- Simpele JavaScript SDK
|
||||
- Gratis tier is ruim voldoende
|
||||
Je hebt nu alle bouwstenen:
|
||||
- TypeScript (Les 4)
|
||||
- Next.js met App Router (Les 5-6)
|
||||
- Supabase database & auth (Les 7-8)
|
||||
|
||||
Vandaag combineren we dit in een **werkende mini-app**. Geen nieuwe concepten - alleen integratie en toepassing.
|
||||
|
||||
---
|
||||
|
||||
### Supabase Project Aanmaken
|
||||
### Mini Project: Personal Bookmarks
|
||||
|
||||
**Stap 1:** Ga naar [supabase.com](https://supabase.com) en maak account
|
||||
Een simpele bookmark manager waar je links kunt opslaan.
|
||||
|
||||
**Stap 2:** Klik "New Project"
|
||||
- Naam: `todo-app`
|
||||
- Database Password: (bewaar deze!)
|
||||
- Region: `West EU (Frankfurt)` (dichtst bij NL)
|
||||
**Features:**
|
||||
- Login met Supabase Auth
|
||||
- Bookmarks toevoegen (URL + titel)
|
||||
- Bookmarks bekijken en verwijderen
|
||||
|
||||
**Stap 3:** Wacht ~2 minuten tot project klaar is
|
||||
|
||||
**Stap 4:** Ga naar Settings → API en kopieer:
|
||||
- `Project URL`
|
||||
- `anon public` key
|
||||
**Tech stack:**
|
||||
- Next.js 14 met App Router
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Supabase (auth + database)
|
||||
- React Query
|
||||
|
||||
---
|
||||
|
||||
### Je Database Schema Implementeren
|
||||
### Stap-voor-stap
|
||||
|
||||
In Les 8 heb je een database schema ontworpen. Nu gaan we dat implementeren!
|
||||
#### Database Schema
|
||||
|
||||
**In Supabase Dashboard → Table Editor:**
|
||||
**Tabel: bookmarks**
|
||||
| Kolom | Type |
|
||||
|-------|------|
|
||||
| id | uuid (PK) |
|
||||
| user_id | uuid (FK naar auth.users) |
|
||||
| url | text |
|
||||
| title | text |
|
||||
| created_at | timestamptz |
|
||||
|
||||
1. Klik "New Table"
|
||||
2. Gebruik je schema uit Les 8
|
||||
3. Voeg kolommen toe met de juiste types
|
||||
4. Definieer Primary Keys en Foreign Keys
|
||||
**RLS:** Users kunnen alleen eigen bookmarks zien, toevoegen en verwijderen.
|
||||
|
||||
**Voorbeeld: todos tabel**
|
||||
#### Wat je bouwt
|
||||
|
||||
| Name | Type | Default | Primary |
|
||||
|------|------|---------|---------|
|
||||
| id | int8 | - | ✓ (auto) |
|
||||
| title | text | - | |
|
||||
| completed | bool | false | |
|
||||
| created_at | timestamptz | now() | |
|
||||
|
||||
---
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Wat zijn environment variables?**
|
||||
- Configuratie die NIET in je code hoort
|
||||
- API keys, database URLs, secrets
|
||||
- Verschillend per omgeving (lokaal vs productie)
|
||||
|
||||
**Maak `.env.local` in je project root:**
|
||||
```bash
|
||||
# .env.local - NOOIT committen naar Git!
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx
|
||||
```
|
||||
|
||||
**Maak ook `.env.example` (WEL committen):**
|
||||
```bash
|
||||
# .env.example - template voor anderen
|
||||
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Supabase SDK Installeren
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
**Maak `src/lib/supabase.ts`:**
|
||||
```typescript
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CRUD Operaties
|
||||
|
||||
**C - Create (toevoegen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.insert({ title: 'Nieuwe taak' })
|
||||
```
|
||||
|
||||
**R - Read (ophalen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
```
|
||||
|
||||
**U - Update (wijzigen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.update({ completed: true })
|
||||
.eq('id', todoId)
|
||||
```
|
||||
|
||||
**D - Delete (verwijderen):**
|
||||
```typescript
|
||||
const { error } = await supabase
|
||||
.from('todos')
|
||||
.delete()
|
||||
.eq('id', todoId)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Authenticatie met Auth UI
|
||||
|
||||
**Installeer auth packages:**
|
||||
```bash
|
||||
npm install @supabase/auth-ui-react @supabase/auth-ui-shared
|
||||
```
|
||||
|
||||
**Login component:**
|
||||
```tsx
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
|
||||
export function LoginForm() {
|
||||
return (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
providers={[]}
|
||||
magicLink={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Huidige user checken:**
|
||||
```typescript
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
|
||||
if (user) {
|
||||
// User is ingelogd
|
||||
console.log('Logged in as:', user.email)
|
||||
} else {
|
||||
// Redirect naar login
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Deployment naar Vercel
|
||||
|
||||
**Stap 1: Push naar GitHub**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Add Supabase integration"
|
||||
git push
|
||||
```
|
||||
|
||||
**Stap 2: Deploy op Vercel**
|
||||
1. Ga naar [vercel.com](https://vercel.com)
|
||||
2. "Add New Project"
|
||||
3. Import je GitHub repo
|
||||
4. **BELANGRIJK:** Voeg Environment Variables toe!
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
5. Klik "Deploy"
|
||||
|
||||
**Stap 3: Supabase Redirect URLs**
|
||||
1. Ga naar Supabase → Authentication → URL Configuration
|
||||
2. Voeg toe bij "Redirect URLs":
|
||||
- `https://jouw-app.vercel.app/**`
|
||||
1. **Login pagina** - Supabase Auth
|
||||
2. **Dashboard** - Lijst van bookmarks
|
||||
3. **Add form** - URL + titel invoeren
|
||||
4. **Delete** - Bookmark verwijderen
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Supabase
|
||||
- Next.js
|
||||
- OpenCode/WebStorm
|
||||
- Vercel
|
||||
- Git
|
||||
- VS Code
|
||||
- Supabase Dashboard
|
||||
- Browser DevTools
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (2.5 uur)
|
||||
|
||||
### Bouw een Todo App met Supabase
|
||||
### Bouw de Bookmark Manager
|
||||
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de database schemas uit Les 8 - wie heeft welke structuur gekozen en waarom?
|
||||
**Checkpoints:**
|
||||
|
||||
**Deel 1: Supabase Setup (30 min)**
|
||||
| Tijd | Wat klaar moet zijn |
|
||||
|------|---------------------|
|
||||
| 30 min | Project setup + database schema |
|
||||
| 60 min | Auth werkt (login/logout) |
|
||||
| 90 min | Bookmarks toevoegen en bekijken |
|
||||
| 120 min | Delete werkt |
|
||||
| 150 min | Styling en testen |
|
||||
|
||||
1. Maak Supabase account en project
|
||||
2. Maak je tabellen via Table Editor (gebaseerd op Les 8 schema)
|
||||
3. Kopieer credentials
|
||||
4. Installeer `@supabase/supabase-js`
|
||||
5. Maak `src/lib/supabase.ts`
|
||||
6. Configureer `.env.local`
|
||||
|
||||
Test: `npm run dev` werkt zonder errors
|
||||
|
||||
**Deel 2: CRUD Interface (1 uur)**
|
||||
|
||||
Bouw UI voor todos:
|
||||
1. Lijst van todos tonen
|
||||
2. Form om nieuwe todo toe te voegen
|
||||
3. Checkbox om todo af te vinken
|
||||
4. Delete button per todo
|
||||
|
||||
Gebruik AI hulp voor de components!
|
||||
|
||||
**Deel 3: Authenticatie (30 min)**
|
||||
|
||||
1. Installeer auth packages
|
||||
2. Maak login pagina met Auth UI
|
||||
3. Toon alleen app voor ingelogde users
|
||||
4. Test: login met magic link
|
||||
**Minimale eisen:**
|
||||
- [ ] Login/logout werkt
|
||||
- [ ] Bookmarks toevoegen werkt
|
||||
- [ ] Bookmarks worden getoond
|
||||
- [ ] Delete werkt
|
||||
|
||||
### Deliverable
|
||||
- Werkende Todo app lokaal
|
||||
- GitHub repository met code
|
||||
- Screenshot van werkende app
|
||||
- Werkende lokale applicatie
|
||||
- Screenshot van je app met data
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
## Huiswerk (1 uur)
|
||||
|
||||
### Deploy naar Productie + Uitbreiden
|
||||
### Reflectie
|
||||
|
||||
**Deel 1: Deployment (30 min)**
|
||||
Schrijf korte reflectie (200 woorden):
|
||||
- Wat ging goed bij het integreren?
|
||||
- Waar liep je vast?
|
||||
- Wat zou je volgende keer anders doen?
|
||||
|
||||
1. Push naar GitHub
|
||||
2. Deploy naar Vercel
|
||||
3. Configureer env vars in Vercel
|
||||
4. Voeg Vercel URL toe aan Supabase Redirect URLs
|
||||
5. Test: app werkt op productie URL!
|
||||
|
||||
**Deel 2: Features Uitbreiden (1 uur)**
|
||||
|
||||
Voeg toe:
|
||||
1. Filter buttons: Alle / Actief / Voltooid
|
||||
2. Sorteer op datum (nieuwste eerst)
|
||||
3. Loading state tijdens data ophalen
|
||||
4. Error state bij problemen
|
||||
5. Empty state: "Geen todos gevonden"
|
||||
|
||||
**Deel 3: Polish (30 min)**
|
||||
|
||||
1. Styling verbeteren met Tailwind
|
||||
2. Responsive design (mobile friendly)
|
||||
3. Kleine animaties (fade in/out)
|
||||
Maak een lijst van 3 verbeterpunten voor je code.
|
||||
|
||||
### Deliverable
|
||||
- Deployed app op Vercel (werkende URL!)
|
||||
- Alle features werken in productie
|
||||
- Screenshot van productie app
|
||||
- Reflectie (200 woorden)
|
||||
- 3 verbeterpunten
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Een Supabase project aanmaken en configureren
|
||||
- Database schema implementeren via Table Editor
|
||||
- Environment variables correct beheren
|
||||
- De Supabase client installeren en configureren
|
||||
- CRUD operaties uitvoeren met de Supabase SDK
|
||||
- Authenticatie implementeren met Auth UI
|
||||
- Deployen naar Vercel met environment variables
|
||||
- Database principles uit Les 8 toepassen in de praktijk
|
||||
- Een complete full-stack applicatie bouwen met Next.js, TypeScript en Supabase
|
||||
- CRUD operaties implementeren met React Query
|
||||
- Authenticatie integreren in een applicatie
|
||||
- Zelfstandig integratieproblemen oplossen
|
||||
|
||||
@@ -1,126 +1,116 @@
|
||||
# Les 10: Full-Stack Mini Project
|
||||
# Les 10: Styling: Tailwind CSS & shadcn/ui
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 3: Integration & AI Tooling** (Les 10-12)
|
||||
**Deel 3: Full-Stack Development** (Les 9-12)
|
||||
|
||||
## Beschrijving
|
||||
Combineer alles wat je geleerd hebt (TypeScript, Next.js, Supabase) in een kleine werkende applicatie. Dit is je eerste "echte" full-stack project en voorbereiding op het werken met AI tools.
|
||||
Styling je applicatie met Tailwind CSS en modern components met shadcn/ui. Utility-first approach, component libraries, themeing en dark mode implementatie.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~45 min)
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de Supabase ervaringen uit Les 9 - welke uitdagingen kwamen jullie tegen bij authenticatie en RLS?
|
||||
|
||||
### Doel van deze les
|
||||
|
||||
Je hebt nu alle bouwstenen:
|
||||
- TypeScript (Les 5)
|
||||
- Next.js met App Router (Les 6-7)
|
||||
- Supabase database & auth (Les 8-9)
|
||||
|
||||
Vandaag combineren we dit in een **werkende mini-app**. Geen nieuwe concepten - alleen integratie en toepassing.
|
||||
|
||||
---
|
||||
|
||||
### Mini Project: Personal Bookmarks
|
||||
|
||||
Een simpele bookmark manager waar je links kunt opslaan.
|
||||
|
||||
**Features:**
|
||||
- Login met Supabase Auth
|
||||
- Bookmarks toevoegen (URL + titel)
|
||||
- Bookmarks bekijken en verwijderen
|
||||
|
||||
**Tech stack:**
|
||||
- Next.js 14 met App Router
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Supabase (auth + database)
|
||||
- React Query
|
||||
|
||||
---
|
||||
|
||||
### Stap-voor-stap
|
||||
|
||||
#### Database Schema
|
||||
|
||||
**Tabel: bookmarks**
|
||||
| Kolom | Type |
|
||||
|-------|------|
|
||||
| id | uuid (PK) |
|
||||
| user_id | uuid (FK naar auth.users) |
|
||||
| url | text |
|
||||
| title | text |
|
||||
| created_at | timestamptz |
|
||||
|
||||
**RLS:** Users kunnen alleen eigen bookmarks zien, toevoegen en verwijderen.
|
||||
|
||||
#### Wat je bouwt
|
||||
|
||||
1. **Login pagina** - Supabase Auth
|
||||
2. **Dashboard** - Lijst van bookmarks
|
||||
3. **Add form** - URL + titel invoeren
|
||||
4. **Delete** - Bookmark verwijderen
|
||||
- Waarom Tailwind? Utility-first CSS approach vs traditioneel CSS
|
||||
- Tailwind in Next.js (meestal al ingesteld)
|
||||
- Core utilities: spacing, colors, flexbox, grid, responsive (mobile-first)
|
||||
- Tailwind components: buttons, cards, forms patterns
|
||||
- Wat is shadcn/ui? Beautifully designed component library
|
||||
- shadcn/ui installatie en configuratie
|
||||
- shadcn/ui components: Button, Card, Input, Dialog, etc.
|
||||
- Custom Tailwind color themes (tailwind.config.ts)
|
||||
- Dark mode implementation met Tailwind
|
||||
- Performance: class optimization, purging unused styles
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- VS Code
|
||||
- Supabase Dashboard
|
||||
- Browser DevTools
|
||||
- Tailwind CSS
|
||||
- shadcn/ui
|
||||
- Cursor
|
||||
- TypeScript
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2.5 uur)
|
||||
## Lesopdracht (2 uur, klassikaal)
|
||||
|
||||
### Bouw de Bookmark Manager
|
||||
### Styling Je Mini-Project
|
||||
|
||||
**Checkpoints:**
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de Full-Stack Mini Project ervaringen uit Les 9 - welke onderdelen werkten goed en waar liepen jullie vast?
|
||||
|
||||
| Tijd | Wat klaar moet zijn |
|
||||
|------|---------------------|
|
||||
| 30 min | Project setup + database schema |
|
||||
| 60 min | Auth werkt (login/logout) |
|
||||
| 90 min | Bookmarks toevoegen en bekijken |
|
||||
| 120 min | Delete werkt |
|
||||
| 150 min | Styling en testen |
|
||||
**Deel 1: Tailwind Basics (30 min)**
|
||||
|
||||
**Minimale eisen:**
|
||||
- [ ] Login/logout werkt
|
||||
- [ ] Bookmarks toevoegen werkt
|
||||
- [ ] Bookmarks worden getoond
|
||||
- [ ] Delete werkt
|
||||
1. Open je mini-project uit Les 9
|
||||
2. Refactor bestaande components met Tailwind classes:
|
||||
- Button: `bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded`
|
||||
- Card: `bg-white rounded-lg shadow-lg p-6`
|
||||
- Form inputs: `w-full px-3 py-2 border border-gray-300 rounded-md`
|
||||
3. Voeg spacing, colors, responsive design toe
|
||||
|
||||
**Deel 2: shadcn/ui Setup & Components (45 min)**
|
||||
|
||||
1. Install shadcn/ui: `npx shadcn-ui@latest init`
|
||||
2. Install components: `npx shadcn-ui@latest add button input card dialog`
|
||||
3. Replace je custom components met shadcn versions
|
||||
4. Test styling en interactiviteit
|
||||
|
||||
**Deel 3: Theme & Dark Mode (30 min)**
|
||||
|
||||
1. Customize Tailwind color scheme in `tailwind.config.ts`
|
||||
2. Voeg dark mode toggle toe (localStorage + CSS class toggle)
|
||||
3. Zorg dat je hele app responsive is (mobile-first)
|
||||
|
||||
### Deliverable
|
||||
- Werkende lokale applicatie
|
||||
- Screenshot van je app met data
|
||||
- Gerestyled mini-project met Tailwind + shadcn/ui
|
||||
- Dark mode toggle werkend
|
||||
- Mobile responsive design
|
||||
- GitHub commit met improvements
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (1 uur)
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Reflectie
|
||||
### Vervolg Styling & Polish
|
||||
|
||||
Schrijf korte reflectie (200 woorden):
|
||||
- Wat ging goed bij het integreren?
|
||||
- Waar liep je vast?
|
||||
- Wat zou je volgende keer anders doen?
|
||||
**Deel 1: Component Library Uitbreiden (1 uur)**
|
||||
|
||||
Maak een lijst van 3 verbeterpunten voor je code.
|
||||
Install en integreer meer shadcn/ui components:
|
||||
- Select, Tabs, Modal/Dialog
|
||||
- Forms package voor betere form handling
|
||||
- Toast notifications
|
||||
- Zorg dat je hele app consistent gelayout is
|
||||
|
||||
**Deel 2: Custom Theme (30 min)**
|
||||
|
||||
Maak je eigen color palette in `tailwind.config.ts`:
|
||||
- Primary, secondary, accent colors
|
||||
- Custom spacing, typography
|
||||
- Test in light en dark mode
|
||||
|
||||
**Deel 3: Accessibility & Polish (30 min)**
|
||||
|
||||
1. Voeg alt text toe aan images
|
||||
2. Zorg voor proper heading hierarchy
|
||||
3. Test keyboard navigation
|
||||
4. Fix UI inconsistencies
|
||||
|
||||
### Deliverable
|
||||
- Reflectie (200 woorden)
|
||||
- 3 verbeterpunten
|
||||
- Compleet gestylde mini-project
|
||||
- Alle shadcn/ui components correct geintegreerd
|
||||
- Custom color theme
|
||||
- GitHub commits met styling improvements
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Een complete full-stack applicatie bouwen met Next.js, TypeScript en Supabase
|
||||
- CRUD operaties implementeren met React Query
|
||||
- Authenticatie integreren in een applicatie
|
||||
- Zelfstandig integratieproblemen oplossen
|
||||
- Tailwind utility-first approach begrijpen en toepassen
|
||||
- shadcn/ui components installeren en gebruiken
|
||||
- Custom Tailwind themes maken
|
||||
- Dark mode implementeren
|
||||
- Responsive design (mobile-first) toepassen
|
||||
- Professional-looking UI bouwen met componenten
|
||||
- Het verschil tussen styling approaches begrijpen
|
||||
|
||||
@@ -1,237 +1,233 @@
|
||||
# Les 11: Hands-on: Van Idee naar Prototype
|
||||
# Les 11: Van Idee naar Prototype
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 3: AI Tooling & Prototyping** (Les 10-12)
|
||||
**Deel 3: Full-Stack Development** (Les 9-12)
|
||||
|
||||
## Beschrijving
|
||||
Pas alles wat je hebt geleerd toe in een hands-on sessie. Ga van een vaag idee naar een werkend prototype met behulp van je AI workflow.
|
||||
Proces van vaag idee naar werkend prototype. Feature breakdown, component thinking, MVP planning, user stories, en voorbereiding op eindproject. Kiezen van eindproject idea en architectuur opzetten.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~45 min)
|
||||
|
||||
- Hoe ga je van vaag idee naar concrete features?
|
||||
- Feature breakdown method en AI-hulp
|
||||
- Component thinking: UI opdelen in herbruikbare stukken
|
||||
- MVP (Minimum Viable Product) denken
|
||||
- User stories schrijven (As a user I want to...)
|
||||
- Prioritizing features: must-have, nice-to-have, later
|
||||
- Database schema planning voor je idee
|
||||
- Project architecture design
|
||||
- Folder structure planning
|
||||
- Eindproject idee kiezen (vereenvoudigde versie)
|
||||
|
||||
---
|
||||
|
||||
### Van Idee naar Feature Breakdown
|
||||
|
||||
**Het probleem:** Je hebt een idee, maar waar begin je?
|
||||
|
||||
**Stap 1: Beschrijf je idee in 1-2 zinnen**
|
||||
**Stap 1: Beschrijf je idee in 1 zin**
|
||||
```
|
||||
"Ik wil een app waar je kunt bijhouden welke planten water nodig hebben."
|
||||
"Ik wil een app waar je recepten kan zoeken op basis van ingrediënten die je hebt"
|
||||
```
|
||||
|
||||
**Stap 2: Vraag AI om feature breakdown**
|
||||
**Stap 2: Werk uit wat gebruikers willen**
|
||||
```
|
||||
Prompt: Ik wil een plant watering tracker app bouwen.
|
||||
Wat zijn de minimale features voor een werkend prototype?
|
||||
Denk aan: wat moet een gebruiker kunnen doen?
|
||||
User stories:
|
||||
- Als gebruiker wil ik ingrediënten kunnen invoeren
|
||||
- Als gebruiker wil ik recepten suggesties krijgen
|
||||
- Als gebruiker wil ik recepten kunnen opslaan
|
||||
```
|
||||
|
||||
**Stap 3: Prioriteer (MVP denken)**
|
||||
- Wat is essentieel? → In prototype
|
||||
- Wat is nice-to-have? → Later
|
||||
**Stap 3: Break down in features**
|
||||
```
|
||||
Core Features:
|
||||
- Ingredient input form
|
||||
- Recipe search/filter
|
||||
- Save favorites
|
||||
- View saved recipes
|
||||
|
||||
Nice-to-have:
|
||||
- Meal planning
|
||||
- Shopping list generator
|
||||
- Nutritional info
|
||||
- Export recipes
|
||||
```
|
||||
|
||||
**Stap 4: Prioriteer (MVP)**
|
||||
- ✅ Input form + recipe search
|
||||
- ✅ Display results
|
||||
- ✅ Save/favorites
|
||||
- ❌ Shopping list (later)
|
||||
- ❌ Meal planning (later)
|
||||
|
||||
---
|
||||
|
||||
### Component Thinking
|
||||
|
||||
**Vraag jezelf af:**
|
||||
- Welke "blokken" zie ik op het scherm?
|
||||
- Welke blokken worden herhaald?
|
||||
- Welke blokken komen op meerdere pagina's?
|
||||
|
||||
**Voorbeeld: Plant Tracker**
|
||||
**Visualiseer je app:**
|
||||
```
|
||||
Herhaalde componenten:
|
||||
- PlantCard (naam, foto, laatste water datum)
|
||||
- WaterButton (markeer als water gegeven)
|
||||
|
||||
Pagina componenten:
|
||||
- PlantList (toont alle PlantCards)
|
||||
- AddPlantForm (nieuw plant toevoegen)
|
||||
App
|
||||
├── Header
|
||||
├── SearchForm (input + submit)
|
||||
├── RecipeList
|
||||
│ └── RecipeCard (repeated)
|
||||
│ ├── Title
|
||||
│ ├── Ingredients
|
||||
│ ├── Favorite button
|
||||
│ └── View details
|
||||
└── Footer
|
||||
```
|
||||
|
||||
**Questions to ask:**
|
||||
- Welke componenten worden herhaald?
|
||||
- Welke componenten zijn child components?
|
||||
- Welke componenten hebben state?
|
||||
- Waar gaat data vandaan?
|
||||
|
||||
---
|
||||
|
||||
### MVP (Minimum Viable Product) Denken
|
||||
### MVP Denken
|
||||
|
||||
**Wat is MVP?**
|
||||
De simpelste versie van je app die nog steeds waarde levert.
|
||||
**MVP = Minimum Viable Product**
|
||||
|
||||
Het eenvoudigst mogelijke product dat nog steeds waarde levert.
|
||||
|
||||
**❌ Niet MVP:**
|
||||
- Alle features tegelijk
|
||||
- Perfect design
|
||||
- Edge cases afhandelen
|
||||
- Login systeem
|
||||
- Perfect design met animaties
|
||||
- Geavanceerde filters
|
||||
- Social features
|
||||
|
||||
**✅ Wel MVP:**
|
||||
- Core functionaliteit werkt
|
||||
- Basis styling
|
||||
- Basis styling (Tailwind)
|
||||
- Happy path werkt
|
||||
- Hardcoded data is oké
|
||||
- Data persists (database)
|
||||
|
||||
---
|
||||
|
||||
### De Prototype Workflow
|
||||
### Database Planning
|
||||
|
||||
**Voor recipe app:**
|
||||
```
|
||||
1. IDEE (1-2 zinnen)
|
||||
↓
|
||||
2. FEATURES (AI breakdown)
|
||||
↓
|
||||
3. PRIORITEER (wat is MVP?)
|
||||
↓
|
||||
4. COMPONENTS (welke blokken?)
|
||||
↓
|
||||
5. BOUWEN (tool per stap)
|
||||
↓
|
||||
6. ITEREREN (feedback → aanpassen)
|
||||
users table:
|
||||
- id, email, password_hash, created_at
|
||||
|
||||
recipes table:
|
||||
- id, title, ingredients, instructions, created_at
|
||||
|
||||
favorites table:
|
||||
- id, user_id, recipe_id, created_at
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Voorbeeld: Weer Widget Prototype
|
||||
|
||||
**Stap 1: Idee**
|
||||
"Simpele weer widget met 3-daagse forecast"
|
||||
|
||||
**Stap 2: AI Feature Breakdown**
|
||||
```
|
||||
Vraag aan ChatGPT:
|
||||
"Wat zijn de minimale features voor een weer widget met 3-daagse forecast?"
|
||||
|
||||
Antwoord:
|
||||
- Huidige temperatuur tonen
|
||||
- Weer icoon (zon, regen, etc.)
|
||||
- 3-daagse forecast (dag + temp + icoon)
|
||||
- Locatie tonen
|
||||
```
|
||||
|
||||
**Stap 3: MVP Selectie**
|
||||
- ✅ Huidige temperatuur
|
||||
- ✅ Weer icoon
|
||||
- ✅ 3 dagen forecast
|
||||
- ❌ Locatie selectie (later)
|
||||
- ❌ Animated icons (later)
|
||||
|
||||
**Stap 4: Components**
|
||||
```
|
||||
WeatherWidget/
|
||||
├── CurrentWeather (temp + icoon)
|
||||
├── ForecastDay (dag + temp + icoon)
|
||||
└── ForecastList (3x ForecastDay)
|
||||
```
|
||||
|
||||
**Stap 5: Bouwen**
|
||||
1. v0.dev: "Weather widget with current temp and 3 day forecast, minimal design"
|
||||
2. OpenCode: "Integreer dit in mijn project, maak components in src/components/weather/"
|
||||
|
||||
**Stap 6: Itereren**
|
||||
- Wat werkt niet?
|
||||
- Wat kan beter?
|
||||
- Vraag AI om verbeteringen
|
||||
|
||||
---
|
||||
|
||||
### Mini-Project Ideeën
|
||||
|
||||
| Project | Core Feature | Complexiteit |
|
||||
|---------|-------------|--------------|
|
||||
| **Weer Widget** | 3-daagse forecast | ⭐ |
|
||||
| **Quiz App** | 3 vragen + score | ⭐ |
|
||||
| **Recipe Card** | Ingrediënten toggle | ⭐ |
|
||||
| **Pomodoro Timer** | Start/stop + countdown | ⭐⭐ |
|
||||
| **Expense Tracker** | Lijst + totaal | ⭐⭐ |
|
||||
**Relations:**
|
||||
- users → favorites (one-to-many)
|
||||
- users → saved_recipes (one-to-many)
|
||||
- recipes → favorites (one-to-many)
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Cursor
|
||||
- ChatGPT (voor planning)
|
||||
- v0.dev (voor prototypes)
|
||||
- OpenCode/WebStorm (voor implementation)
|
||||
- Pen & papier (voor sketches)
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (2 uur, klassikaal)
|
||||
|
||||
### Bouw Je Mini-Prototype
|
||||
### Kies je Eindproject & Plan Architectuur
|
||||
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de Tool Selection reflecties uit Les 10 - welke workflows werken het beste?
|
||||
Bespreek klassikaal de styling ervaringen uit Les 10 - welke Tailwind en shadcn/ui patterns werkten goed?
|
||||
|
||||
**Deel 1: Planning (30 min)**
|
||||
**Deel 1: Idee Kiezen (30 min)**
|
||||
|
||||
1. Kies een project uit de lijst (of bedenk eigen simpel idee)
|
||||
2. Schrijf je idee in 1-2 zinnen
|
||||
3. Vraag ChatGPT om feature breakdown
|
||||
4. Selecteer MVP features (max 3)
|
||||
5. Schets de components op papier
|
||||
Kies één van deze projecten OF je eigen idee (met goedkeuring docent):
|
||||
- **AI Recipe Generator** - Input: ingredients, Output: recipe suggestions + cooking tips
|
||||
- **Smart Budget Buddy** - Track expenses, AI insights on spending patterns
|
||||
- **Travel Planner AI** - Generate itineraries based on preferences
|
||||
- **Study Buddy AI** - Quiz generation, note-taking, Q&A helper
|
||||
- **Jouw eigen idee**
|
||||
|
||||
**Deel 2: Bouwen (1 uur)**
|
||||
**Deel 2: Feature Breakdown (30 min)**
|
||||
|
||||
1. Genereer UI in v0.dev
|
||||
2. Open project in OpenCode
|
||||
3. Integreer en pas aan
|
||||
4. Zorg dat het werkt (happy path)
|
||||
Voor je gekozen project:
|
||||
1. Schrijf project description (2-3 zinnen)
|
||||
2. Maak 3-5 user stories
|
||||
3. Break down in core vs nice-to-have features
|
||||
4. Selecteer MVP (wat is essentieel?)
|
||||
|
||||
**Focus op WORKFLOW, niet perfectie!**
|
||||
**Deel 3: Architecture Planning (45 min)**
|
||||
|
||||
**Deel 3: Documentatie (15 min)**
|
||||
1. Schets database schema
|
||||
2. List de main components
|
||||
3. Plan folder structure
|
||||
4. Identify donde AI integreert (tool calling? Chat? Completion?)
|
||||
|
||||
Maak `docs/PROTOTYPE-LOG.md`:
|
||||
- Je idee
|
||||
- Feature breakdown
|
||||
- MVP keuzes
|
||||
- Prompts die werkten
|
||||
- Wat ging fout en hoe opgelost
|
||||
**Deel 4: Start Codebase (15 min)**
|
||||
|
||||
1. Maak GitHub repo
|
||||
2. Create-next-app setup
|
||||
3. Push initial commit
|
||||
|
||||
### Deliverable
|
||||
- Werkend prototype (kan simpel zijn)
|
||||
- `docs/PROTOTYPE-LOG.md` met je proces
|
||||
- Screenshot van werkend prototype
|
||||
- Project description document
|
||||
- Feature breakdown met user stories
|
||||
- Database schema diagram (hand-drawn ok)
|
||||
- Component tree visualization
|
||||
- GitHub repo met initial setup
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Verbeter en Reflecteer
|
||||
### Bouw Basis Architecture
|
||||
|
||||
**Deel 1: Prototype Verbeteren (1 uur)**
|
||||
**Deel 1: Folder Structure (30 min)**
|
||||
|
||||
1. Fix eventuele bugs
|
||||
2. Voeg 1 extra feature toe
|
||||
3. Verbeter styling
|
||||
4. Handle 1 edge case
|
||||
Create proper folder structure:
|
||||
- src/components/ui/
|
||||
- src/components/features/
|
||||
- src/lib/
|
||||
- src/hooks/
|
||||
- src/types/
|
||||
- docs/
|
||||
|
||||
**Deel 2: Peer Review (30 min)**
|
||||
**Deel 2: Type Definitions (30 min)**
|
||||
|
||||
- Deel je prototype met een klasgenoot
|
||||
- Krijg feedback
|
||||
- Geef feedback op hun prototype
|
||||
Schrijf TypeScript types voor je project:
|
||||
- User type
|
||||
- Main data types
|
||||
- API response types
|
||||
|
||||
**Deel 3: Reflectie (30 min)**
|
||||
**Deel 3: Component Skeleton (1 uur)**
|
||||
|
||||
Schrijf "Lessons Learned" document (300 woorden):
|
||||
- Wat ging goed in je workflow?
|
||||
- Waar liep je vast?
|
||||
- Welke tool was het meest nuttig?
|
||||
- Wat doe je volgende keer anders?
|
||||
- Hoe voelde het om met AI te bouwen vs alleen?
|
||||
Maak skeleton components:
|
||||
- Main layout
|
||||
- 3-4 main feature components (without logic)
|
||||
- Proper props interfaces
|
||||
- Basic Tailwind styling
|
||||
|
||||
### Deliverable
|
||||
- Verbeterd prototype
|
||||
- Peer review feedback (gegeven en ontvangen)
|
||||
- Lessons Learned document (300 woorden)
|
||||
- GitHub commits met folder structure
|
||||
- types/ folder met je data types
|
||||
- Skeleton components
|
||||
- README.md met project description
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Van een vaag idee naar concrete features gaan
|
||||
- AI gebruiken voor feature breakdown
|
||||
- MVP denken toepassen (essentieel vs nice-to-have)
|
||||
- Een app opdelen in components
|
||||
- De complete workflow doorlopen (idee → prototype)
|
||||
- Het bouwproces documenteren
|
||||
- Reflecteren op wat werkt en wat niet
|
||||
- Een project idee van vaag naar concreet uitwerken
|
||||
- User stories schrijven
|
||||
- Feature breakdown maken
|
||||
- MVP bepalen
|
||||
- Database schema ontwerpen
|
||||
- Component architecture plannen
|
||||
- Project codebase opzetten
|
||||
- TypeScript types structureren
|
||||
- Voorbereiding maken op verdere implementatie
|
||||
|
||||
@@ -1,261 +1,157 @@
|
||||
# Les 12: Introduction to Cursor
|
||||
# Les 12: Project Setup & AI Config (.cursorrules, claude.md)
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 3: AI Tooling & Prototyping** (Les 10-12)
|
||||
**Deel 3: Full-Stack Development** (Les 9-12)
|
||||
|
||||
## Beschrijving
|
||||
Kennismaking met Cursor - de professionele AI code editor. Leer de core features en ontdek waarom dit de tool is voor serieuze AI-assisted development.
|
||||
Professional project setup en AI configuration voor optimal developer experience. Setup .cursorrules, claude.md, docs/ folder, folder structure optimalisatie en git best practices.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~45 min)
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de prototype ervaringen uit Les 11 - welke workflow patterns werkten goed? Wat ging fout en hoe loste je dat op?
|
||||
|
||||
### Waarom Cursor?
|
||||
|
||||
**Tot nu toe gebruikten we:**
|
||||
- OpenCode (gratis, goed voor leren)
|
||||
- Claude Desktop (voor agents en projects)
|
||||
|
||||
**Cursor is de volgende stap:**
|
||||
- Purpose-built voor AI-assisted coding
|
||||
- Professionele editor (gebaseerd op VS Code)
|
||||
- Superieure AI integratie
|
||||
- Free tier beschikbaar (voldoende voor de cursus)
|
||||
|
||||
---
|
||||
|
||||
### Free vs Pro
|
||||
|
||||
| Aspect | Free Tier | Pro ($20/maand) |
|
||||
|--------|-----------|-----------------|
|
||||
| Tab completion | ✅ | ✅ |
|
||||
| CMD+K edits | Beperkt | Onbeperkt |
|
||||
| Chat | Beperkt | Onbeperkt |
|
||||
| Composer | Beperkt | Onbeperkt |
|
||||
| Models | GPT-5, Claude | Alle modellen |
|
||||
|
||||
**Voor deze cursus:** Free tier is voldoende!
|
||||
|
||||
---
|
||||
|
||||
### Installatie
|
||||
|
||||
1. Ga naar [cursor.sh](https://cursor.sh)
|
||||
2. Download voor jouw OS
|
||||
3. Installeer
|
||||
4. Open Cursor
|
||||
5. Sign in met GitHub
|
||||
|
||||
**Eerste keer:**
|
||||
- Cursor vraagt om settings te importeren van VS Code (optioneel)
|
||||
- Accept default keybindings
|
||||
|
||||
---
|
||||
|
||||
### Core Features
|
||||
|
||||
#### 1. Tab Completion
|
||||
AI-powered autocomplete die hele blokken code voorspelt.
|
||||
|
||||
**Hoe het werkt:**
|
||||
- Begin met typen
|
||||
- Cursor suggereert code in grijs
|
||||
- Druk Tab om te accepteren
|
||||
- Druk Escape om te negeren
|
||||
|
||||
**Tip:** Schrijf een comment over wat je wilt, en Tab completion vult de code in.
|
||||
|
||||
```typescript
|
||||
// Function that calculates the total price with tax
|
||||
// [Tab completion vult de functie in]
|
||||
```
|
||||
|
||||
#### 2. CMD+K (Inline Editing)
|
||||
Selecteer code en vraag AI om het aan te passen.
|
||||
|
||||
**Hoe het werkt:**
|
||||
1. Selecteer code (of zet cursor op een regel)
|
||||
2. Druk CMD+K (Mac) of Ctrl+K (Windows)
|
||||
3. Typ je instructie
|
||||
4. Enter om te genereren
|
||||
5. Accept of Reject de wijziging
|
||||
|
||||
**Voorbeelden:**
|
||||
- "Add error handling"
|
||||
- "Convert to TypeScript"
|
||||
- "Make this responsive"
|
||||
- "Add loading state"
|
||||
|
||||
#### 3. Chat (Sidebar)
|
||||
Converseer met AI over je code.
|
||||
|
||||
**Hoe het werkt:**
|
||||
1. CMD+Shift+L opent Chat
|
||||
2. Stel je vraag
|
||||
3. AI heeft context van je open file
|
||||
|
||||
**Voorbeelden:**
|
||||
- "Explain what this function does"
|
||||
- "How can I optimize this?"
|
||||
- "What's wrong with this code?"
|
||||
|
||||
#### 4. @ Mentions
|
||||
Refereer naar files, folders, of documentatie.
|
||||
|
||||
**Syntax:**
|
||||
- `@filename.tsx` - specifieke file
|
||||
- `@folder/` - hele folder
|
||||
- `@Docs` - officiële docs zoeken
|
||||
- `@Web` - web zoeken
|
||||
|
||||
**Voorbeeld:**
|
||||
```
|
||||
@components/Button.tsx - How can I add a loading prop to this?
|
||||
```
|
||||
|
||||
#### 5. Composer Mode
|
||||
Multi-file generatie in één keer.
|
||||
|
||||
**Hoe het werkt:**
|
||||
1. CMD+I opent Composer
|
||||
2. Beschrijf wat je wilt bouwen
|
||||
3. AI genereert meerdere files tegelijk
|
||||
4. Review en accept changes
|
||||
|
||||
**Wanneer gebruiken:**
|
||||
- Nieuwe features met meerdere components
|
||||
- Refactoring over meerdere files
|
||||
- Boilerplate code genereren
|
||||
|
||||
---
|
||||
|
||||
### Workflow Vergelijking
|
||||
|
||||
| Taak | OpenCode | Cursor |
|
||||
|------|----------|--------|
|
||||
| Snelle fix | Chat | CMD+K |
|
||||
| Nieuwe component | Chat | Tab completion + CMD+K |
|
||||
| Multi-file feature | Meerdere chats | Composer |
|
||||
| Code uitleg | Chat | Chat + @ mentions |
|
||||
| Refactoring | Chat | CMD+K of Composer |
|
||||
|
||||
---
|
||||
|
||||
### Keyboard Shortcuts Cheat Sheet
|
||||
|
||||
| Actie | Mac | Windows |
|
||||
|-------|-----|---------|
|
||||
| Tab completion accept | Tab | Tab |
|
||||
| Inline edit | CMD+K | Ctrl+K |
|
||||
| Open Chat | CMD+Shift+L | Ctrl+Shift+L |
|
||||
| Open Composer | CMD+I | Ctrl+I |
|
||||
| Accept suggestion | CMD+Y | Ctrl+Y |
|
||||
| Reject suggestion | CMD+N | Ctrl+N |
|
||||
- Waarom goede project structuur cruciaal is voor AI tools
|
||||
- De ideale folder structuur voor Next.js + AI collaboration
|
||||
- .cursorrules file: syntax, best practices, examples
|
||||
- claude.md: project documentation voor Claude
|
||||
- docs/ folder organization: AI-DECISIONS.md, ARCHITECTURE.md, PROMPT-LOG.md
|
||||
- .env.local vs .env.example configuratie
|
||||
- .gitignore en .cursorignore voor AI tools
|
||||
- .git best practices: meaningful commits, proper history
|
||||
- README setup met project info
|
||||
- TypeScript configuration optimization
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Cursor
|
||||
- Git
|
||||
- GitHub
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (2 uur, klassikaal)
|
||||
|
||||
### Cursor Verkennen
|
||||
### Setup Je Eindproject Proper
|
||||
|
||||
**Deel 1: Setup (20 min)**
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de architecture planning uit Les 11 - welke design patterns voelen goed voor jullie eindproject?
|
||||
|
||||
1. Download en installeer Cursor
|
||||
2. Sign in met GitHub
|
||||
3. Open je Todo app project
|
||||
4. Verken de interface
|
||||
**Deel 1: Folder Structure (30 min)**
|
||||
|
||||
**Deel 2: Tab Completion (30 min)**
|
||||
Zorg dat je project er zo uitziet:
|
||||
```
|
||||
project/
|
||||
├── src/
|
||||
│ ├── app/
|
||||
│ ├── components/
|
||||
│ │ ├── ui/
|
||||
│ │ ├── layout/
|
||||
│ │ └── features/
|
||||
│ ├── lib/
|
||||
│ ├── hooks/
|
||||
│ └── types/
|
||||
├── docs/
|
||||
├── public/
|
||||
├── .cursorrules
|
||||
├── .env.local
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
└── claude.md
|
||||
```
|
||||
|
||||
Maak nieuwe file `src/components/LoadingSpinner.tsx`:
|
||||
1. Typ comment: `// Loading spinner component with size prop`
|
||||
2. Laat Tab completion de rest doen
|
||||
3. Itereer met meer comments
|
||||
4. Noteer: hoe goed is de completion?
|
||||
**Deel 2: .cursorrules Writing (30 min)**
|
||||
|
||||
**Deel 3: CMD+K Oefenen (30 min)**
|
||||
Maak comprehensive `.cursorrules`:
|
||||
1. Project naam + beschrijving
|
||||
2. Tech stack (Next.js 14, TypeScript, Tailwind, Supabase)
|
||||
3. File structure conventions
|
||||
4. Code conventions (naming, styling, error handling)
|
||||
5. TypeScript strict rules
|
||||
6. DO's en DON'Ts specifiek voor je project
|
||||
|
||||
Open je TodoList component:
|
||||
1. Selecteer de list rendering code
|
||||
2. CMD+K → "Add loading state with skeleton"
|
||||
3. Selecteer een button
|
||||
4. CMD+K → "Add disabled state while loading"
|
||||
5. Selecteer een function
|
||||
6. CMD+K → "Add try-catch with error toast"
|
||||
Example snippets:
|
||||
- "Named exports only, no default exports"
|
||||
- "All components are functional with TypeScript"
|
||||
- "Use Tailwind classes, no inline styles"
|
||||
|
||||
Noteer wat werkt en wat niet.
|
||||
**Deel 3: Documentation Files (30 min)**
|
||||
|
||||
**Deel 4: Chat + @ Mentions (20 min)**
|
||||
Maak in docs/ folder:
|
||||
- `PROJECT-BRIEF.md` - Project overview, features
|
||||
- `ARCHITECTURE.md` - Component structure, data flow
|
||||
- `AI-DECISIONS.md` - Start document met AI choices
|
||||
- `PROMPT-LOG.md` - Template voor prompts die je gebruikt
|
||||
|
||||
1. Open Chat (CMD+Shift+L)
|
||||
2. Vraag: "@TodoList.tsx What could I improve in this component?"
|
||||
3. Vraag: "@lib/supabase.ts How do I add real-time subscriptions?"
|
||||
4. Probeer @Docs voor Next.js documentatie
|
||||
**Deel 4: Git Setup (20 min)**
|
||||
|
||||
**Deel 5: Composer Proberen (20 min)**
|
||||
|
||||
1. Open Composer (CMD+I)
|
||||
2. Vraag: "Create a Settings page with dark mode toggle and notification preferences. Use our existing component style."
|
||||
3. Review de gegenereerde files
|
||||
4. Accept of reject
|
||||
1. Review `.gitignore` (include `.env.local`)
|
||||
2. Make initial commit: "Initial project setup"
|
||||
3. Push naar GitHub
|
||||
4. Verify: `.env.local` NOT in git history
|
||||
|
||||
### Deliverable
|
||||
- Screenshot van werkende Tab completion
|
||||
- 3 voorbeelden van CMD+K edits
|
||||
- Notities: wat werkt wel/niet goed
|
||||
- Complete folder structure
|
||||
- Comprehensive .cursorrules file
|
||||
- Documentation files in docs/
|
||||
- GitHub repo met clean initial commit
|
||||
- README.md with project info
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Bouw Feature met Cursor
|
||||
### Optimize Configuration & Start Building
|
||||
|
||||
**Deel 1: Feature Bouwen (1.5 uur)**
|
||||
**Deel 1: tsconfig.json Optimization (30 min)**
|
||||
|
||||
Voeg "Due Dates" toe aan je Todo app:
|
||||
1. Elk todo kan een due date hebben
|
||||
2. Toon due date in de lijst
|
||||
3. Sorteer op due date
|
||||
4. Markeer overdue items in rood
|
||||
1. Enable strict mode in TypeScript
|
||||
2. Configure path aliases for cleaner imports:
|
||||
```json
|
||||
"@/*": ["./src/*"]
|
||||
```
|
||||
3. Set proper include/exclude
|
||||
|
||||
**Gebruik ALLE Cursor features:**
|
||||
- Tab completion voor nieuwe code
|
||||
- CMD+K voor aanpassingen
|
||||
- Chat voor vragen
|
||||
- Composer voor multi-file changes
|
||||
**Deel 2: Tailwind & Component Setup (45 min)**
|
||||
|
||||
**Deel 2: Reflectie (30 min)**
|
||||
1. Customize `tailwind.config.ts` with your colors
|
||||
2. Setup `globals.css` properly
|
||||
3. Create 5 base UI components:
|
||||
- Button.tsx
|
||||
- Input.tsx
|
||||
- Card.tsx
|
||||
- Layout.tsx
|
||||
- Navigation.tsx
|
||||
|
||||
Schrijf vergelijking (400 woorden):
|
||||
- Cursor vs OpenCode: wat is beter?
|
||||
- Welke feature gebruik je het meest?
|
||||
- Is free tier voldoende?
|
||||
- Ga je overstappen?
|
||||
**Deel 3: Lib Setup (30 min)**
|
||||
|
||||
Create essential lib files:
|
||||
- `lib/supabase.ts` - Initialize Supabase client
|
||||
- `lib/utils.ts` - Utility functions
|
||||
- `lib/constants.ts` - App constants
|
||||
- `types/database.ts` - Database types
|
||||
|
||||
### Deliverable
|
||||
- Werkende Due Dates feature
|
||||
- GitHub commit met de changes
|
||||
- Reflectie (400 woorden)
|
||||
- Optimized TypeScript config with path aliases
|
||||
- Base UI components created
|
||||
- Lib utilities setup
|
||||
- Documentation updated with decisions
|
||||
- GitHub commits with setup progress
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Cursor installeren en configureren
|
||||
- Tab completion effectief gebruiken
|
||||
- CMD+K gebruiken voor inline edits
|
||||
- Chat gebruiken met @ mentions voor context
|
||||
- Composer mode gebruiken voor multi-file generatie
|
||||
- Het verschil beoordelen tussen Cursor en OpenCode
|
||||
- De juiste Cursor feature kiezen per taak
|
||||
- Een professional project structure opzetten
|
||||
- Een effectieve .cursorrules file schrijven
|
||||
- claude.md projectdocumentatie maken
|
||||
- AI-DECISIONS.md beginnen en onderhouden
|
||||
- Git best practices volgen
|
||||
- TypeScript strict mode configureren
|
||||
- Path aliases voor cleaner imports opzetten
|
||||
- Base components en utilities creëren
|
||||
- Voorbereiding maken op Deel 4 (Advanced AI)
|
||||
|
||||
@@ -1,345 +1,298 @@
|
||||
# Les 13: Prompt Engineering & Custom GPTs
|
||||
# Les 13: Vercel AI SDK, Tool Calling & Agents
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 4: Advanced AI Features** (Les 13-18)
|
||||
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
||||
|
||||
## Beschrijving
|
||||
Verdiep je in advanced prompt engineering en leer eigen AI assistenten maken met Custom GPTs en Claude Projects. Focus op no-code manieren om AI te personaliseren voor jouw workflow.
|
||||
Vercel AI SDK fundamentals voor het bouwen van AI-powered features. Stream responses, tool calling, Zod schemas, system prompts, agents met autonome actie. Integreer LLM capabilities in je app.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~45 min)
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de Cursor reflecties uit Les 12 - welke features werken het beste voor welke taken?
|
||||
- Vercel AI SDK: wat is het en waarom gebruiken?
|
||||
- Installation en basic setup
|
||||
- useChat hook voor chat UI state management
|
||||
- Streaming responses van API
|
||||
- Tool calling: laat AI externe APIs aanroepen
|
||||
- Zod schemas voor tool parameters validation
|
||||
- System prompts schrijven voor AI behavior
|
||||
- Agent patterns: maxSteps, autonomous execution
|
||||
- Error handling en edge cases
|
||||
- Model selection: OpenAI, Claude, Gemini, etc.
|
||||
|
||||
### Advanced Prompt Engineering
|
||||
---
|
||||
|
||||
**Recap van basis technieken (Les 4):**
|
||||
- Zero-shot vs few-shot prompting
|
||||
- Chain-of-thought reasoning
|
||||
- Role prompting
|
||||
### Vercel AI SDK Basics
|
||||
|
||||
**Nieuwe technieken:**
|
||||
**Wat is het?**
|
||||
- React library van Vercel voor AI integration
|
||||
- Streaming responses van LLMs
|
||||
- Server-side tool calling
|
||||
- Multi-turn conversations
|
||||
- Gratis, open-source
|
||||
|
||||
#### 1. Structured Output Prompting
|
||||
```
|
||||
Analyseer deze code en geef feedback in dit exacte format:
|
||||
|
||||
## Samenvatting
|
||||
[1 zin over de code]
|
||||
|
||||
## Sterke punten
|
||||
- [punt 1]
|
||||
- [punt 2]
|
||||
|
||||
## Verbeterpunten
|
||||
- [punt 1 met code voorbeeld]
|
||||
- [punt 2 met code voorbeeld]
|
||||
|
||||
## Prioriteit
|
||||
[Hoog/Medium/Laag]: [waarom]
|
||||
```
|
||||
|
||||
#### 2. Constraint-Based Prompting
|
||||
```
|
||||
Schrijf een React component met deze constraints:
|
||||
- Maximaal 50 regels code
|
||||
- Geen externe dependencies
|
||||
- TypeScript met strict types
|
||||
- Alleen Tailwind voor styling
|
||||
- Inclusief error handling
|
||||
```
|
||||
|
||||
#### 3. Iterative Refinement
|
||||
```
|
||||
Stap 1: "Schrijf een basis login form"
|
||||
Stap 2: "Voeg validatie toe"
|
||||
Stap 3: "Voeg loading states toe"
|
||||
Stap 4: "Voeg error handling toe"
|
||||
Stap 5: "Optimaliseer voor accessibility"
|
||||
**Installation:**
|
||||
```bash
|
||||
npm install ai zod openai
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Custom GPTs
|
||||
### useChat Hook
|
||||
|
||||
**Wat zijn Custom GPTs?**
|
||||
Gespecialiseerde ChatGPT versies met:
|
||||
- Specifieke instructies (personality, expertise)
|
||||
- Eigen kennis (uploaded files)
|
||||
- Optioneel: Actions (API calls)
|
||||
**Client-side chat state management:**
|
||||
|
||||
**Wanneer een Custom GPT maken:**
|
||||
- Repetitieve taken met dezelfde context
|
||||
- Specifieke expertise nodig
|
||||
- Delen met team of anderen
|
||||
```typescript
|
||||
'use client'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
**Custom GPT maken:**
|
||||
1. Ga naar chat.openai.com/gpts
|
||||
2. Klik "Create"
|
||||
3. Configureer in "Create" tab OF gebruik "Configure"
|
||||
export function ChatComponent() {
|
||||
const { messages, input, handleInputChange, handleSubmit } = useChat({
|
||||
api: '/api/chat',
|
||||
})
|
||||
|
||||
**Voorbeeld: Code Review GPT**
|
||||
```
|
||||
Instructions:
|
||||
Je bent een code reviewer gespecialiseerd in React/Next.js.
|
||||
|
||||
Bij elke code review check je:
|
||||
1. TypeScript best practices
|
||||
2. React hooks correct gebruik
|
||||
3. Performance (unnecessary re-renders)
|
||||
4. Accessibility basics
|
||||
5. Error handling
|
||||
|
||||
Geef feedback in dit format:
|
||||
- ✅ Goed: [wat goed is]
|
||||
- ⚠️ Suggestie: [verbeterpunten]
|
||||
- ❌ Issue: [problemen die gefixed moeten worden]
|
||||
```
|
||||
|
||||
**Voorbeeld: Project Assistant GPT**
|
||||
```
|
||||
Instructions:
|
||||
Je bent mijn persoonlijke development assistant voor [project naam].
|
||||
|
||||
Tech stack:
|
||||
- Next.js 14 met App Router
|
||||
- TypeScript strict mode
|
||||
- Tailwind CSS
|
||||
- Supabase voor backend
|
||||
|
||||
Je kent de volgende conventies:
|
||||
- Named exports (geen default exports)
|
||||
- Nederlandse comments
|
||||
- Error handling met try/catch
|
||||
|
||||
Wanneer ik code vraag:
|
||||
1. Vraag eerst om context als die ontbreekt
|
||||
2. Geef TypeScript code met types
|
||||
3. Voeg korte uitleg toe
|
||||
return (
|
||||
<div>
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id}>
|
||||
<strong>{msg.role}:</strong> {msg.content}
|
||||
</div>
|
||||
))}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Type message..."
|
||||
/>
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Claude Projects
|
||||
### Streaming Responses
|
||||
|
||||
**Wat is een Claude Project?**
|
||||
- Verzameling van context specifiek voor één doel
|
||||
- Blijft behouden over conversaties
|
||||
- Kan bestanden bevatten als knowledge base
|
||||
**API Route met streaming:**
|
||||
|
||||
**Wanneer gebruiken:**
|
||||
- Terugkerend werk aan hetzelfde project
|
||||
- Consistente coding style nodig
|
||||
- Documentatie die AI moet kennen
|
||||
```typescript
|
||||
import { generateText, streamText } from 'ai'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
|
||||
**Project aanmaken:**
|
||||
1. Ga naar claude.ai → Projects
|
||||
2. Klik "New Project"
|
||||
3. Voeg project knowledge toe (files, instructies)
|
||||
4. Start conversaties binnen het project
|
||||
export async function POST(req: Request) {
|
||||
const { messages } = await req.json()
|
||||
|
||||
**Voorbeeld Project Instructions:**
|
||||
```
|
||||
Je bent een expert React/Next.js developer die mij helpt met [project].
|
||||
const result = await streamText({
|
||||
model: openai('gpt-4'),
|
||||
system: 'You are a helpful assistant',
|
||||
messages,
|
||||
})
|
||||
|
||||
Technologie stack:
|
||||
- Next.js 14 met App Router
|
||||
- TypeScript strict mode
|
||||
- Tailwind CSS
|
||||
- Supabase voor backend
|
||||
- React Query voor data fetching
|
||||
|
||||
Coding conventions:
|
||||
- Functional components met TypeScript
|
||||
- Named exports (geen default exports)
|
||||
- Error handling met try/catch
|
||||
- Nederlandse comments in code
|
||||
|
||||
Wanneer je code schrijft:
|
||||
- Gebruik altijd TypeScript types
|
||||
- Voeg JSDoc comments toe voor complexe functies
|
||||
- Denk aan edge cases en error handling
|
||||
return result.toAIStreamResponse()
|
||||
}
|
||||
```
|
||||
|
||||
**Project Knowledge toevoegen:**
|
||||
- Upload je belangrijkste files (schema, README, .cursorrules)
|
||||
- Upload voorbeeldcode die je stijl toont
|
||||
- Upload documentatie van libraries die je gebruikt
|
||||
**Waarom streaming?**
|
||||
- Responses verschijnen real-time (beter UX)
|
||||
- Bespaar tokens vs waiting for full response
|
||||
|
||||
---
|
||||
|
||||
### Custom GPTs vs Claude Projects
|
||||
### Tool Calling
|
||||
|
||||
| Aspect | Custom GPT | Claude Project |
|
||||
|--------|------------|----------------|
|
||||
| **Beschikbaar** | ChatGPT Plus ($20/maand) | Claude Pro ($20/maand) |
|
||||
| **Knowledge** | File uploads (tot 20 files) | File uploads (tot 200k tokens) |
|
||||
| **Delen** | Kan gepubliceerd worden | Alleen persoonlijk |
|
||||
| **Actions** | Ja (API calls) | Nee |
|
||||
| **Context window** | ~128k tokens | ~200k tokens |
|
||||
| **Beste voor** | Gedeelde tools, API integratie | Persoonlijke projecten, grote codebases |
|
||||
**Laat AI externe APIs aanroepen:**
|
||||
|
||||
```typescript
|
||||
import { generateText } from 'ai'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { z } from 'zod'
|
||||
|
||||
const tools = {
|
||||
getWeather: {
|
||||
description: 'Get weather for a city',
|
||||
parameters: z.object({
|
||||
city: z.string(),
|
||||
}),
|
||||
execute: async ({ city }: { city: string }) => {
|
||||
// Call external API
|
||||
const response = await fetch(`https://api.weather.com?city=${city}`)
|
||||
return response.json()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4'),
|
||||
tools,
|
||||
prompt: 'What is the weather in Amsterdam?',
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Prompt Templates Library
|
||||
### Zod Schemas
|
||||
|
||||
**Code Generation:**
|
||||
```
|
||||
Context: [beschrijf je project/stack]
|
||||
Taak: [wat moet er gemaakt worden]
|
||||
Requirements:
|
||||
- [requirement 1]
|
||||
- [requirement 2]
|
||||
Constraints:
|
||||
- [constraint 1]
|
||||
Output: [gewenst format]
|
||||
**Type-safe tool parameters:**
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
const SearchProductsSchema = z.object({
|
||||
query: z.string().describe('Search query'),
|
||||
limit: z.number().optional().describe('Max results'),
|
||||
sortBy: z.enum(['price', 'rating']).optional(),
|
||||
})
|
||||
|
||||
type SearchProductsInput = z.infer<typeof SearchProductsSchema>
|
||||
```
|
||||
|
||||
**Debugging:**
|
||||
```
|
||||
Error message:
|
||||
[plak error]
|
||||
---
|
||||
|
||||
Relevante code:
|
||||
[plak code]
|
||||
### System Prompts
|
||||
|
||||
Wat ik verwacht:
|
||||
[gewenst gedrag]
|
||||
**Stuur AI behavior:**
|
||||
|
||||
Wat er gebeurt:
|
||||
[actueel gedrag]
|
||||
```typescript
|
||||
const systemPrompt = `You are a helpful recipe assistant.
|
||||
Your role is to:
|
||||
1. Suggest recipes based on ingredients
|
||||
2. Provide cooking instructions
|
||||
3. Estimate cooking time
|
||||
|
||||
Always be friendly and encouraging.`
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4'),
|
||||
system: systemPrompt,
|
||||
prompt: userMessage,
|
||||
})
|
||||
```
|
||||
|
||||
**Code Review:**
|
||||
```
|
||||
Review de volgende code op:
|
||||
1. Best practices voor [framework]
|
||||
2. Potentiële bugs
|
||||
3. Performance issues
|
||||
4. Security vulnerabilities
|
||||
---
|
||||
|
||||
[plak code]
|
||||
### Agent Patterns
|
||||
|
||||
Geef feedback in format: ✅ Goed / ⚠️ Suggestie / ❌ Issue
|
||||
**Multi-step autonomous execution:**
|
||||
|
||||
```typescript
|
||||
const result = await generateText({
|
||||
model: openai('gpt-4'),
|
||||
tools: { getWeather, getFlights },
|
||||
maxSteps: 3, // Maximum iterations
|
||||
prompt: 'Plan a trip to Paris next week',
|
||||
})
|
||||
```
|
||||
|
||||
**Refactoring:**
|
||||
```
|
||||
Refactor deze code met de volgende doelen:
|
||||
- [doel 1, bijv. "verbeter leesbaarheid"]
|
||||
- [doel 2, bijv. "reduceer duplicatie"]
|
||||
|
||||
Behoud dezelfde functionaliteit.
|
||||
Leg uit wat je verandert en waarom.
|
||||
|
||||
[plak code]
|
||||
```
|
||||
**Hoe het werkt:**
|
||||
1. AI bepaalt welke tool nodig is
|
||||
2. Tool wordt uitgevoerd
|
||||
3. Result teruggestuurd naar AI
|
||||
4. AI beslist next stap (repeat tot maxSteps of done)
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- ChatGPT (Custom GPTs)
|
||||
- Claude (Projects)
|
||||
- Prompt template documenten
|
||||
- Vercel AI SDK
|
||||
- Zod
|
||||
- OpenAI API (of andere LLM provider)
|
||||
- Cursor
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (2 uur, klassikaal)
|
||||
|
||||
### Bouw Je Eigen AI Assistants
|
||||
### Bouw Chat Interface met Streaming
|
||||
|
||||
**Deel 1: Claude Project (45 min)**
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de project setup ervaringen uit Les 12 - hoe goed werken jullie .cursorrules en configuration?
|
||||
|
||||
Maak een Claude Project voor je eindproject:
|
||||
1. Ga naar claude.ai → Projects → New Project
|
||||
2. Schrijf project instructions:
|
||||
- Tech stack
|
||||
- Coding conventions
|
||||
- Project specifieke regels
|
||||
3. Upload relevante files (schema, README)
|
||||
4. Test met 3 verschillende vragen
|
||||
**Deel 1: Installation & Setup (30 min)**
|
||||
|
||||
**Deel 2: Custom GPT (45 min)**
|
||||
```bash
|
||||
npm install ai zod openai
|
||||
```
|
||||
|
||||
Maak een Custom GPT voor code review:
|
||||
1. Ga naar chat.openai.com/gpts
|
||||
2. Klik "Create"
|
||||
3. Schrijf instructions voor jouw stack
|
||||
4. Test met code uit je project
|
||||
Create `app/api/chat/route.ts`:
|
||||
- Setup Vercel AI SDK
|
||||
- Configure OpenAI model
|
||||
- Add system prompt
|
||||
|
||||
**Deel 3: Vergelijking (30 min)**
|
||||
**Deel 2: Chat Component (45 min)**
|
||||
|
||||
Test dezelfde taak met beide:
|
||||
- "Review deze component op best practices"
|
||||
- Noteer verschillen in output
|
||||
- Welke is beter voor welk doel?
|
||||
Build `app/page.tsx`:
|
||||
1. Use useChat hook
|
||||
2. Render messages list
|
||||
3. Input form for user messages
|
||||
4. Display streaming responses
|
||||
|
||||
**Deel 3: Tool Calling (30 min)**
|
||||
|
||||
Add 2 simple tools:
|
||||
- getTime: return current time
|
||||
- getRandomNumber: return random number
|
||||
|
||||
Update API route to handle tools with Zod schemas.
|
||||
|
||||
**Deel 4: Testing (15 min)**
|
||||
|
||||
Test chat locally with different prompts that trigger tools.
|
||||
|
||||
### Deliverable
|
||||
- Claude Project URL of screenshot
|
||||
- Custom GPT (als je ChatGPT Plus hebt) of instructies doc
|
||||
- Vergelijkingsnotities
|
||||
- Werkende chat interface with streaming
|
||||
- 2 integrated tools
|
||||
- GitHub commit with AI chat feature
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Optimaliseer Je AI Assistants
|
||||
### Integreer AI in Eindproject
|
||||
|
||||
**Deel 1: Iteratie (1 uur)**
|
||||
**Deel 1: Project-Specific Tools (1 uur)**
|
||||
|
||||
Verbeter je Claude Project:
|
||||
1. Test met 5 verschillende taken
|
||||
2. Noteer waar instructies tekortschieten
|
||||
3. Pas instructies aan
|
||||
4. Test opnieuw
|
||||
Add 2-3 tools relevant to your project:
|
||||
- Recipe Generator: tool to search recipes API
|
||||
- Budget App: tool to calculate expenses
|
||||
- Travel Planner: tool to search destinations
|
||||
|
||||
Documenteer in `docs/AI-ASSISTANTS.md`:
|
||||
- Originele instructies
|
||||
- Wat niet werkte
|
||||
- Verbeterde instructies
|
||||
- Resultaat verschil
|
||||
Define with Zod schemas and execute functions.
|
||||
|
||||
**Deel 2: Prompt Library (30 min)**
|
||||
**Deel 2: System Prompt Tuning (30 min)**
|
||||
|
||||
Maak een persoonlijke prompt library:
|
||||
```markdown
|
||||
# Mijn Prompt Templates
|
||||
Write a custom system prompt for your AI:
|
||||
- Define personality
|
||||
- Set constraints
|
||||
- Add context about your app
|
||||
|
||||
## Code Generation
|
||||
[template]
|
||||
**Deel 3: Integration (30 min)**
|
||||
|
||||
## Debugging
|
||||
[template]
|
||||
|
||||
## Code Review
|
||||
[template]
|
||||
|
||||
## Refactoring
|
||||
[template]
|
||||
```
|
||||
|
||||
**Deel 3: Reflectie (30 min)**
|
||||
|
||||
Schrijf reflectie (300 woorden):
|
||||
- Welke AI assistant gebruik je waarvoor?
|
||||
- Wat is het verschil tussen Claude Projects en Custom GPTs?
|
||||
- Hoe heeft dit je workflow verbeterd?
|
||||
Connect AI chat to your main app:
|
||||
- Add chat page/component
|
||||
- Integrate with Supabase auth (if needed)
|
||||
- Test end-to-end
|
||||
|
||||
### Deliverable
|
||||
- Geoptimaliseerde AI assistant instructies
|
||||
- Prompt library document
|
||||
- Reflectie (300 woorden)
|
||||
- AI feature integrated in project
|
||||
- Custom tools defined
|
||||
- docs/AI-DECISIONS.md updated with choices
|
||||
- GitHub commits with AI integration
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Advanced prompt engineering technieken toepassen (structured output, constraints, iterative refinement)
|
||||
- Een Custom GPT maken met specifieke instructies en knowledge
|
||||
- Een Claude Project opzetten met project context
|
||||
- De juiste tool kiezen (Custom GPT vs Claude Project) per use case
|
||||
- Een persoonlijke prompt library opbouwen en onderhouden
|
||||
- Vercel AI SDK installeren en configureren
|
||||
- useChat hook gebruiken voor chat UI
|
||||
- Streaming responses implementeren
|
||||
- Tool calling setup met Zod schemas
|
||||
- Externe APIs aanroepen via tools
|
||||
- System prompts schrijven voor AI behavior
|
||||
- Agent patterns verstaan (maxSteps)
|
||||
- AI features in een Next.js app integreren
|
||||
- Tool parameters valideren met Zod
|
||||
|
||||
@@ -1,444 +1,330 @@
|
||||
# Les 14: Project Setup & Repository Structure
|
||||
# Les 14: AI Chat Interface & Streaming
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 4: Advanced AI Features** (Les 13-18)
|
||||
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
||||
|
||||
## Beschrijving
|
||||
Leer professionele project setup en repository structuur. Begrijp hoe een goed georganiseerd project AI tools effectiever maakt en samenwerking vergemakkelijkt.
|
||||
Bouwen van professionele chat interfaces met streaming responses. Message rendering, markdown support, error handling, loading states, en UX patterns voor AI-powered features.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~45 min)
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de AI assistant reflecties uit Les 13 - welke instructies werkten goed en welke niet?
|
||||
|
||||
### Waarom Project Structuur Belangrijk Is
|
||||
|
||||
**Voor jezelf:**
|
||||
- Sneller code terugvinden
|
||||
- Makkelijker onderhouden
|
||||
- Minder bugs door consistentie
|
||||
|
||||
**Voor AI tools:**
|
||||
- Betere context understanding
|
||||
- Consistentere code generation
|
||||
- Cursor/Claude begrijpt je project beter
|
||||
|
||||
**Voor samenwerking:**
|
||||
- Anderen begrijpen je code sneller
|
||||
- Standaard conventies = minder discussie
|
||||
- Onboarding nieuwe developers eenvoudiger
|
||||
- Chat UI patterns en best practices
|
||||
- useChat hook deep dive (state, loading, error)
|
||||
- Streaming response handling en real-time updates
|
||||
- Message rendering strategies en optimizations
|
||||
- Markdown rendering in chat messages
|
||||
- Error handling en error boundaries
|
||||
- Loading states en skeleton loaders
|
||||
- User input validation and sanitization
|
||||
- Accessibility in chat interfaces (ARIA labels)
|
||||
- Message persistence (localStorage of database)
|
||||
- Performance optimization
|
||||
|
||||
---
|
||||
|
||||
### Next.js 14 Project Structuur
|
||||
### useChat Hook Deep Dive
|
||||
|
||||
**Aanbevolen structuur:**
|
||||
```
|
||||
project-root/
|
||||
├── src/
|
||||
│ ├── app/ # Next.js App Router
|
||||
│ │ ├── (auth)/ # Route group voor auth pagina's
|
||||
│ │ │ ├── login/
|
||||
│ │ │ └── register/
|
||||
│ │ ├── api/ # API routes
|
||||
│ │ │ └── chat/
|
||||
│ │ ├── dashboard/
|
||||
│ │ ├── layout.tsx
|
||||
│ │ ├── page.tsx
|
||||
│ │ └── globals.css
|
||||
│ │
|
||||
│ ├── components/ # React components
|
||||
│ │ ├── ui/ # Basis UI components
|
||||
│ │ │ ├── Button.tsx
|
||||
│ │ │ ├── Input.tsx
|
||||
│ │ │ └── Card.tsx
|
||||
│ │ ├── layout/ # Layout components
|
||||
│ │ │ ├── Header.tsx
|
||||
│ │ │ ├── Footer.tsx
|
||||
│ │ │ └── Sidebar.tsx
|
||||
│ │ └── features/ # Feature-specifieke components
|
||||
│ │ ├── auth/
|
||||
│ │ └── dashboard/
|
||||
│ │
|
||||
│ ├── lib/ # Utilities en configuraties
|
||||
│ │ ├── supabase.ts # Supabase client
|
||||
│ │ ├── utils.ts # Helper functies
|
||||
│ │ └── constants.ts # App constanten
|
||||
│ │
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ │ ├── useAuth.ts
|
||||
│ │ └── useTodos.ts
|
||||
│ │
|
||||
│ └── types/ # TypeScript types
|
||||
│ ├── database.ts
|
||||
│ └── api.ts
|
||||
│
|
||||
├── public/ # Static assets
|
||||
│ ├── images/
|
||||
│ └── favicon.ico
|
||||
│
|
||||
├── docs/ # Documentatie
|
||||
│ ├── PROMPT-LOG.md
|
||||
│ ├── AI-DECISIONS.md
|
||||
│ └── PROJECT-BRIEF.md
|
||||
│
|
||||
├── .cursorrules # Cursor AI configuratie
|
||||
├── .env.local # Environment variables (niet in git!)
|
||||
├── .env.example # Template voor env vars
|
||||
├── .gitignore
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── tailwind.config.ts
|
||||
└── README.md
|
||||
**State management met useChat:**
|
||||
|
||||
```typescript
|
||||
'use client'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
export function ChatComponent() {
|
||||
const {
|
||||
messages, // All messages in conversation
|
||||
input, // Current input text
|
||||
handleInputChange, // Update input
|
||||
handleSubmit, // Send message
|
||||
isLoading, // Is AI responding?
|
||||
error, // Any errors?
|
||||
} = useChat({
|
||||
api: '/api/chat',
|
||||
initialMessages: [], // Optional: pre-load messages
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id}>
|
||||
<strong>{msg.role}:</strong> {msg.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{isLoading && <div>AI is thinking...</div>}
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<button type="submit" disabled={isLoading}>Send</button>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Component Organisatie
|
||||
### Message Rendering Patterns
|
||||
|
||||
**UI Components (src/components/ui/):**
|
||||
- Herbruikbare, generieke components
|
||||
- Geen business logic
|
||||
- Props-driven
|
||||
- Voorbeelden: Button, Input, Modal, Card
|
||||
|
||||
**Layout Components (src/components/layout/):**
|
||||
- Structurele components
|
||||
- Meestal één per type
|
||||
- Voorbeelden: Header, Footer, Sidebar, Navigation
|
||||
|
||||
**Feature Components (src/components/features/):**
|
||||
- Business logic bevattend
|
||||
- Specifiek voor één feature
|
||||
- Groepeer per feature/domein
|
||||
|
||||
---
|
||||
|
||||
### File Naming Conventions
|
||||
|
||||
**Components:**
|
||||
```
|
||||
✅ Button.tsx # PascalCase
|
||||
✅ UserProfile.tsx
|
||||
❌ button.tsx
|
||||
❌ user-profile.tsx
|
||||
**Basic pattern:**
|
||||
```typescript
|
||||
<div className="space-y-4">
|
||||
{messages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={msg.role === 'user' ? 'ml-auto' : 'mr-auto'}
|
||||
>
|
||||
<div className="bg-gray-200 p-3 rounded">
|
||||
{msg.content}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Hooks:**
|
||||
```
|
||||
✅ useAuth.ts # camelCase met 'use' prefix
|
||||
✅ useTodos.ts
|
||||
❌ UseAuth.ts
|
||||
❌ auth-hook.ts
|
||||
**With markdown rendering:**
|
||||
```typescript
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
<div className="bg-gray-200 p-3 rounded">
|
||||
<ReactMarkdown>{msg.content}</ReactMarkdown>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Utilities:**
|
||||
```
|
||||
✅ formatDate.ts # camelCase
|
||||
✅ utils.ts
|
||||
✅ constants.ts
|
||||
```
|
||||
|
||||
**Types:**
|
||||
```
|
||||
✅ database.ts # camelCase
|
||||
✅ User.types.ts # optioneel: .types suffix
|
||||
**With message types:**
|
||||
```typescript
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id} className={msg.role === 'user' ? 'user-message' : 'ai-message'}>
|
||||
{msg.content}
|
||||
</div>
|
||||
))}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### .cursorrules Setup
|
||||
### Error Handling
|
||||
|
||||
**Maak .cursorrules in project root:**
|
||||
```markdown
|
||||
# Project: [Jouw Project Naam]
|
||||
**Structured error handling:**
|
||||
|
||||
## Tech Stack
|
||||
- Next.js 14 met App Router
|
||||
- TypeScript (strict mode)
|
||||
- Tailwind CSS
|
||||
- Supabase
|
||||
- React Query
|
||||
```typescript
|
||||
try {
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ messages }),
|
||||
})
|
||||
|
||||
## File Structure
|
||||
- Components in src/components/
|
||||
- UI components in src/components/ui/
|
||||
- API routes in src/app/api/
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`)
|
||||
}
|
||||
|
||||
## Code Conventions
|
||||
- Functional components only
|
||||
- Named exports (geen default exports)
|
||||
- Props interface boven component
|
||||
- Nederlandse comments
|
||||
// Handle streaming...
|
||||
} catch (error) {
|
||||
console.error('Chat error:', error)
|
||||
setError({
|
||||
message: 'Failed to send message',
|
||||
code: error instanceof Error ? error.message : 'unknown'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Naming
|
||||
- Components: PascalCase (Button.tsx)
|
||||
- Hooks: camelCase met use prefix (useAuth.ts)
|
||||
- Utils: camelCase (formatDate.ts)
|
||||
|
||||
## Styling
|
||||
- Tailwind CSS classes
|
||||
- Geen inline styles
|
||||
- Responsive mobile-first
|
||||
|
||||
## TypeScript
|
||||
- Strict mode
|
||||
- Geen any types
|
||||
- Interfaces voor props
|
||||
- Types voor data
|
||||
|
||||
## Don'ts
|
||||
- Geen console.log in productie
|
||||
- Geen hardcoded strings
|
||||
- Geen unused imports
|
||||
**Error boundary:**
|
||||
```typescript
|
||||
<ErrorBoundary fallback={<div>Chat error occurred</div>}>
|
||||
<ChatComponent />
|
||||
</ErrorBoundary>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Git Best Practices
|
||||
### Loading States
|
||||
|
||||
**Commit Message Format:**
|
||||
```
|
||||
type: korte beschrijving
|
||||
**Skeleton loader:**
|
||||
```typescript
|
||||
function MessageSkeleton() {
|
||||
return (
|
||||
<div className="animate-pulse">
|
||||
<div className="bg-gray-300 h-4 rounded w-48 mb-2" />
|
||||
<div className="bg-gray-300 h-4 rounded w-64" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Types:
|
||||
- feat: nieuwe feature
|
||||
- fix: bug fix
|
||||
- refactor: code verbetering
|
||||
- docs: documentatie
|
||||
- style: formatting
|
||||
- test: tests toevoegen
|
||||
```
|
||||
|
||||
**Voorbeelden:**
|
||||
```bash
|
||||
git commit -m "feat: add user authentication with Supabase"
|
||||
git commit -m "fix: resolve hydration error in TodoList"
|
||||
git commit -m "docs: update README with setup instructions"
|
||||
```
|
||||
|
||||
**.gitignore essentials:**
|
||||
```
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Environment
|
||||
.env*.local
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
{isLoading && <MessageSkeleton />}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Environment Variables
|
||||
### Input Validation
|
||||
|
||||
**Structuur:**
|
||||
```bash
|
||||
# .env.local (NOOIT committen!)
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
|
||||
OPENAI_API_KEY=sk-...
|
||||
**Validate before sending:**
|
||||
|
||||
# .env.example (WEL committen)
|
||||
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
OPENAI_API_KEY=your-openai-key
|
||||
```typescript
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
|
||||
// Trim whitespace
|
||||
const trimmedInput = input.trim()
|
||||
|
||||
// Validate non-empty
|
||||
if (!trimmedInput) {
|
||||
setError('Message cannot be empty')
|
||||
return
|
||||
}
|
||||
|
||||
// Validate length
|
||||
if (trimmedInput.length > 1000) {
|
||||
setError('Message too long (max 1000 chars)')
|
||||
return
|
||||
}
|
||||
|
||||
// Send message
|
||||
handleSubmit(e)
|
||||
}
|
||||
```
|
||||
|
||||
**Regels:**
|
||||
- `NEXT_PUBLIC_` prefix = zichtbaar in browser
|
||||
- Zonder prefix = alleen server-side
|
||||
- Nooit secrets in `NEXT_PUBLIC_` vars
|
||||
|
||||
---
|
||||
|
||||
### README.md Template
|
||||
### Message Persistence
|
||||
|
||||
```markdown
|
||||
# Project Naam
|
||||
**Save to localStorage:**
|
||||
|
||||
Korte beschrijving van je project.
|
||||
```typescript
|
||||
const [messages, setMessages] = useState(() => {
|
||||
const saved = localStorage.getItem('chat_history')
|
||||
return saved ? JSON.parse(saved) : []
|
||||
})
|
||||
|
||||
## Features
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
// Save whenever messages change
|
||||
useEffect(() => {
|
||||
localStorage.setItem('chat_history', JSON.stringify(messages))
|
||||
}, [messages])
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
- Next.js 14
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Supabase
|
||||
- Vercel AI SDK
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 18+
|
||||
- npm of yarn
|
||||
- Supabase account
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone de repository
|
||||
```bash
|
||||
git clone https://github.com/username/project.git
|
||||
cd project
|
||||
```
|
||||
|
||||
2. Installeer dependencies
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Maak .env.local (zie .env.example)
|
||||
|
||||
4. Start development server
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
Zie `.env.example` voor benodigde variabelen.
|
||||
|
||||
## Deployment
|
||||
Deployed op Vercel: [productie-url]
|
||||
|
||||
## Documentatie
|
||||
- [PROMPT-LOG.md](docs/PROMPT-LOG.md)
|
||||
- [AI-DECISIONS.md](docs/AI-DECISIONS.md)
|
||||
**Save to database:**
|
||||
```typescript
|
||||
const saveMessage = async (message: Message) => {
|
||||
await fetch('/api/messages', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
content: message.content,
|
||||
role: message.role,
|
||||
userId: user.id,
|
||||
}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Vercel AI SDK
|
||||
- React Markdown
|
||||
- Cursor
|
||||
- Git
|
||||
- GitHub
|
||||
- TypeScript
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (2 uur, klassikaal)
|
||||
|
||||
### Setup Je Eindproject
|
||||
### Build Professional Chat Interface
|
||||
|
||||
**Deel 1: Project Structuur (45 min)**
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de Vercel AI SDK ervaringen uit Les 13 - welke tool calling patterns werkten goed?
|
||||
|
||||
1. Maak nieuw Next.js project:
|
||||
```bash
|
||||
npx create-next-app@latest mijn-eindproject --typescript --tailwind --app
|
||||
```
|
||||
**Deel 1: Chat Component (45 min)**
|
||||
|
||||
2. Maak de mappenstructuur:
|
||||
- src/components/ui/
|
||||
- src/components/layout/
|
||||
- src/components/features/
|
||||
- src/lib/
|
||||
- src/hooks/
|
||||
- src/types/
|
||||
- docs/
|
||||
Build components/ChatInterface.tsx:
|
||||
1. Use useChat hook
|
||||
2. Render messages with proper styling
|
||||
3. User vs AI message styling
|
||||
4. Input form with validation
|
||||
5. Tailwind + shadcn/ui components
|
||||
|
||||
3. Maak placeholder files:
|
||||
- src/lib/supabase.ts
|
||||
- src/lib/utils.ts
|
||||
**Deel 2: Markdown & Error Handling (30 min)**
|
||||
|
||||
**Deel 2: Configuratie (30 min)**
|
||||
1. Install react-markdown: `npm install react-markdown`
|
||||
2. Render AI responses with markdown
|
||||
3. Add error boundary
|
||||
4. Show error messages to user
|
||||
5. Proper loading states
|
||||
|
||||
1. Maak .cursorrules met jouw conventies
|
||||
2. Maak .env.example
|
||||
3. Update .gitignore
|
||||
4. Maak README.md met template
|
||||
**Deel 3: UX Improvements (30 min)**
|
||||
|
||||
**Deel 3: Git Setup (25 min)**
|
||||
1. Auto-scroll to latest message
|
||||
2. Disable input while loading
|
||||
3. Show message count/token usage
|
||||
4. Add clear chat history button
|
||||
5. Save messages to localStorage
|
||||
|
||||
1. git init
|
||||
2. Initial commit met goede message
|
||||
3. Push naar GitHub
|
||||
4. Check: .env.local NIET gecommit?
|
||||
**Deel 4: Testing (15 min)**
|
||||
|
||||
**Deel 4: Documentatie Start (20 min)**
|
||||
|
||||
Maak in docs/:
|
||||
- PROJECT-BRIEF.md (beschrijving eindproject)
|
||||
- PROMPT-LOG.md (leeg template)
|
||||
- AI-DECISIONS.md (leeg template)
|
||||
Test chat interface locally with various inputs and error scenarios.
|
||||
|
||||
### Deliverable
|
||||
- GitHub repository met correcte structuur
|
||||
- .cursorrules file
|
||||
- README.md
|
||||
- docs/ folder met templates
|
||||
- Werkende chat interface component
|
||||
- Markdown rendering working
|
||||
- Error handling implemented
|
||||
- LocalStorage persistence
|
||||
- GitHub commit with chat UI
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Bouw Project Foundation
|
||||
### Integrate Chat into Your Project
|
||||
|
||||
**Deel 1: Base Components (1 uur)**
|
||||
**Deel 1: Project Integration (1 uur)**
|
||||
|
||||
Maak basis UI components met AI hulp:
|
||||
- src/components/ui/Button.tsx
|
||||
- src/components/ui/Input.tsx
|
||||
- src/components/ui/Card.tsx
|
||||
- src/components/layout/Header.tsx
|
||||
- src/components/layout/Footer.tsx
|
||||
1. Add chat component to your app
|
||||
2. Connect to your API route with tools
|
||||
3. Style to match your design
|
||||
4. Test with actual tools/integrations
|
||||
5. Fix any bugs
|
||||
|
||||
Requirements:
|
||||
- TypeScript interfaces voor props
|
||||
- Tailwind styling
|
||||
- Responsive design
|
||||
- Volg je .cursorrules
|
||||
**Deel 2: Enhanced Features (30 min)**
|
||||
|
||||
**Deel 2: Supabase Setup (30 min)**
|
||||
Add one of these:
|
||||
- Message copy button
|
||||
- Regenerate response option
|
||||
- Clear history confirmation
|
||||
- Export chat history
|
||||
- Message timestamps
|
||||
|
||||
1. Maak Supabase project (of hergebruik van Les 9)
|
||||
2. Configureer src/lib/supabase.ts
|
||||
3. Voeg env vars toe aan .env.local
|
||||
4. Test connectie
|
||||
**Deel 3: Performance & Polish (30 min)**
|
||||
|
||||
**Deel 3: Eerste Feature (30 min)**
|
||||
|
||||
Kies je eindproject en implementeer 1 basisfeature:
|
||||
- Recipe Generator: ingredient input form
|
||||
- Budget Buddy: expense entry form
|
||||
- Travel Planner: destination search
|
||||
|
||||
Commit en push!
|
||||
1. Optimize re-renders (useMemo, useCallback)
|
||||
2. Virtual scrolling for long chats
|
||||
3. Better accessibility (keyboard nav)
|
||||
4. Mobile responsive tweaks
|
||||
5. Update docs/AI-DECISIONS.md
|
||||
|
||||
### Deliverable
|
||||
- Werkende UI components
|
||||
- Supabase connectie
|
||||
- 1 basic feature
|
||||
- Alle commits met goede messages
|
||||
|
||||
---
|
||||
|
||||
## 💡 Eindopdracht
|
||||
|
||||
Dit is een goed moment om te starten met **deelopdracht 1** van je eindopdracht. De setup die je vandaag maakt kun je direct gebruiken voor je eindproject. Bespreek je projectidee met de docent als je feedback wilt.
|
||||
- Chat fully integrated in project
|
||||
- Enhanced features implemented
|
||||
- Performance optimized
|
||||
- GitHub commits with improvements
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Een professionele project structuur opzetten
|
||||
- File naming conventions toepassen
|
||||
- Een effectieve .cursorrules file schrijven
|
||||
- Git best practices volgen
|
||||
- Environment variables correct beheren
|
||||
- Een README.md schrijven
|
||||
- Project documentatie structureren
|
||||
- useChat hook volledig begrijpen en gebruiken
|
||||
- Professionele chat UI patterns implementeren
|
||||
- Markdown rendering in chat messages
|
||||
- Error handling en error boundaries toepassen
|
||||
- Loading states en skeletons bouwen
|
||||
- User input valideren en sanitizen
|
||||
- Message persistence (localStorage/DB)
|
||||
- Accessibility in chat interfaces verbeteren
|
||||
- Performance optimizations toepassen
|
||||
- Complete chat feature in Next.js app integreren
|
||||
|
||||
@@ -1,336 +1,130 @@
|
||||
# Les 15: MCP & Context Management
|
||||
# Les 15: Eindproject Werkdag 1
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 4: Advanced AI Features** (Les 13-18)
|
||||
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
||||
|
||||
## Beschrijving
|
||||
Leer werken met Model Context Protocol (MCP) en geavanceerd context management. Begrijp hoe je AI tools maximale context geeft voor betere resultaten.
|
||||
Eerste intensieve werkdag aan je eindproject. Focus op core features bouwen, Q&A sessions, individuele hulp, en probleemoplossing. Docent beschikbaar voor guidance en klassikale sparring.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Te Behandelen (~45 min guidance + 120 min work)
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de project setup ervaringen uit Les 14 - welke structuur conventies werken goed en welke niet?
|
||||
|
||||
### Wat is Context?
|
||||
|
||||
**Context = wat de AI weet over jouw situatie**
|
||||
|
||||
**Soorten context:**
|
||||
- **Code context:** welke files, welke functies
|
||||
- **Project context:** tech stack, conventies
|
||||
- **Taak context:** wat je probeert te bereiken
|
||||
- **Historische context:** eerdere conversatie
|
||||
|
||||
**Meer context = betere antwoorden**
|
||||
- Q&A session: wat loop je tegen aan?
|
||||
- Cursor tips & tricks review
|
||||
- Debugging strategies en common patterns
|
||||
- Performance optimization basics
|
||||
- Deployment troubleshooting preview
|
||||
- Hands-on assistance beschikbaar
|
||||
- Code review feedback
|
||||
- Feature planning discussion
|
||||
|
||||
---
|
||||
|
||||
### Model Context Protocol (MCP)
|
||||
## Doel van deze Werkdag
|
||||
|
||||
**Wat is MCP?**
|
||||
- Open protocol van Anthropic
|
||||
- Standaard manier om AI models context te geven
|
||||
- Verbindt AI met externe tools en data
|
||||
|
||||
**Waarom MCP?**
|
||||
- AI kan nu "tools gebruiken"
|
||||
- Toegang tot je filesystem
|
||||
- Database queries uitvoeren
|
||||
- Web searches doen
|
||||
- En meer...
|
||||
|
||||
**Hoe werkt het:**
|
||||
```
|
||||
Jij → vraag → AI → MCP → Tool → resultaat → AI → antwoord → Jij
|
||||
```
|
||||
- Core features van je eindproject afmaken
|
||||
- Architecture validatie (is je setup goed?)
|
||||
- Database integratie testen
|
||||
- Auth flows uitwerken
|
||||
- First AI feature implementeren
|
||||
- Testing en bug fixes
|
||||
|
||||
---
|
||||
|
||||
### MCP in de Praktijk
|
||||
### Checklist voor deze Sessie
|
||||
|
||||
**Cursor gebruikt MCP onder de hood:**
|
||||
- `@file` mentions = file context via MCP
|
||||
- `@codebase` = codebase search via MCP
|
||||
- `@Docs` = documentation lookup via MCP
|
||||
- `@Web` = web search via MCP
|
||||
**Code Quality:**
|
||||
- [ ] TypeScript geen errors
|
||||
- [ ] No console.logs in production code
|
||||
- [ ] Proper error handling
|
||||
- [ ] Component props typed
|
||||
|
||||
**Claude Desktop met MCP:**
|
||||
- Kan tools uitvoeren
|
||||
- Filesystem access
|
||||
- Terminal commands
|
||||
- Database queries
|
||||
**Features:**
|
||||
- [ ] Core feature 1 werkend
|
||||
- [ ] Core feature 2 werkend
|
||||
- [ ] Data persistence (Supabase)
|
||||
- [ ] Loading states visible
|
||||
- [ ] Error states handled
|
||||
|
||||
---
|
||||
|
||||
### Context Management Strategieën
|
||||
|
||||
**1. Expliciete Context**
|
||||
Geef context direct in je prompt:
|
||||
```
|
||||
Context: Next.js 14 project met TypeScript en Supabase.
|
||||
Dit is een e-commerce app voor verkoop van boeken.
|
||||
|
||||
Vraag: Hoe implementeer ik een winkelwagen?
|
||||
```
|
||||
|
||||
**2. File Context**
|
||||
Gebruik @ mentions om relevante files toe te voegen:
|
||||
```
|
||||
@src/types/product.ts
|
||||
@src/lib/supabase.ts
|
||||
Hoe maak ik een addToCart functie?
|
||||
```
|
||||
|
||||
**3. Project Context**
|
||||
Gebruik .cursorrules of Claude Project instructions:
|
||||
```
|
||||
# In .cursorrules
|
||||
Dit project is een e-commerce platform.
|
||||
Alle prices zijn in cents (niet euros).
|
||||
```
|
||||
|
||||
**4. Fresh Context**
|
||||
Begin nieuwe chat voor nieuw onderwerp:
|
||||
- Voorkomt context vervuiling
|
||||
- AI raakt niet in de war met oude info
|
||||
|
||||
---
|
||||
|
||||
### @ Mentions Strategisch Gebruiken
|
||||
|
||||
**Cursor @ mentions:**
|
||||
|
||||
| Mention | Wanneer | Voorbeeld |
|
||||
|---------|---------|-----------|
|
||||
| `@file.tsx` | Specifieke file context | `@Button.tsx hoe voeg ik loading toe?` |
|
||||
| `@folder/` | Hele folder context | `@components/ welke patterns gebruik ik?` |
|
||||
| `@codebase` | Zoeken in project | `@codebase waar handle ik auth?` |
|
||||
| `@Docs` | Officiële documentatie | `@Docs Next.js App Router` |
|
||||
| `@Web` | Live web search | `@Web Supabase RLS policies` |
|
||||
|
||||
**Best practice:** Combineer mentions voor rijke context:
|
||||
```
|
||||
@src/components/auth/LoginForm.tsx
|
||||
@src/lib/supabase.ts
|
||||
@Docs Supabase Auth
|
||||
|
||||
Hoe voeg ik Google OAuth toe aan mijn login?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Claude Projects voor Persistente Context
|
||||
|
||||
**Wat kun je toevoegen aan een Project:**
|
||||
- Instructies (system prompt)
|
||||
- Files (code, docs, schema's)
|
||||
- Kennis die over sessies heen blijft
|
||||
|
||||
**Project Instructions Template:**
|
||||
```markdown
|
||||
# Project: [Naam]
|
||||
|
||||
## Tech Stack
|
||||
- Next.js 14 met App Router
|
||||
- TypeScript strict
|
||||
- Tailwind CSS
|
||||
- Supabase
|
||||
|
||||
## Code Conventies
|
||||
[jouw conventies]
|
||||
|
||||
## Database Schema
|
||||
[beschrijf je schema]
|
||||
|
||||
## Current Focus
|
||||
[waar werk je nu aan]
|
||||
```
|
||||
|
||||
**Tip:** Upload je database schema, README, en belangrijke types files.
|
||||
|
||||
---
|
||||
|
||||
### Context Windows en Limieten
|
||||
|
||||
**Wat is een context window?**
|
||||
- Maximum hoeveelheid tekst die AI kan "zien"
|
||||
- Gemeten in tokens (±4 karakters per token)
|
||||
|
||||
**Limieten per model:**
|
||||
| Model | Context Window |
|
||||
|-------|---------------|
|
||||
| GPT-5 | 128K tokens |
|
||||
| Claude 3 Sonnet | 200K tokens |
|
||||
| Claude 3 Opus | 200K tokens |
|
||||
|
||||
**Praktisch:**
|
||||
- Lange conversaties → context vol
|
||||
- Veel file mentions → context vol
|
||||
- Start fresh chat wanneer nodig
|
||||
|
||||
**Signs van vol context:**
|
||||
- AI vergeet eerdere instructies
|
||||
- Antwoorden worden minder relevant
|
||||
- AI herhaalt zichzelf
|
||||
|
||||
---
|
||||
|
||||
### Context Hygiene
|
||||
|
||||
**Do's:**
|
||||
- Geef alleen relevante context
|
||||
- Wees specifiek in file mentions
|
||||
- Start nieuwe chat voor nieuw onderwerp
|
||||
- Gebruik project-level context voor consistentie
|
||||
|
||||
**Don'ts:**
|
||||
- Hele codebase als context geven
|
||||
- Oude irrelevante chats voortzetten
|
||||
- Te veel files tegelijk mentionen
|
||||
- Context herhalen die AI al heeft
|
||||
|
||||
---
|
||||
|
||||
### Debugging met Context
|
||||
|
||||
**Wanneer AI verkeerde antwoorden geeft:**
|
||||
|
||||
1. **Check context:** Heeft AI de juiste files?
|
||||
2. **Check instructies:** Zijn project rules geladen?
|
||||
3. **Fresh start:** Begin nieuwe chat
|
||||
4. **Explicieter:** Voeg meer context toe in prompt
|
||||
|
||||
**Debug prompt:**
|
||||
```
|
||||
Ik merk dat je antwoord niet klopt.
|
||||
Heb je toegang tot @src/lib/auth.ts?
|
||||
Dit is de huidige implementatie: [plak code]
|
||||
Kun je opnieuw kijken?
|
||||
```
|
||||
**Setup:**
|
||||
- [ ] Project structure clean
|
||||
- [ ] .cursorrules up-to-date
|
||||
- [ ] Environment variables configured
|
||||
- [ ] Git history clean
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Cursor (@ mentions)
|
||||
- Claude Projects
|
||||
- ChatGPT
|
||||
- Cursor
|
||||
- Chrome DevTools
|
||||
- Supabase Dashboard
|
||||
- GitHub
|
||||
- Vercel (optional preview)
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (4 uur, hands-on werk)
|
||||
|
||||
### Context Management Oefenen
|
||||
### Intensive Development Session
|
||||
|
||||
**Deel 1: @ Mentions Mastery (45 min)**
|
||||
**Hou je voortgang bij:**
|
||||
|
||||
Voer deze taken uit in Cursor:
|
||||
**Eerste uur (0-60 min):**
|
||||
- [ ] Maak prioriteiten list van features
|
||||
- [ ] Fix top 3 bugs van vorige sessies
|
||||
- [ ] Test alles lokaal
|
||||
- [ ] Ask docent for guidance if stuck
|
||||
|
||||
1. **Single file context:**
|
||||
- Open je TodoList component
|
||||
- Vraag: "@TodoList.tsx Hoe kan ik infinite scroll toevoegen?"
|
||||
- Noteer kwaliteit antwoord
|
||||
**Twee uur (60-120 min):**
|
||||
- [ ] Implementeer core feature 1 (CRUD)
|
||||
- [ ] Implementeer core feature 2
|
||||
- [ ] Add loading/error states
|
||||
- [ ] Styling pass 1
|
||||
|
||||
2. **Multi-file context:**
|
||||
- Vraag: "@src/components/ @src/types/ Welke types mis ik?"
|
||||
- Noteer hoe context helpt
|
||||
**Drie uur (120-180 min):**
|
||||
- [ ] Implementeer AI feature (tool calling)
|
||||
- [ ] Test end-to-end flows
|
||||
- [ ] Fix bugs found
|
||||
- [ ] Performance check
|
||||
|
||||
3. **Docs context:**
|
||||
- Vraag: "@Docs Supabase realtime Hoe voeg ik real-time updates toe aan mijn todo app?"
|
||||
- Noteer of antwoord up-to-date is
|
||||
|
||||
4. **Codebase search:**
|
||||
- Vraag: "@codebase Waar handle ik error states?"
|
||||
- Noteer of het de juiste plekken vindt
|
||||
|
||||
**Deel 2: Claude Project Setup (30 min)**
|
||||
|
||||
1. Maak Claude Project voor je eindproject
|
||||
2. Schrijf comprehensive instructions
|
||||
3. Upload 3-5 belangrijke files:
|
||||
- Database schema/types
|
||||
- Main component
|
||||
- Supabase client
|
||||
4. Test met 3 vragen
|
||||
|
||||
**Deel 3: Context Vergelijking (45 min)**
|
||||
|
||||
Voer dezelfde taak uit met:
|
||||
1. Geen context (nieuwe chat, geen mentions)
|
||||
2. File context (@mentions)
|
||||
3. Project context (Claude Project)
|
||||
|
||||
Taak: "Implementeer een search feature voor todos"
|
||||
|
||||
Noteer:
|
||||
- Kwaliteit code
|
||||
- Relevantie voor jouw project
|
||||
- Hoeveel aanpassing nodig
|
||||
**Vier uur (180-240 min):**
|
||||
- [ ] Polish UI
|
||||
- [ ] Code cleanup
|
||||
- [ ] Document decisions
|
||||
- [ ] Commit and push
|
||||
|
||||
### Deliverable
|
||||
- Notities over @ mentions effectiviteit
|
||||
- Claude Project met instructions
|
||||
- Context vergelijkingsnotities
|
||||
- Werkende core features
|
||||
- Updated PROMPT-LOG.md (add entries)
|
||||
- Updated AI-DECISIONS.md
|
||||
- GitHub commits with progress
|
||||
- Screenshot of working features
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
## Huiswerk
|
||||
|
||||
### Optimaliseer Je Context Strategie
|
||||
Continue building your project for next session.
|
||||
|
||||
**Deel 1: .cursorrules Perfectioneren (30 min)**
|
||||
|
||||
Update je .cursorrules met:
|
||||
- Specifieke file structure info
|
||||
- Naming conventions met voorbeelden
|
||||
- Common patterns in je project
|
||||
- DON'Ts specifiek voor jouw situatie
|
||||
|
||||
**Deel 2: Claude Project Uitbreiden (45 min)**
|
||||
|
||||
1. Upload alle relevante project files
|
||||
2. Test met complexe vragen:
|
||||
- "Hoe implementeer ik feature X?"
|
||||
- "Review mijn auth implementatie"
|
||||
- "Wat ontbreekt er nog?"
|
||||
3. Itereer op instructions
|
||||
|
||||
**Deel 3: Context Documentation (45 min)**
|
||||
|
||||
Maak `docs/CONTEXT-GUIDE.md`:
|
||||
```markdown
|
||||
# Context Guide voor [Project]
|
||||
|
||||
## Cursor @ Mentions
|
||||
- Voor UI changes: @src/components/
|
||||
- Voor data logic: @src/lib/ @src/hooks/
|
||||
- Voor types: @src/types/
|
||||
|
||||
## Claude Project
|
||||
- Project URL: [link]
|
||||
- Uploaded files: [lijst]
|
||||
- Best practices: [jouw learnings]
|
||||
|
||||
## Common Prompts
|
||||
[verzameling werkende prompts]
|
||||
```
|
||||
|
||||
### Deliverable
|
||||
- Geoptimaliseerde .cursorrules
|
||||
- Complete Claude Project
|
||||
- docs/CONTEXT-GUIDE.md
|
||||
**Checklist:**
|
||||
- [ ] Core features complete
|
||||
- [ ] All changes pushed to GitHub
|
||||
- [ ] PROMPT-LOG.md updated with prompts used
|
||||
- [ ] AI-DECISIONS.md updated with choices made
|
||||
- [ ] Ready for next workday session
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
|
||||
Na deze les kan de student:
|
||||
- Uitleggen wat context is en waarom het belangrijk is
|
||||
- Model Context Protocol (MCP) begrijpen
|
||||
- @ mentions strategisch gebruiken in Cursor
|
||||
- Een Claude Project opzetten met effectieve instructions
|
||||
- Context windows en limieten begrijpen
|
||||
- Context management best practices toepassen
|
||||
- Debuggen wanneer AI verkeerde antwoorden geeft door context issues
|
||||
- Zelfstandig problemen identificeren en fixen
|
||||
- Cursor effectief gebruiken voor rapid development
|
||||
- Architectuur decisions valideren en aanpassen
|
||||
- Debugging strategieën toepassen
|
||||
- Features end-to-end implementeren
|
||||
- Development workflows versnellen
|
||||
- Documentatie up-to-date houden
|
||||
|
||||
@@ -1,346 +1,122 @@
|
||||
# Les 16: Mastering Cursor
|
||||
# Les 16: Eindproject Werkdag 2
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 4: Advanced AI Features** (Les 13-18)
|
||||
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
||||
|
||||
## Beschrijving
|
||||
Verdieping in Cursor's geavanceerde features. Leer model keuze, Composer Mode, @ mentions, en .cursorrules optimaal gebruiken.
|
||||
Verlengde werksessie voor je eindproject. Continue Development Day waar je intensief aan je AI-powered applicatie werkt met begeleiding en feedback van de docent.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de context management ervaringen uit Les 15 - welke strategieën werkten het beste?
|
||||
Bespreek klassikaal de deployment ervaringen uit Les 15 - welke problemen kwamen jullie tegen en hoe losten jullie die op?
|
||||
|
||||
### Model Keuze
|
||||
### Doel van deze werkdag
|
||||
|
||||
**Wanneer welk model?**
|
||||
|
||||
| Model | Gebruik voor | Kosten |
|
||||
|-------|-------------|--------|
|
||||
| **Haiku** | Simpele taken, autocomplete | Goedkoop |
|
||||
| **Sonnet** | Dagelijks werk, de meeste taken | Medium |
|
||||
| **Opus** | Complexe architectuur, multi-file | Duur |
|
||||
|
||||
**Vuistregels:**
|
||||
- Tab completion: Haiku (automatisch)
|
||||
- CMD+K: Sonnet (default)
|
||||
- Composer: Sonnet of Opus
|
||||
- Complexe debugging: Opus
|
||||
Deze les is puur praktisch werken:
|
||||
- Bugs fixen die je bent tegengekomen
|
||||
- Features afmaken die nog niet klaar zijn
|
||||
- Code cleanup en optimalisatie
|
||||
- Performance verbeteren
|
||||
- Documentatie aanpassen
|
||||
- Testen in productie
|
||||
|
||||
---
|
||||
|
||||
### Composer Mode Diepgaand
|
||||
### Checklist voor deze sessie
|
||||
|
||||
**Wat is Composer?**
|
||||
Multi-file generatie in één keer. Cursor plant en voert wijzigingen uit over meerdere bestanden.
|
||||
**Code Quality:**
|
||||
- [ ] TypeScript geen errors
|
||||
- [ ] ESLint check passed
|
||||
- [ ] Geen unused imports/variables
|
||||
- [ ] Proper error handling overal
|
||||
|
||||
**Wanneer Composer gebruiken:**
|
||||
- Nieuwe feature met meerdere components
|
||||
- Refactoring over meerdere files
|
||||
- Boilerplate generatie
|
||||
- Complexe wijzigingen
|
||||
**Features:**
|
||||
- [ ] Alle core features werken
|
||||
- [ ] AI feature functionaliteit compleet
|
||||
- [ ] Edge cases afgehandeld
|
||||
- [ ] Loading states geimplementeerd
|
||||
- [ ] Error states geimplementeerd
|
||||
|
||||
**Composer Workflow:**
|
||||
1. CMD+I opent Composer
|
||||
2. Beschrijf je doel duidelijk
|
||||
3. Voeg context toe met @ mentions
|
||||
4. Laat Cursor plannen
|
||||
5. Review het plan
|
||||
6. Accept of reject per file
|
||||
7. Itereer met feedback
|
||||
**Performance:**
|
||||
- [ ] Lighthouse score > 80
|
||||
- [ ] Images optimized
|
||||
- [ ] Lazy loading waar nodig
|
||||
- [ ] Bundle size reasonable
|
||||
|
||||
**Voorbeeld prompt:**
|
||||
```
|
||||
Create a user profile page with:
|
||||
- @components/ui/ style components
|
||||
- Profile header with avatar
|
||||
- Edit form with validation
|
||||
- Save to @lib/supabase.ts
|
||||
- Loading and error states
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### @ Mentions Systeem
|
||||
|
||||
**Alle types:**
|
||||
|
||||
| Mention | Wat het doet | Voorbeeld |
|
||||
|---------|--------------|-----------|
|
||||
| `@file.tsx` | Specifieke file | `@Button.tsx` |
|
||||
| `@folder/` | Hele folder | `@components/` |
|
||||
| `@codebase` | Zoek in codebase | `@codebase auth logic` |
|
||||
| `@Docs` | Officiële docs | `@Docs Next.js routing` |
|
||||
| `@Web` | Web zoeken | `@Web Supabase auth setup` |
|
||||
|
||||
**Best practices:**
|
||||
- Wees specifiek met file mentions
|
||||
- Gebruik folder mentions voor context
|
||||
- @Docs voor up-to-date informatie
|
||||
- Combineer mentions voor betere context
|
||||
|
||||
---
|
||||
|
||||
### .cursorrules Advanced
|
||||
|
||||
**Meerdere rules files:**
|
||||
|
||||
```
|
||||
.cursor/
|
||||
└── rules/
|
||||
├── general.mdc # Project-brede regels
|
||||
├── components.mdc # Component conventies
|
||||
├── api.mdc # API route regels
|
||||
└── testing.mdc # Test conventies
|
||||
```
|
||||
|
||||
**Effectieve rules schrijven:**
|
||||
|
||||
```markdown
|
||||
# Component Rules
|
||||
|
||||
## Structure
|
||||
Alle components moeten volgen:
|
||||
1. Props interface bovenaan
|
||||
2. Component function
|
||||
3. Named export onderaan
|
||||
|
||||
## Example
|
||||
\`\`\`tsx
|
||||
interface ButtonProps {
|
||||
label: string
|
||||
onClick: () => void
|
||||
variant?: 'primary' | 'secondary'
|
||||
}
|
||||
|
||||
export function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
|
||||
return (
|
||||
<button onClick={onClick} className={...}>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## DON'Ts
|
||||
- Geen default exports
|
||||
- Geen inline styles
|
||||
- Geen any types
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Codebase Indexing
|
||||
|
||||
**Hoe Cursor indexeert:**
|
||||
- Scant alle files in je project
|
||||
- Bouwt semantic understanding
|
||||
- Gebruikt voor autocomplete en context
|
||||
|
||||
**Optimaliseren:**
|
||||
1. Goede `.cursorignore` (node_modules, .next, etc.)
|
||||
2. Semantische naming
|
||||
3. Duidelijke file structuur
|
||||
4. Comments waar nodig
|
||||
|
||||
**Re-indexeren:**
|
||||
CMD+Shift+P → "Reindex"
|
||||
|
||||
---
|
||||
|
||||
### Cost Management
|
||||
|
||||
**Token gebruik monitoren:**
|
||||
- Cursor toont token count in chat
|
||||
- Check monthly usage in settings
|
||||
|
||||
**Bespaartips:**
|
||||
1. Gebruik Haiku voor simpele taken
|
||||
2. Beperk context (niet hele codebase)
|
||||
3. Wees specifiek in prompts
|
||||
4. Fresh chat voor nieuwe onderwerpen
|
||||
|
||||
**Free tier strategie:**
|
||||
- Focus op Tab completion (onbeperkt)
|
||||
- Gebruik CMD+K spaarzaam
|
||||
- Composer alleen voor grote taken
|
||||
|
||||
---
|
||||
|
||||
### Debugging met Cursor
|
||||
|
||||
**AI-Assisted Debugging:**
|
||||
|
||||
**Stap 1: Error identificeren**
|
||||
```
|
||||
@file-met-error.tsx
|
||||
Ik krijg deze error: [plak error]
|
||||
Wat gaat er mis?
|
||||
```
|
||||
|
||||
**Stap 2: Context toevoegen**
|
||||
```
|
||||
@file-met-error.tsx
|
||||
@gerelateerde-file.ts
|
||||
De error treedt op wanneer ik X doe.
|
||||
Console log toont: [data]
|
||||
```
|
||||
|
||||
**Stap 3: Fix implementeren**
|
||||
- Selecteer code met error
|
||||
- CMD+K → "Fix this error: [beschrijving]"
|
||||
- Review en test
|
||||
|
||||
---
|
||||
|
||||
### Refactoring met Cursor
|
||||
|
||||
**Pattern 1: Extract Component**
|
||||
```
|
||||
Selecteer JSX block → CMD+K
|
||||
"Extract this into a separate component called ProductCard"
|
||||
```
|
||||
|
||||
**Pattern 2: Extract Hook**
|
||||
```
|
||||
Selecteer state + useEffect → CMD+K
|
||||
"Extract this into a custom hook called useProductData"
|
||||
```
|
||||
|
||||
**Pattern 3: Improve Performance**
|
||||
```
|
||||
@Component.tsx
|
||||
"Optimize this component:
|
||||
- Add memoization waar nodig
|
||||
- Fix unnecessary re-renders
|
||||
- Improve loading performance"
|
||||
```
|
||||
**Deployment:**
|
||||
- [ ] Builds succesvol lokaal
|
||||
- [ ] Alle env vars in Vercel
|
||||
- [ ] Works in production
|
||||
- [ ] Supabase redirects configured
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Cursor
|
||||
- Claude models (Haiku/Sonnet/Opus)
|
||||
- .cursorrules
|
||||
- Chrome DevTools
|
||||
- Vercel Dashboard
|
||||
- Supabase Dashboard
|
||||
- GitHub
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (4 uur)
|
||||
|
||||
### Multi-Step Form Wizard
|
||||
### Intensive Development Session
|
||||
|
||||
**Bouw met Composer:**
|
||||
**Hou je voortgang bij:**
|
||||
|
||||
| Stap | Features |
|
||||
|------|----------|
|
||||
| 1 | Personal info (naam, email) |
|
||||
| 2 | Preferences (theme, notifications) |
|
||||
| 3 | Review & confirm |
|
||||
| 4 | Success animation |
|
||||
**Eerste uur:**
|
||||
- [ ] Maak prioriteiten list
|
||||
- [ ] Fix top 3 bugs
|
||||
- [ ] Test alles lokaal
|
||||
|
||||
**Requirements:**
|
||||
- Progress indicator
|
||||
- Per-stap validatie
|
||||
- localStorage persistence
|
||||
- TypeScript strict
|
||||
- Tailwind styling
|
||||
- Mobile responsive
|
||||
**Twee uur:**
|
||||
- [ ] Implementeer ontbrekende features
|
||||
- [ ] Add missing error handling
|
||||
- [ ] Improve styling/UX
|
||||
|
||||
**Process:**
|
||||
**Drie uur:**
|
||||
- [ ] Code cleanup
|
||||
- [ ] Performance optimization
|
||||
- [ ] Redeploy
|
||||
|
||||
**Deel 1: Composer Setup (30 min)**
|
||||
1. Open Composer (CMD+I)
|
||||
2. Schrijf comprehensive prompt
|
||||
3. Include @ mentions naar relevante files
|
||||
4. Kies Sonnet of Opus
|
||||
|
||||
**Deel 2: Generatie & Review (45 min)**
|
||||
1. Laat Composer genereren
|
||||
2. Review elke file
|
||||
3. Accept wat goed is
|
||||
4. Reject wat niet past
|
||||
|
||||
**Deel 3: Refinement (45 min)**
|
||||
1. Gebruik CMD+K voor kleine fixes
|
||||
2. Chat voor vragen
|
||||
3. Itereer tot het werkt
|
||||
**Vier uur:**
|
||||
- [ ] Final testing in production
|
||||
- [ ] Screenshot taken
|
||||
- [ ] Docs updated
|
||||
|
||||
### Deliverable
|
||||
- Werkende form wizard
|
||||
- Notities: welk model wanneer, hoeveel iteraties
|
||||
- Werkende app (lokaal en productie)
|
||||
- Updated PROMPT-LOG.md
|
||||
- Updated AI-DECISIONS.md
|
||||
- Screenshot van werkende app
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
## Huiswerk
|
||||
|
||||
### Perfecte .cursorrules
|
||||
|
||||
**Deel 1: Research (30 min)**
|
||||
- Zoek 3-5 .cursorrules voorbeelden online
|
||||
- Analyseer wat ze effectief maakt
|
||||
|
||||
**Deel 2: Write Comprehensive Rules (1 uur)**
|
||||
|
||||
Maak complete .cursorrules voor je eindproject:
|
||||
|
||||
```markdown
|
||||
# [Project Naam]
|
||||
|
||||
## Tech Stack
|
||||
[Jouw stack]
|
||||
|
||||
## Code Conventions
|
||||
[Jouw conventies]
|
||||
|
||||
## File Naming
|
||||
[Jouw regels]
|
||||
|
||||
## Component Structure
|
||||
[Jouw patterns]
|
||||
|
||||
## Styling
|
||||
[Tailwind regels]
|
||||
|
||||
## API Routes
|
||||
[Route conventies]
|
||||
|
||||
## Error Handling
|
||||
[Error patterns]
|
||||
|
||||
## DON'Ts
|
||||
[Wat te vermijden]
|
||||
```
|
||||
|
||||
**Deel 3: Test (30 min)**
|
||||
1. Start nieuw component
|
||||
2. Vraag Cursor om het te bouwen
|
||||
3. Check: volgt Cursor je regels?
|
||||
4. Itereer indien nodig
|
||||
Continue working on your end project for next session. Make sure to:
|
||||
- Push all changes to GitHub
|
||||
- Document what you've done
|
||||
- List remaining work for next session
|
||||
|
||||
### Deliverable
|
||||
- Complete .cursorrules file
|
||||
- Screenshot van Cursor die regels volgt
|
||||
- Korte analyse: wat werkt goed, wat niet
|
||||
|
||||
---
|
||||
|
||||
## 💡 Eindopdracht Check-in
|
||||
|
||||
Hoe gaat je eindproject? Loop je ergens tegenaan? Dit is een goed moment om vragen te stellen en feedback te krijgen van de docent en klasgenoten.
|
||||
- All work committed and pushed
|
||||
- Documentation current
|
||||
- Task list for Les 17
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Het juiste Claude model kiezen per taak
|
||||
- Composer Mode effectief gebruiken voor multi-file features
|
||||
- @ mentions strategisch inzetten voor context
|
||||
- Geavanceerde .cursorrules files schrijven
|
||||
- Codebase indexing optimaliseren
|
||||
- Token gebruik monitoren en kosten beheren
|
||||
- AI-assisted debugging toepassen
|
||||
- Refactoring uitvoeren met Cursor
|
||||
- Zelfstandig bugs identificeren en fixen
|
||||
- Code quality verbeteren
|
||||
- Performance optimisatie toepassen
|
||||
- Productie deployment testen en valideren
|
||||
- Documentatie up-to-date houden
|
||||
|
||||
@@ -1,483 +1,207 @@
|
||||
# Les 17: Vercel AI SDK, Tool Calling & Agents
|
||||
# Les 17: Eindproject Polish & Code Review
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 4: Advanced AI Features** (Les 13-18)
|
||||
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
||||
|
||||
## Beschrijving
|
||||
Bouw AI-powered features in je apps met de Vercel AI SDK. Leer niet alleen chat interfaces bouwen, maar ook hoe AI externe data kan ophalen via Tool Calling en autonome taken kan uitvoeren als Agent.
|
||||
Finale polish fase van je eindproject. Focus op code review, peer feedback, en laatste verbeteringen voor inlevering. Voorbereiding op presentatie.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de Cursor .cursorrules ervaringen uit Les 16 - welke regels zorgen voor betere AI output?
|
||||
Bespreek klassikaal de werkdag voortgang uit Les 16 - wat hebben jullie afgekregen, wat bleek moeilijker dan verwacht?
|
||||
|
||||
### Waarom Vercel AI SDK?
|
||||
### Code Review Checklist
|
||||
|
||||
**Het probleem:** Direct API calls naar OpenAI/Anthropic zijn complex:
|
||||
- Streaming handmatig implementeren
|
||||
- Error handling
|
||||
- State management
|
||||
- Tool calling implementeren
|
||||
**TypeScript & Code Quality:**
|
||||
- [ ] No TypeScript errors
|
||||
- [ ] No `any` types
|
||||
- [ ] Props properly typed
|
||||
- [ ] Error handling complete
|
||||
- [ ] No console.logs in production
|
||||
|
||||
**De oplossing:** Vercel AI SDK
|
||||
- Simpele React hooks (`useChat`, `useCompletion`)
|
||||
- Built-in streaming
|
||||
- Provider-agnostic (OpenAI, Anthropic, etc.)
|
||||
- **Tool calling out-of-the-box**
|
||||
- **Agent capabilities met `maxSteps`**
|
||||
**React Best Practices:**
|
||||
- [ ] No unnecessary re-renders
|
||||
- [ ] Keys properly set in lists
|
||||
- [ ] Hooks rules followed
|
||||
- [ ] Components split logically
|
||||
- [ ] Prop drilling minimized
|
||||
|
||||
**Styling & UX:**
|
||||
- [ ] Responsive design working
|
||||
- [ ] Mobile friendly
|
||||
- [ ] Consistent styling
|
||||
- [ ] Accessible (alt text, labels, etc.)
|
||||
- [ ] No visual bugs
|
||||
|
||||
**Performance:**
|
||||
- [ ] Lighthouse > 80
|
||||
- [ ] Lazy load images
|
||||
- [ ] Optimize bundles
|
||||
- [ ] Fast interactions
|
||||
- [ ] Minimal flickering
|
||||
|
||||
---
|
||||
|
||||
### Installatie & Setup
|
||||
### Peer Review Process
|
||||
|
||||
```bash
|
||||
npm install ai @ai-sdk/openai zod
|
||||
# zod is nodig voor tool parameter validatie
|
||||
```
|
||||
**Hoe peer review doen:**
|
||||
|
||||
**Environment variable:**
|
||||
```bash
|
||||
# .env.local
|
||||
OPENAI_API_KEY=sk-xxxxx
|
||||
```
|
||||
1. **Voorbereiding (10 min)**
|
||||
- Share productie URL of GitHub link
|
||||
- List main features
|
||||
- Highlight AI features
|
||||
|
||||
2. **Review (15 min)**
|
||||
- Reviewer tests alle features
|
||||
- Takes notes
|
||||
- Looks at code (if applicable)
|
||||
|
||||
3. **Feedback (10 min)**
|
||||
- ✅ Wat werkt goed
|
||||
- ⚠️ What could improve
|
||||
- ❌ Any bugs found
|
||||
|
||||
4. **Discussion (5 min)**
|
||||
- Q&A
|
||||
- Discuss suggestions
|
||||
- Agree on priorities
|
||||
|
||||
---
|
||||
|
||||
### Deel 1: Basic Chat (Herhaling)
|
||||
### Final Checklist for Submission
|
||||
|
||||
#### useChat Hook
|
||||
**Functionality:**
|
||||
- [ ] All features work in production
|
||||
- [ ] Auth flows complete
|
||||
- [ ] CRUD operations complete
|
||||
- [ ] AI feature functional
|
||||
- [ ] No console errors
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import { useChat } from 'ai/react'
|
||||
**Documentation:**
|
||||
- [ ] README.md complete
|
||||
- [ ] PROMPT-LOG.md has 10+ entries
|
||||
- [ ] AI-DECISIONS.md has 5+ entries
|
||||
- [ ] .env.example up to date
|
||||
- [ ] Setup instructions clear
|
||||
|
||||
export function ChatComponent() {
|
||||
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()
|
||||
**Code Quality:**
|
||||
- [ ] Code is clean and organized
|
||||
- [ ] Comments where needed
|
||||
- [ ] Consistent naming
|
||||
- [ ] No dead code
|
||||
- [ ] .cursorrules present
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen">
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{messages.map(m => (
|
||||
<div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
|
||||
<span className="inline-block p-2 rounded-lg bg-gray-100">
|
||||
{m.content}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
**Performance & UX:**
|
||||
- [ ] Lighthouse score > 80
|
||||
- [ ] Loading states visible
|
||||
- [ ] Error states handled
|
||||
- [ ] Mobile responsive
|
||||
- [ ] Fast load times
|
||||
|
||||
<form onSubmit={handleSubmit} className="p-4 border-t">
|
||||
<input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Type a message..."
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Basic API Route
|
||||
|
||||
```typescript
|
||||
// app/api/chat/route.ts
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { streamText } from 'ai'
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages } = await req.json()
|
||||
|
||||
const result = streamText({
|
||||
model: openai('gpt-4o-mini'),
|
||||
system: 'You are a helpful assistant.',
|
||||
messages,
|
||||
})
|
||||
|
||||
return result.toDataStreamResponse()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Deel 2: Tool Calling - AI + Externe Data
|
||||
|
||||
**Het probleem met basic chat:**
|
||||
- AI kent alleen zijn training data
|
||||
- Geen toegang tot realtime informatie
|
||||
- Kan geen acties uitvoeren
|
||||
|
||||
**De oplossing: Tool Calling**
|
||||
- Definieer "tools" die AI kan aanroepen
|
||||
- AI besluit zelf wanneer een tool nodig is
|
||||
- Tool haalt data op → AI interpreteert resultaat
|
||||
|
||||
#### Voorbeeld: Cocktail Advisor met TheCocktailDB
|
||||
|
||||
```typescript
|
||||
// app/api/chat/route.ts
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { streamText, tool } from 'ai'
|
||||
import { z } from 'zod'
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages } = await req.json()
|
||||
|
||||
const result = streamText({
|
||||
model: openai('gpt-4o-mini'),
|
||||
system: `Je bent een cocktail expert.
|
||||
Gebruik de tools om cocktails te zoeken en recepten op te halen.
|
||||
Geef persoonlijk advies op basis van de resultaten.`,
|
||||
messages,
|
||||
tools: {
|
||||
// Tool 1: Zoek cocktails op ingrediënt
|
||||
searchByIngredient: tool({
|
||||
description: 'Zoek cocktails die een specifiek ingrediënt bevatten',
|
||||
parameters: z.object({
|
||||
ingredient: z.string().describe('Het ingrediënt om op te zoeken, bijv. "rum" of "vodka"')
|
||||
}),
|
||||
execute: async ({ ingredient }) => {
|
||||
const res = await fetch(
|
||||
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?i=${ingredient}`
|
||||
)
|
||||
const data = await res.json()
|
||||
return data.drinks?.slice(0, 5) || []
|
||||
}
|
||||
}),
|
||||
|
||||
// Tool 2: Haal cocktail details op
|
||||
getCocktailDetails: tool({
|
||||
description: 'Haal het volledige recept van een cocktail op',
|
||||
parameters: z.object({
|
||||
cocktailId: z.string().describe('Het ID van de cocktail')
|
||||
}),
|
||||
execute: async ({ cocktailId }) => {
|
||||
const res = await fetch(
|
||||
`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${cocktailId}`
|
||||
)
|
||||
const data = await res.json()
|
||||
return data.drinks?.[0] || null
|
||||
}
|
||||
}),
|
||||
|
||||
// Tool 3: Zoek non-alcoholische opties
|
||||
searchNonAlcoholic: tool({
|
||||
description: 'Zoek non-alcoholische cocktails/mocktails',
|
||||
parameters: z.object({}),
|
||||
execute: async () => {
|
||||
const res = await fetch(
|
||||
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?a=Non_Alcoholic`
|
||||
)
|
||||
const data = await res.json()
|
||||
return data.drinks?.slice(0, 5) || []
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return result.toDataStreamResponse()
|
||||
}
|
||||
```
|
||||
|
||||
**Wat gebeurt er?**
|
||||
```
|
||||
User: "Ik heb rum en limoen, wat kan ik maken?"
|
||||
|
||||
AI denkt: "Ik moet zoeken op rum"
|
||||
→ Roept searchByIngredient({ ingredient: "rum" }) aan
|
||||
→ Krijgt: [{ name: "Mojito", id: "11000" }, { name: "Daiquiri", id: "11006" }, ...]
|
||||
|
||||
AI denkt: "Mojito klinkt goed met limoen, laat me het recept ophalen"
|
||||
→ Roept getCocktailDetails({ cocktailId: "11000" }) aan
|
||||
→ Krijgt: { name: "Mojito", ingredients: [...], instructions: "..." }
|
||||
|
||||
AI antwoordt: "Met rum en limoen kun je een heerlijke Mojito maken!
|
||||
Je hebt nog nodig: verse munt en suiker..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Deel 3: Agents - Autonome Multi-Step AI
|
||||
|
||||
**Van Tool Calling naar Agent:**
|
||||
- Tool calling = AI roept 1 tool aan, klaar
|
||||
- Agent = AI blijft tools aanroepen totdat de taak af is
|
||||
|
||||
**Het verschil is één parameter: `maxSteps`**
|
||||
|
||||
```typescript
|
||||
const result = streamText({
|
||||
model: openai('gpt-4o-mini'),
|
||||
system: `Je bent een cocktail party planner.
|
||||
Plan een compleet menu met alle details.`,
|
||||
messages,
|
||||
tools: { /* ... tools ... */ },
|
||||
maxSteps: 8 // ← Agent mag 8 tool-calls doen
|
||||
})
|
||||
```
|
||||
|
||||
#### Voorbeeld: Party Planner Agent
|
||||
|
||||
```typescript
|
||||
// app/api/party-planner/route.ts
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { streamText, tool } from 'ai'
|
||||
import { z } from 'zod'
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages } = await req.json()
|
||||
|
||||
const result = streamText({
|
||||
model: openai('gpt-4o'), // Gebruik slimmer model voor agent taken
|
||||
system: `Je bent een professionele cocktail party planner.
|
||||
|
||||
Wanneer iemand een feest wil plannen:
|
||||
1. Zoek eerst cocktails die passen bij de wensen
|
||||
2. Haal recepten op van de beste opties
|
||||
3. Denk aan non-alcoholische alternatieven
|
||||
4. Geef een compleet overzicht met ingrediënten
|
||||
|
||||
Wees proactief en denk mee.`,
|
||||
messages,
|
||||
tools: {
|
||||
searchByIngredient: tool({
|
||||
description: 'Zoek cocktails met een ingrediënt',
|
||||
parameters: z.object({
|
||||
ingredient: z.string()
|
||||
}),
|
||||
execute: async ({ ingredient }) => {
|
||||
const res = await fetch(
|
||||
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?i=${ingredient}`
|
||||
)
|
||||
return res.json()
|
||||
}
|
||||
}),
|
||||
|
||||
getCocktailDetails: tool({
|
||||
description: 'Haal recept details op',
|
||||
parameters: z.object({
|
||||
cocktailId: z.string()
|
||||
}),
|
||||
execute: async ({ cocktailId }) => {
|
||||
const res = await fetch(
|
||||
`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${cocktailId}`
|
||||
)
|
||||
return res.json()
|
||||
}
|
||||
}),
|
||||
|
||||
searchNonAlcoholic: tool({
|
||||
description: 'Zoek mocktails',
|
||||
parameters: z.object({}),
|
||||
execute: async () => {
|
||||
const res = await fetch(
|
||||
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?a=Non_Alcoholic`
|
||||
)
|
||||
return res.json()
|
||||
}
|
||||
}),
|
||||
|
||||
searchByCategory: tool({
|
||||
description: 'Zoek cocktails per categorie (Cocktail, Shot, Beer, etc.)',
|
||||
parameters: z.object({
|
||||
category: z.string()
|
||||
}),
|
||||
execute: async ({ category }) => {
|
||||
const res = await fetch(
|
||||
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=${category}`
|
||||
)
|
||||
return res.json()
|
||||
}
|
||||
})
|
||||
},
|
||||
maxSteps: 10 // Agent kan tot 10 tool calls doen
|
||||
})
|
||||
|
||||
return result.toDataStreamResponse()
|
||||
}
|
||||
```
|
||||
|
||||
**Agent in actie:**
|
||||
```
|
||||
User: "Plan cocktails voor mijn verjaardagsfeest.
|
||||
15 mensen, een paar drinken geen alcohol,
|
||||
we houden van citrus smaken."
|
||||
|
||||
Agent stappen:
|
||||
1. searchByIngredient("lemon") → 12 cocktails
|
||||
2. searchByIngredient("lime") → 15 cocktails
|
||||
3. searchByIngredient("orange") → 10 cocktails
|
||||
4. searchNonAlcoholic() → 8 mocktails
|
||||
5. getCocktailDetails("11000") → Mojito recept
|
||||
6. getCocktailDetails("11007") → Margarita recept
|
||||
7. getCocktailDetails("12162") → Virgin Piña Colada recept
|
||||
8. getCocktailDetails("12316") → Lemonade recept
|
||||
|
||||
Output: Compleet party plan met:
|
||||
- 3 alcoholische cocktails met citrus
|
||||
- 2 mocktails voor niet-drinkers
|
||||
- Gecombineerde ingrediëntenlijst
|
||||
- Tips voor bereiding
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Gratis APIs voor Projecten
|
||||
|
||||
| API | Data | URL | Auth |
|
||||
|-----|------|-----|------|
|
||||
| TheCocktailDB | 636 cocktails, recepten | thecocktaildb.com/api.php | Geen (key=1) |
|
||||
| TheMealDB | 597 recepten, ingrediënten | themealdb.com/api.php | Geen (key=1) |
|
||||
| Open Trivia DB | 4000+ quiz vragen | opentdb.com/api_config.php | Geen |
|
||||
| REST Countries | Landen data | restcountries.com | Geen |
|
||||
| Open Library | Boeken data | openlibrary.org/developers | Geen |
|
||||
|
||||
---
|
||||
|
||||
### Best Practices
|
||||
|
||||
**Tool Design:**
|
||||
```typescript
|
||||
// ✅ Goed: Specifieke, duidelijke tools
|
||||
searchByIngredient: tool({
|
||||
description: 'Zoek cocktails die een specifiek ingrediënt bevatten',
|
||||
// ...
|
||||
})
|
||||
|
||||
// ❌ Slecht: Vage tool
|
||||
search: tool({
|
||||
description: 'Zoek iets',
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
**Agent System Prompts:**
|
||||
```typescript
|
||||
// ✅ Goed: Geef duidelijke instructies
|
||||
system: `Je bent een cocktail expert.
|
||||
|
||||
Wanneer je een vraag krijgt:
|
||||
1. Zoek eerst relevante cocktails
|
||||
2. Haal details op van de beste matches
|
||||
3. Geef persoonlijk advies
|
||||
|
||||
Wees proactief en denk mee met de gebruiker.`
|
||||
|
||||
// ❌ Slecht: Te vaag
|
||||
system: `Je bent een assistent.`
|
||||
```
|
||||
|
||||
**Error Handling in Tools:**
|
||||
```typescript
|
||||
execute: async ({ ingredient }) => {
|
||||
try {
|
||||
const res = await fetch(`...`)
|
||||
if (!res.ok) {
|
||||
return { error: 'Kon geen cocktails vinden' }
|
||||
}
|
||||
return res.json()
|
||||
} catch (error) {
|
||||
return { error: 'API niet beschikbaar' }
|
||||
}
|
||||
}
|
||||
```
|
||||
**Deployment:**
|
||||
- [ ] Deployed on Vercel
|
||||
- [ ] Working on production URL
|
||||
- [ ] Supabase configured
|
||||
- [ ] Environment variables secure
|
||||
- [ ] No errors in production
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Vercel AI SDK (`ai` package)
|
||||
- Zod (parameter validatie)
|
||||
- Next.js API Routes
|
||||
- Externe APIs (TheCocktailDB, TheMealDB, etc.)
|
||||
- GitHub
|
||||
- Vercel
|
||||
- Chrome DevTools
|
||||
- Cursor
|
||||
- Peer reviewers
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht (3 uur)
|
||||
|
||||
### Bouw een AI Agent met Externe Data
|
||||
### Code Review & Polish Session
|
||||
|
||||
**Deel 1: Setup (15 min)**
|
||||
1. `npm install ai @ai-sdk/openai zod`
|
||||
2. Voeg `OPENAI_API_KEY` toe aan `.env.local`
|
||||
3. Kies je API: TheCocktailDB of TheMealDB
|
||||
**Deel 1: Peer Review (1 uur)**
|
||||
|
||||
**Deel 2: Basic Tool Calling (45 min)**
|
||||
1. Maak `/api/chat/route.ts`
|
||||
2. Implementeer 2 tools:
|
||||
- Zoek op ingrediënt
|
||||
- Haal details op
|
||||
3. Test: "Wat kan ik maken met [ingrediënt]?"
|
||||
Work in pairs or small groups:
|
||||
1. Exchange project URLs/repos
|
||||
2. Each person reviews another's work
|
||||
3. Take detailed notes
|
||||
4. Provide constructive feedback
|
||||
5. Discuss improvements
|
||||
|
||||
**Deel 3: Agent met maxSteps (45 min)**
|
||||
1. Voeg `maxSteps: 5` toe
|
||||
2. Voeg een 3e tool toe (bijv. zoek per categorie)
|
||||
3. Verbeter je system prompt voor agent gedrag
|
||||
4. Test: "Help me een menu plannen voor..."
|
||||
**Deel 2: Final Polish (1.5 uur)**
|
||||
|
||||
**Deel 4: Frontend (15 min)**
|
||||
1. Bouw chat UI met `useChat`
|
||||
2. Voeg loading indicator toe
|
||||
3. Test de complete flow
|
||||
Based on feedback:
|
||||
1. Fix identified bugs
|
||||
2. Implement suggested improvements
|
||||
3. Code cleanup
|
||||
4. Update documentation
|
||||
5. Final test in production
|
||||
|
||||
**Deel 3: Final Checks (30 min)**
|
||||
|
||||
Go through the submission checklist:
|
||||
1. Verify all items are done
|
||||
2. Test everything once more
|
||||
3. Make final commits
|
||||
4. Push to GitHub
|
||||
5. Screenshot for documentation
|
||||
|
||||
### Deliverable
|
||||
- Werkende agent met minimaal 3 tools
|
||||
- Chat interface
|
||||
- Screenshot van agent die meerdere tools aanroept
|
||||
- Peer review feedback received
|
||||
- All feedback items addressed
|
||||
- Final production-ready code
|
||||
- Complete documentation
|
||||
- Screenshot of final app
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
## Huiswerk
|
||||
|
||||
### Bouw AI Feature voor Eindproject
|
||||
**Final submission preparation:**
|
||||
|
||||
**Deel 1: Agent Design (30 min)**
|
||||
1. **Complete ALL documentation:**
|
||||
- README with features and setup
|
||||
- PROMPT-LOG.md with 10+ prompts
|
||||
- AI-DECISIONS.md with 5+ decisions
|
||||
- Project state documented
|
||||
|
||||
Plan je agent voor de eindopdracht:
|
||||
- Welke externe API gebruik je?
|
||||
- Welke tools heeft je agent nodig? (minimaal 3)
|
||||
- Wat is de typische flow?
|
||||
2. **Final testing:**
|
||||
- Test all features in production
|
||||
- Check Lighthouse score
|
||||
- Verify mobile responsiveness
|
||||
- Check load times
|
||||
|
||||
Documenteer in `docs/AI-DECISIONS.md`
|
||||
3. **Code review:**
|
||||
- Ask classmates to review code
|
||||
- Ask docent for feedback
|
||||
- Fix any issues found
|
||||
- Final cleanup
|
||||
|
||||
**Deel 2: Implementatie (1 uur)**
|
||||
|
||||
Bouw de agent voor je eindproject:
|
||||
- Minimaal 3 tools
|
||||
- `maxSteps` van minimaal 3
|
||||
- Goede error handling
|
||||
- Relevante system prompt
|
||||
|
||||
**Deel 3: Integratie (30 min)**
|
||||
|
||||
Combineer met Supabase:
|
||||
- Sla user preferences op
|
||||
- Geef preferences mee als context aan agent
|
||||
- Sla conversation history op
|
||||
4. **Prepare for submission:**
|
||||
- Ensure Git history is clean
|
||||
- All commits have good messages
|
||||
- GitHub repo is public/accessible
|
||||
- Production URL is stable
|
||||
|
||||
### Deliverable
|
||||
- Werkende agent in eindproject
|
||||
- `docs/AI-DECISIONS.md` met agent design
|
||||
- Minimaal 5 prompts in `PROMPT-LOG.md`
|
||||
|
||||
---
|
||||
|
||||
## 💡 Eindopdracht
|
||||
|
||||
Heb je al nagedacht over je AI feature? Dit is het moment om je idee te bespreken met de docent en klasgenoten. Welke externe API ga je gebruiken? Welke tools heeft je agent nodig?
|
||||
- Final, polished application
|
||||
- All documentation complete
|
||||
- Code review completed
|
||||
- Ready for submission
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Vercel AI SDK installeren en configureren
|
||||
- Tools definiëren met Zod parameters
|
||||
- Tool Calling implementeren voor externe API integratie
|
||||
- Agents bouwen met `maxSteps` voor autonome taken
|
||||
- De juiste aanpak kiezen (basic chat vs tool calling vs agent)
|
||||
- Error handling implementeren in tools
|
||||
- Gratis externe APIs integreren in AI features
|
||||
- Code review uitvoeren volgens best practices
|
||||
- Peer feedback ontvangen en implementeren
|
||||
- Final polish toepassen op projecten
|
||||
- Production checklist doorlopen
|
||||
- Professional quality deliverables opleveren
|
||||
- Zelfstandig werk evalueren en verbeteren
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 4: Advanced AI Features** (Les 13-18)
|
||||
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
||||
|
||||
## Beschrijving
|
||||
Deploy je eindproject naar productie. Leer environment variables, Vercel deployment, en basis performance optimalisatie. Bespreking van de eindopdracht requirements en afrondende werksessie.
|
||||
|
||||
Reference in New Issue
Block a user