`. Commit en push naar dezelfde branch.
-`*[Wacht]*`
+CI draait opnieuw, faalt. Rode X. Open de logs in GitHub — zie de TypeScript error.
-**Vertel:** "Groene check verschijnt. Merge PR. CI is nu live op elke toekomstige PR."
+Fix met Composer:
-`*[Demo: failing CI]*`
+> Repareer de TypeScript build error in `app/page.tsx`.
-**Vertel:** "Laat ik laten zien wat er gebeurt bij een fout."
+Composer fixt. Commit, push. CI draait. Groen.
-`*[Cursor → Composer:]*`
+Laatste stap: branch protection. Ga naar GitHub repo → Settings → Branches → Add rule. Branch name pattern: `main`. Vink aan: "Require status checks to pass before merging". Selecteer de CI-workflow. Save.
-> Maak een TypeScript fout in app/page.tsx. Verwijder een belangrijke import. Push naar een nieuwe branch chore/test-ci-fail.
+Vanaf nu mag niemand — ook jij niet — naar main mergen zonder groene CI.
-```bash
-git push -u origin chore/test-ci-fail
-```
-
-`*[GitHub → PR open → wait]*`
-
-**Vertel:** "Rode X. Build faalt. Klik op de details — daar staat letterlijk wat mis is."
-
-`*[Fix met Composer]*`
-
-> Repareer de TypeScript-fout in app/page.tsx (voeg de import terug). Commit.
-
-```bash
-git push
-```
-
-`*[Wacht — groene check]*`
-
-**Vertel:** "Fixed. CI groen. Merge mag nu."
-
-`*[Branch protection toevoegen — kort]*` `[SCHERM: github settings]`
-
-**Vertel:** "Tot slot — branch protection. Settings → Branches → Add rule. Branch pattern: main. Vink aan: require PR before merging. Require status checks: select onze CI workflow. Save.
-
-Wat dit doet: niemand kan nog direct naar main pushen. Background Agents werken via PRs — dat blijft werken. PR moet groene CI hebben voor merge."
-
-`*[Demo: probeer direct push naar main]*`
-
-```bash
-git checkout main && echo "test" >> README.md && git commit -am "direct" && git push
-```
-
-```
-! [remote rejected] main -> main (protected branch hook declined)
-```
-
-**Vertel:** "Geblokkeerd. Goed."
+Merge de PR. Klaar.
---
-## BLOK 10 — Composer vs Background reflectie (10 min)
+## BLOK 10 — Composer vs Background (10 min)
-`[SLIDE 15 — Wanneer welke]`
+`[SLIDE 15]`
-**Vertel:** "Twee tools, één keuze per taak. Wanneer welke?
+Vandaag hebben we beide gebruikt. Wanneer kies je welke?
-Composer voor pair-programming. Snel feedback, jij stuurt, agent voert uit, jij accepteert per diff. Goed voor: exploreren, leren, refactors waar je wilt meekijken.
+Composer is je pair-programmer. Je zit erbij, hij maakt aanpassingen, je accepteert per file. Goed voor: snelle wijzigingen tijdens je werk, complex refactor waar je wilt meekijken, onbekend gebied waar je niet wilt dat een agent vastloopt.
-Background Agent voor delegatie. Specifieke feature, kun je niet bij zijn, parallel werken aan andere taken. Goed voor: PR-prep, parallel werken aan 3 features, terwijl-je-vergadert.
+Background Agent is je delegate. Je geeft een welomschreven taak, gaat iets anders doen, krijgt later een PR met preview URL. Goed voor: features met heldere specs, parallel werken aan meerdere features, tijdens een meeting iets voorbereiden.
-Risico's bij Background. Vage prompts = vage PR's. Specifiek zijn helpt. Agent kost credits — voor solo-projecten geen issue, voor teams kan oplopen.
+Mentaal model: Composer is "samen werken". Background Agent is "uitbesteden".
-Wat ik zelf doe: 80% Composer, 20% Background. Background voor de écht goed-gedefinieerde features die ik dan parallel laat draaien."
+Twee waarschuwingen voor Background Agent. Een: hij kan vastlopen op rare config — debuggen is moeilijker omdat je er niet bij was. Twee: hij verbruikt credits, dus Cursor Pro of Business is wel nodig.
-💬 *Vraag: 'Welke is beter voor onze eindopdracht?'*
-
-**Antwoord:** "Beide. Ontwerpen + leren = Composer. Specifieke features uitwerken = Background. Voor de eindopdracht-pitch: laat zien dat je beide kunt — dat is een vaardigheid."
+Praktische tip: te vage prompts geven matige PR's. Wees specifiek. Noem de bestanden, beschrijf de UX, geef voorbeelden van wat je wilt. Hoe meer context, hoe beter de output.
---
## BLOK 11 — Lesopdracht + Huiswerk (10 min)
-`[SLIDE 16 — Lesopdracht + Huiswerk]`
+`[SLIDE 16]`
-**Vertel:** "Lesopdracht — in de les. Half uur. Je eigen Pokédex setuppen, één feature met Composer, push naar GitHub, deploy op Vercel. Aan het eind: jouw eigen productie-URL.
+Tijd voor jullie eigen werk. Twee delen.
-Huiswerk — voor volgende week. Vier dingen.
+**Lesopdracht — doe je nu, dertig minuten:**
-Een: feature toevoegen via Background Agent. Branch + PR + preview URL.
+Een. Eigen Pokédex-repo opzetten met dezelfde stappen als de demo: `pnpm create next-app`, Composer prompt voor de lijst, Composer prompt voor de detail-page.
-Twee: GitHub Actions CI workflow. Lint + build per PR.
+Twee. Push naar GitHub.
-Drie: branch protection op main. PR's verplicht, CI moet groen.
+Drie. Verbind met Vercel, deploy. Open de productie-URL en stuur de URL in de chat — ik wil zien dat het werkt.
-Vier: DEPLOY.md schrijven. URLs, prompt die je aan Background Agent gaf, reflectie over Composer vs Background, bewijs van CI (groen + rood voorbeeld), één observatie.
+Vier. Voeg de `NEXT_PUBLIC_APP_ENV` environment variable toe met een aparte waarde voor production en preview. Verifieer dat het werkt op beide URLs.
-Bonus: tweede externe API, environment scoping, parallelle Background Agents.
+**Huiswerk — voor volgende les:**
-Tien punten, voldoende is zes. Inleveren via Brightspace: repo URL + productie URL + DEPLOY.md."
+A. Eén feature toevoegen met de Background Agent. Mijn voorstel: zoekbalk, type-filter, favorieten-systeem, of vergelijk-twee-pokemon. Open de PR, krijg de preview URL.
+
+B. GitHub Actions CI toevoegen met lint en build.
+
+C. Branch protection op `main` instellen.
+
+D. Schrijf een `DEPLOY.md` in de root van je repo met: productie-URL, één preview-URL, en een reflectie van ongeveer driehonderd woorden over Composer versus Background Agent. Wanneer werkte welke beter? Geef één voorbeeld waar Background verbluffend goed of juist slecht was.
+
+Bonus voor wie wil: vervang de Pokédex door een andere externe API. Open-Meteo voor weer, GitHub API voor jouw eigen repositories, of een free API naar keuze.
+
+Inleveren via Teams, voor volgende les.
---
## BLOK 12 — Afsluiting (5 min)
-`[SLIDE 17 — Afsluiting]`
+`[SLIDE 17]`
-**Vertel:** "Wat hebben we vandaag gedaan. Externe APIs in Server Components. Cursor Composer voor synchrone werk. Cursor Background Agent voor async PR's. Vercel productie + preview per branch. GitHub Actions CI. Branch protection.
+Vandaag hebben we veel gedaan.
-Volgende les: RAG en embeddings. Vector search met pgvector. We bouwen een tool die semantic search doet — handig om in een agent te plakken. Combo van les 13 en 15.
+We hebben externe APIs leren fetchen vanuit Server Components. We hebben drie rendering modes gezien: static voor de lijst, ISR voor detail-pages, en kort genoemd hoe je dynamic of client-side zou doen. Dat is een knop die je per fetch omdraait.
-Daarna: performance + observability, eindopdracht-werkdagen, pitch.
+We hebben Cursor Composer gebruikt voor synchrone wijzigingen, en Background Agent voor twee parallel-features. Verschillende tools voor verschillende momenten.
-Vragen?"
+We hebben naar Vercel gedeployed. Productie op main, preview per branch. En het belangrijkste: environment variables per omgeving. Een variabele kan drie totaal verschillende waarden hebben, één per scope.
-`*[Vragenronde — minstens 5 min over laten]*`
+En tenslotte GitHub Actions CI. Zekerheid dat je code lint en bouwt voor de merge.
+
+Volgende les is les 14: Agents. Nog een stap verder dan tool calling. LLM in een loop met tools, autonoom twintig tot vijftig stappen. We bouwen een research-agent from scratch met `ToolLoopAgent`, `stopWhen` en `prepareStep`. En we bespreken wanneer agents wel of niet de juiste keuze zijn.
+
+Dat was 'm. Bedankt voor jullie aandacht. Vragen?
---
-## NA DE LES — Wrap-up
+## VEELVOORKOMENDE FOUTEN — voor mezelf
-- Push working `pokedex` repo naar GitHub als referentie voor studenten
-- Brightspace: link naar referentie-repo + huiswerk-instructies
-- Verzamel vragen die niet beantwoord — terug in Les 13 opening
-- Voor Klas B (later): check of Cursor Pro licenties beschikbaar zijn voor studenten, anders Background Agent demo moeilijk
+| Probleem | Oplossing |
+|----------|-----------|
+| Vercel deploy faalt op build | Check Build Logs — meestal type-error of missende env var |
+| Background Agent doet niks | Repo niet gekoppeld aan Cursor cloud — Cmd+Shift+P → Repository Settings |
+| Preview URL toont oude versie | Hard refresh (Cmd+Shift+R), of Vercel cache invalidate |
+| Env var komt niet door | Redeploy na toevoegen var — bestaande build pakt 'm niet automatisch |
+| `process.env.NEXT_PUBLIC_*` undefined | Moet beginnen met `NEXT_PUBLIC_` om in browser te werken |
+| GitHub Actions faalt op pnpm | `actions/setup-node` MET `cache: pnpm` — anders geen lockfile-aware install |
+| Branch protection blokkeert PR | Eerst CI groen zien voor je merge |
+| Composer 'kan repo niet vinden' | Cursor verbinden met die specifieke repo via Cmd+Shift+P |
---
-## Veelvoorkomende fouten tijdens live coding
+## TIMING-CHECK
-| Fout | Oplossing |
-|------|-----------|
-| `next/image` PokéAPI domain error | Composer: "voeg images.remotePatterns toe in next.config.ts" |
-| Cursor Composer onresponsive | Cmd+I opnieuw, of restart Cursor |
-| Background Agent verbinding faalt | Logout/login Cursor cloud, refresh repo connection |
-| Vercel deploy faalt: missing dep | `pnpm install` lokaal, commit lockfile, push |
-| GitHub Actions: pnpm not found | Check `pnpm/action-setup@v3` versie in yml |
-| Branch protection: kan eigen PR niet mergen | Tijdens demo: voeg jezelf toe als bypass user |
-| Preview URL 404 | Wachten — eerste build duurt soms 2 min |
-| Background Agent fixt verkeerde files | Te vage prompt — specifiek met file paths volgende keer |
+| Blok | Cumulatief | Duur |
+|------|-----------|------|
+| 1. Welkom + Terugblik | 0–10 | 10 min |
+| 2. Theorie Externe APIs | 10–25 | 15 min |
+| 3. Theorie Vercel + GH | 25–40 | 15 min |
+| 4. Wat we bouwen | 40–45 | 5 min |
+| 5. Demo 1 — Composer | 45–75 | 30 min |
+| 6. Demo 2 — Vercel + env | 75–95 | 20 min |
+| 7. Pauze | 95–110 | 15 min |
+| 8. Demo 3 — Background Agent | 110–135 | 25 min |
+| 9. Demo 4 — GitHub Actions | 135–150 | 15 min |
+| 10. Composer vs Background | 150–160 | 10 min |
+| 11. Lesopdracht + Huiswerk | 160–170 | 10 min |
+| 12. Afsluiting | 170–175 | 5 min |
+| Buffer voor vragen | 175–180 | 5 min |
----
-
-## Mentale model voor de klas
-
-Als studenten verward zijn over de Cursor agent-modes:
-
-> **Composer** = jij pair-programt met AI. Synchroon. Cmd+I. Lokaal.
->
-> **Background Agent** = jij delegeert aan AI. Async. Cmd+Shift+P. Cloud. Opent PR.
-
-Belangrijkste regel voor Background Agent: prompts moeten zo specifiek zijn alsof je het aan een nieuwe junior junior dev op afstand uitlegt. Geen context-cues, geen "je weet wel".
+Totaal: 180 minuten.
diff --git a/Les13-Cursor-Vercel-Deploy/Les13-Huiswerk.pdf b/Les13-Cursor-Vercel-Deploy/Les13-Huiswerk.pdf
index 3753695..1f046d3 100644
--- a/Les13-Cursor-Vercel-Deploy/Les13-Huiswerk.pdf
+++ b/Les13-Cursor-Vercel-Deploy/Les13-Huiswerk.pdf
@@ -97,7 +97,7 @@ endobj
endobj
13 0 obj
<<
-/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607091328+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607091328+00'00') /Producer (ReportLab PDF Library - \(opensource\))
+/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607134605+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607134605+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 13 Huiswerk) /Trapped /False
>>
endobj
@@ -182,7 +182,7 @@ xref
trailer
<<
/ID
-[
]
+[<9e0017c2fae42cc449cacedd8dc86476><9e0017c2fae42cc449cacedd8dc86476>]
% ReportLab generated PDF document -- digest (opensource)
/Info 13 0 R
diff --git a/Les13-Cursor-Vercel-Deploy/Les13-Lesopdracht.pdf b/Les13-Cursor-Vercel-Deploy/Les13-Lesopdracht.pdf
index 1ad4a90..e784e1f 100644
--- a/Les13-Cursor-Vercel-Deploy/Les13-Lesopdracht.pdf
+++ b/Les13-Cursor-Vercel-Deploy/Les13-Lesopdracht.pdf
@@ -77,7 +77,7 @@ endobj
endobj
11 0 obj
<<
-/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607091328+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607091328+00'00') /Producer (ReportLab PDF Library - \(opensource\))
+/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607134605+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607134605+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 13 Lesopdracht) /Trapped /False
>>
endobj
@@ -144,7 +144,7 @@ xref
trailer
<<
/ID
-[]
+[<6d65c6032a302e19cbfdfbf2e67cdcbe><6d65c6032a302e19cbfdfbf2e67cdcbe>]
% ReportLab generated PDF document -- digest (opensource)
/Info 11 0 R
diff --git a/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.md b/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.md
index 64f96b9..4941fb8 100644
--- a/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.md
+++ b/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.md
@@ -3,7 +3,7 @@
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
-**Vorige les:** Les 14 — RAG + Embeddings
+**Vorige les:** Les 12 — Tool Calling
**Volgende les:** Les 14 — Agents
---
@@ -65,29 +65,49 @@ Drie dingen om te onthouden:
---
-## 2. Server- vs client-side fetching
+## 2. Vier manieren om een externe API te fetchen
-### Server-side (Server Component of API route)
+Next.js geeft je per fetch-call de keuze hoe je data wilt renderen. Voor 95% van je werk komt het neer op drie server-modes plus client-side. De code verandert minimaal — meestal één optie in je `fetch()`.
+
+### 2.1 Static (build-time)
```typescript
-// app/pokemon/[name]/page.tsx
-async function getPokemon(name) {
- const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`, {
- next: { revalidate: 3600 }, // cache 1 uur
- });
+// app/page.tsx — Server Component
+async function getPokemon() {
+ const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
return res.json();
}
```
-**Voordelen:**
-- API key blijft op de server (nooit in client-bundle)
-- Caching gratis via Next.js
-- SEO-vriendelijk (HTML met data terug)
-- Snellere TTFB (geen extra roundtrip)
+- Gefetched tijdens `next build`, daarna **HTML voor altijd**
+- Geen server-werk per request → snelste optie
+- Data ververst alleen bij een nieuwe build (= nieuwe deploy)
+- **Goed voor:** marketing-pagina's, blogs, productlijsten, alles dat zelden verandert
+- **Niet voor:** prijzen die per minuut updaten, user-specifieke data
-**Wanneer gebruiken:** initiële data, lijsten, detail-pagina's.
+### 2.2 ISR — Incremental Static Regeneration
-### Client-side (`useEffect` of `useState` met fetch)
+```typescript
+const res = await fetch(URL, { next: { revalidate: 3600 } });
+```
+
+- Eerste request: zelfde als static (HTML uit cache)
+- Na 3600 seconden: volgende request krijgt nog steeds cache, maar achter de schermen wordt de pagina vernieuwd
+- **Goed voor:** Pokédex detail-pages, een nieuws-feed die niet realtime hoeft, productdetails
+- **De sweet spot tussen snel en redelijk vers**
+
+### 2.3 Dynamic (request-time / SSR)
+
+```typescript
+const res = await fetch(URL, { cache: "no-store" });
+```
+
+- Bij elke request opnieuw gefetched op de server
+- Altijd 100% vers, maar trager (geen cache)
+- **Goed voor:** dashboards met live data, user-specifieke views, anything per-request
+- **Let op cost:** elke request = serverless function call
+
+### 2.4 Client-side (`useEffect`)
```typescript
"use client";
@@ -102,63 +122,73 @@ export function LivePokemon({ name }) {
}
```
-**Wanneer gebruiken:**
-- User-interactie (search, filter, infinite scroll)
-- Real-time updates (polling, websockets)
-- Data die per user verschilt en niet op server kan
+- Fetch gebeurt in de browser na hydration
+- Goed voor user-interactie, real-time, infinite scroll
+- **Cruciaal:** API-keys staan **in de browser** als je hier rechtstreeks naar een externe API roept. Alleen voor publieke APIs, of via een eigen `/api/...` proxy-route.
-> **Belangrijke regel:** API keys NOOIT in client-code. Voor authenticated externe APIs: maak een API-route in `app/api/...` als proxy.
+### Spiekbriefje
-### Cache-opties bij `fetch`
-
-```typescript
-fetch(url, { cache: "force-cache" }) // permanent (default Next.js)
-fetch(url, { next: { revalidate: 3600 } }) // ISR — herval na N seconden
-fetch(url, { cache: "no-store" }) // nooit cachen (per request)
-fetch(url, { next: { tags: ["pokemon"] } }) // tag-based revalidation
-```
+| Mode | `fetch()` optie | Wanneer |
+|------|----------------|---------|
+| Static | `fetch(URL)` (default) | Data verandert per deploy |
+| ISR | `{ next: { revalidate: N } }` | Data verandert traag |
+| Dynamic | `{ cache: "no-store" }` | Data verandert per request |
+| Client | `useEffect` + fetch | User-interactie / live |
+| Tag-based | `{ next: { tags: ["x"] } }` | Triggered revalidation via `revalidateTag()` |
---
-## 3. Environment variabelen
+## 3. Environment variabelen — server-only vs client-exposed
-### Drie scopes in Next.js + Vercel
+### Twee soorten variabelen
-| Scope | Wanneer | Waar |
-|-------|---------|------|
-| `OPENAI_API_KEY` | Server-only | Process env op server |
-| `NEXT_PUBLIC_APP_URL` | Server + client | In client-bundle (zichtbaar in DevTools!) |
-| `.env.local` | Lokaal alleen | Niet committen, `.gitignore` |
+| Type | Voorbeeld | Waar beschikbaar | Veiligheid |
+|------|-----------|------------------|------------|
+| Server-only | `OPENAI_API_KEY`, `DATABASE_URL` | Alleen op de server | **Geheim** — nooit zichtbaar voor de browser |
+| Client-exposed | `NEXT_PUBLIC_APP_ENV`, `NEXT_PUBLIC_APP_URL` | Server + client bundle | **Publiek** — iedereen kan ze zien in DevTools |
-### Regel
-
-**Alles wat geheim is — zonder `NEXT_PUBLIC_` prefix.** Met prefix = in client bundle = iedereen kan het zien.
+**Regel:** alles met `NEXT_PUBLIC_` prefix wordt ingebakken in de browser-bundle. Zonder prefix = server-only.
```typescript
-// ✅ Server-only — veilig
+// ✅ Veilig — server-side gebruik
process.env.OPENAI_API_KEY
-// ❌ NEVER doen
+// ❌ NOOIT
"use client";
-const key = process.env.NEXT_PUBLIC_OPENAI_KEY; // staat in HTML response!
+const key = process.env.NEXT_PUBLIC_OPENAI_KEY; // staat in de HTML response
```
-### Vercel environment scoping
+### 3.1 Vercel — drie omgevingen, drie scopes
-Op Vercel heb je drie environments:
+Op Vercel heb je per project drie environments. Bij elke env-var kies je in welke scopes hij wordt geïnjecteerd:
-- **Production** — deploys vanaf `main`
-- **Preview** — alle andere branches + PRs
-- **Development** — lokaal (`vercel env pull` om te syncen)
+| Scope | Wordt gebruikt bij | URL-patroon |
+|-------|---------------------|-------------|
+| **Production** | Push naar `main` | `je-app.vercel.app` |
+| **Preview** | Push naar elke andere branch | `je-app-git-{branch}-{user}.vercel.app` |
+| **Development** | Lokaal (`pnpm dev`) | `localhost:3000` |
-Per env-var kies je in welke scopes hij beschikbaar is. Handig:
+### 3.2 Per-omgeving andere waarden
-```
-OPENAI_API_KEY_PROD → Production scope
-OPENAI_API_KEY_TEST → Preview + Development scope
-```
+In **Vercel → Project → Settings → Environment Variables** zet je per variabele drie keer een waarde:
-Op die manier verbruiken preview deploys niet je productie-quota.
+| Var | Production | Preview | Development |
+|-----|------------|---------|-------------|
+| `DATABASE_URL` | productie-DB | staging-DB | lokale DB |
+| `OPENAI_API_KEY` | echte key | test-key (lage limit) | jouw key |
+| `STRIPE_KEY` | `sk_live_...` | `sk_test_...` | `sk_test_...` |
+| `NEXT_PUBLIC_APP_URL` | `je-app.com` | preview-URL | `http://localhost:3000` |
+
+**Hoe in te stellen:** in de UI: Add new → vink alleen de juiste environment(s) aan → Save.
+
+**Belangrijke veiligheidsregels:**
+- Productie-secrets (echte API keys, prod-DB-password): **alleen** Production vinken
+- Preview krijgt een test-variant, anders kunnen experimentele branches je productie raken
+- Development: `vercel env pull` om Vercel's dev-vars naar je `.env.local` te syncen, of zelf invullen
+
+### 3.3 Verandering pakt pas door bij nieuwe build
+
+Bestaande deploys zien een nieuwe env-var niet automatisch. Na het toevoegen of wijzigen: **redeploy** (Deployments → ··· → Redeploy) of push een nieuwe commit.
---
diff --git a/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.pdf b/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.pdf
index 6aae1b4..4f6966d 100644
--- a/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.pdf
+++ b/Les13-Cursor-Vercel-Deploy/Les13-Lesstof.pdf
@@ -137,7 +137,7 @@ endobj
endobj
17 0 obj
<<
-/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607091328+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607091328+00'00') /Producer (ReportLab PDF Library - \(opensource\))
+/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260607134605+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260607134605+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (Les 13 Lesstof) /Trapped /False
>>
endobj
@@ -259,7 +259,7 @@ xref
trailer
<<
/ID
-[<7a091ff13b69dcda5597317f91fb07e0><7a091ff13b69dcda5597317f91fb07e0>]
+[<1e77dbe9d9f83762252045861df07f97><1e77dbe9d9f83762252045861df07f97>]
% ReportLab generated PDF document -- digest (opensource)
/Info 17 0 R
diff --git a/Les13-Cursor-Vercel-Deploy/Les13-Slide-Overzicht.md b/Les13-Cursor-Vercel-Deploy/Les13-Slide-Overzicht.md
index ad3211d..28cc97f 100644
--- a/Les13-Cursor-Vercel-Deploy/Les13-Slide-Overzicht.md
+++ b/Les13-Cursor-Vercel-Deploy/Les13-Slide-Overzicht.md
@@ -71,51 +71,41 @@
**Voorbeelden:**
- PokéAPI — Pokemon data (geen key)
+- Open-Meteo — weer (geen key)
+- GitHub API — repos, users, issues
- Tavily — web search (key nodig)
- Stripe — betalingen (key + secret)
-- GitHub API — repos, users, issues
-- Open-Meteo — weer (geen key)
-**In Next.js — twee plekken om te fetchen:**
-
-| Server-side (RSC / API route) | Client-side (`useEffect`) |
-|-------------------------------|---------------------------|
-| Snel, key veilig, gecached | Nodig voor user-interactie |
-| Voor initiële data | Voor live updates |
+**API keys** horen in environment variables — nooit in je code committen.
**Visual:** Diagram: Next.js → fetch → externe API → JSON terug.
---
-## Slide 5: Externe API in Next.js — code
-### Server Component met fetch
+## Slide 5: Drie manieren om een API te fetchen in Next.js
+### Static, Server (dynamic), Client — wanneer welke?
+
+| | Wanneer gerenderd | Data versheid | Externe API key veilig? | Wanneer kiezen |
+|---|---|---|---|---|
+| **Static** (build-time) | Tijdens `next build` | Bij elke build | Ja, server | Data verandert zelden (blog, marketing, lijst) |
+| **Server / Dynamic** (request-time) | Bij elke request | Altijd vers | Ja, server | Data per user/request anders, of telkens vers nodig |
+| **ISR** (revalidate) | Eerste keer + na X seconden | Periodiek vers | Ja, server | Sweet spot: snel + redelijk vers (Pokédex!) |
+| **Client** (`useEffect`) | In de browser na hydration | Live | **Nee** — sleutel staat in browser | User-interactie, live updates, geen geheim |
+
+**Code-verschil = één optie in `fetch`:**
```typescript
-// app/pokemon/[name]/page.tsx
-async function getPokemon(name: string) {
- const res = await fetch(
- `https://pokeapi.co/api/v2/pokemon/${name}`,
- { next: { revalidate: 3600 } } // cache 1 uur
- );
- if (!res.ok) throw new Error("Pokemon niet gevonden");
- return res.json();
-}
+// 1. STATIC — gecached forever (default in Next.js!)
+const res = await fetch(URL);
-export default async function PokemonPage({ params }) {
- const pokemon = await getPokemon(params.name);
- return (
-
-
{pokemon.name}
-

-
- );
-}
+// 2. ISR — cache met revalidate
+const res = await fetch(URL, { next: { revalidate: 3600 } });
+
+// 3. DYNAMIC — altijd vers
+const res = await fetch(URL, { cache: "no-store" });
```
-**Drie dingen om te weten:**
-1. **`async`** Server Component — fetch direct in component
-2. **`next: { revalidate }`** — Next.js cache opties
-3. **Error handling** — `!res.ok` netjes afvangen
+**Vandaag gebruiken we:** Static voor de Pokédex-lijst, ISR voor detail-pages.
---
@@ -139,27 +129,35 @@ export default async function PokemonPage({ params }) {
---
-## Slide 7: Vercel preview deploys
-### Een URL per branch — automatisch
+## Slide 7: Vercel — productie, preview, environment variables
+### Wat verandert er per omgeving?
-**Hoe het werkt:**
-1. Connect je GitHub repo aan Vercel (eenmalig)
-2. Push naar `main` → **productie** deploy op `je-app.vercel.app`
-3. Push naar elke andere branch → **preview** deploy op `je-app-git-{branch}-{user}.vercel.app`
-4. PR krijgt automatisch een comment met preview URL
+**Drie omgevingen, drie scopes:**
-**Wat krijg je:**
-- Stakeholders zien een nieuwe feature voor de merge
-- Cursor Background Agent maakt een PR → preview URL → klikbaar
-- Reviewer test live in browser, niet alleen code-diff
+| Scope | Wordt gedeployed bij | URL | Wie ziet het |
+|-------|---------------------|-----|--------------|
+| **Production** | Push naar `main` | `je-app.vercel.app` | Iedereen |
+| **Preview** | Push naar elke andere branch | `je-app-git-{branch}-{user}.vercel.app` | Iedereen met link (delen via PR) |
+| **Development** | Lokaal `pnpm dev` | `localhost:3000` | Alleen jij |
-**Environment scopes:**
+**Environment variables per scope:**
-| Scope | Wanneer | Voorbeeld var |
-|-------|---------|---------------|
-| **Production** | Deploys vanaf `main` | Echte API key |
-| **Preview** | Alle andere branches | Test API key |
-| **Development** | Lokaal (`.env.local`) | Dev API key |
+In Vercel → Project → Settings → Environment Variables zet je per var **drie keer** een waarde:
+
+| Var | Production | Preview | Development |
+|-----|------------|---------|-------------|
+| `DATABASE_URL` | prod database | staging database | lokale database |
+| `OPENAI_API_KEY` | echte key | test-key (lage limit) | jouw persoonlijke key |
+| `STRIPE_KEY` | `sk_live_...` | `sk_test_...` | `sk_test_...` |
+| `NEXT_PUBLIC_APP_URL` | je-app.com | preview-url | localhost:3000 |
+
+**Waarom is dit belangrijk?**
+- Preview branches mogen NIET stagecoach naar je productie-database schrijven
+- Test API-keys voorkomen kosten/quota-problemen bij experiments
+- Eén branch = één geïsoleerde wereld om in te testen
+
+**Cursor Background Agent + Vercel preview = killer combo:**
+Agent opent PR → preview URL automatisch in PR-comment → reviewer test live in browser, geen lokale setup nodig.
---
@@ -224,31 +222,33 @@ jobs:
---
-## Slide 10: LIVE DEMO 1 — Pokédex met Composer
+## Slide 10: LIVE DEMO 1 — Pokédex met Composer + rendering modes
### ~30 min
**Wat ik laat zien:**
1. `pnpm create next-app pokedex --typescript --tailwind --app`
2. Open in Cursor
-3. **Composer (`Cmd+I`)**: "Bouw een lijst-pagina met de eerste 151 Pokémon van PokéAPI, gebruik Tailwind cards"
-4. Composer maakt: `app/page.tsx` met fetch + map + cards
+3. **Composer (`Cmd+I`)**: "Bouw een lijst-pagina met de eerste 151 Pokémon van PokéAPI als statische pagina. Gebruik Tailwind cards. De fetch moet static zijn — geen revalidate."
+4. Composer maakt: `app/page.tsx` met `fetch(URL)` (static) + map + cards
5. `pnpm dev` — werkt
-6. **Composer**: "Voeg detail-pagina toe op /pokemon/[name] met sprite + types + stats"
-7. Composer maakt: `app/pokemon/[name]/page.tsx`
-8. Cards op homepage linken naar detail-pages
-9. Klein bugje fix-en in chat: "de cards zijn te smal"
+6. **Composer**: "Voeg detail-pagina toe op /pokemon/[name] met sprite + types + stats. Gebruik ISR met revalidate van 3600 seconden."
+7. Composer maakt: `app/pokemon/[name]/page.tsx` met `{ next: { revalidate: 3600 } }`
+8. **Bewijs in de browser:** open detail-pagina, refresh — direct geladen (statisch). Verander URL → andere Pokémon → ook snel (ISR cache).
+9. **Show de Network tab:** geen calls naar PokéAPI vanuit browser, alles server.
+10. **Bonus:** maak één pagina met `cache: "no-store"` (dynamic) zodat studenten alle drie de modi gezien hebben.
**Belangrijk om uit te leggen:**
- Composer is een TAB in Cursor — niet hetzelfde als Chat
- Multi-file edits zichtbaar als diffs — je accepteert per file
- Terminal commands vragen toestemming
+- De rendering-mode is één optie in `fetch()` — verder verandert je code niet
**Visual:** Cursor screenshot met Composer panel.
---
-## Slide 11: LIVE DEMO 2 — Deploy naar Vercel productie
-### ~15 min
+## Slide 11: LIVE DEMO 2 — Deploy naar Vercel + Environment variables
+### ~20 min
**Wat ik laat zien:**
1. **GitHub:** new repo `pokedex` — public
@@ -261,15 +261,24 @@ jobs:
git push -u origin main
```
3. **Vercel:** Add New → Import Git Repository → kies `pokedex` → Deploy
-4. Wachten 60-90s op build
-5. Open productie URL — werkt!
-6. **Wijziging:** title aanpassen, push naar main → 2e deploy, ~30s
+4. Wachten 60-90s op build → Productie URL — werkt!
+5. **Environment Variables demo:** voeg een var toe in `app/page.tsx`:
+ ```tsx
+ Omgeving: {process.env.NEXT_PUBLIC_APP_ENV}
+ ```
+6. **Vercel → Settings → Environment Variables:**
+ - `NEXT_PUBLIC_APP_ENV` = `production` → vinkje bij Production
+ - `NEXT_PUBLIC_APP_ENV` = `preview` → vinkje bij Preview
+ - Lokaal: `.env.local` → `NEXT_PUBLIC_APP_ENV=development`
+7. Redeploy → productie URL toont "production"
+8. Maak feature branch `chore/banner`, push → preview URL toont "preview"
+9. Lokaal `pnpm dev` → toont "development"
**Wat studenten moeten zien:**
-- One-time setup (Vercel verbinden)
-- Daarna: push to main = deploy
-- Build logs in Vercel dashboard
-- Custom domain mogelijk (skipping vandaag)
+- Push to main = productie deploy
+- Push to andere branch = preview deploy (eigen URL)
+- Per omgeving andere env-waarden
+- Voor secrets: vinkje **alleen** bij Production (preview-key gebruiken in preview!)
---
@@ -375,21 +384,18 @@ Beide branches kunnen tegelijk draaien.
**Vandaag gezien:**
- Externe API integratie in Server Components
+- Static / ISR / Dynamic / Client — vier rendering modes
- Cursor Composer voor synchrone changes
- Cursor Background Agent voor async features
- Vercel productie + preview per branch
+- Environment variables per omgeving (production / preview / development)
- GitHub Actions CI (lint + build)
-- Wanneer welke Cursor-modus
-**Volgende les (Les 16): MCP — Model Context Protocol**
-- Wat is MCP (Model Context Protocol)
-- Bestaande MCP servers gebruiken in Cursor/Claude Desktop
-- Eigen MCP server bouwen + laden
-- Demo: eigen tools beschikbaar in alle MCP-clients
-
-**Daarna in deze leerlijn:**
-- Les 17: Externe APIs in diepte (OAuth, webhooks, paid APIs)
-- Les 18: Supabase Auth + RLS — multi-user apps
+**Volgende les (Les 14): Agents**
+- LLM in een loop met tools, autonoom 20-50 stappen
+- `ToolLoopAgent`, `stopWhen`, `prepareStep`
+- Research-agent from scratch
+- Wanneer wel/niet agents gebruiken
**Vragen? Feedback?**
diff --git a/Les13-Cursor-Vercel-Deploy/Les13-Slides.pdf b/Les13-Cursor-Vercel-Deploy/Les13-Slides.pdf
index b9da99f..ec92408 100644
Binary files a/Les13-Cursor-Vercel-Deploy/Les13-Slides.pdf and b/Les13-Cursor-Vercel-Deploy/Les13-Slides.pdf differ
diff --git a/Les13-Cursor-Vercel-Deploy/Les13-Slides.pptx b/Les13-Cursor-Vercel-Deploy/Les13-Slides.pptx
index 2d5150f..2173dfc 100644
Binary files a/Les13-Cursor-Vercel-Deploy/Les13-Slides.pptx and b/Les13-Cursor-Vercel-Deploy/Les13-Slides.pptx differ
diff --git a/Les13-Cursor-Vercel-Deploy/lesbestanden/pokedex-finished.zip b/Les13-Cursor-Vercel-Deploy/lesbestanden/pokedex-finished.zip
new file mode 100644
index 0000000..4b4ee26
Binary files /dev/null and b/Les13-Cursor-Vercel-Deploy/lesbestanden/pokedex-finished.zip differ
diff --git a/Les13-Cursor-Vercel-Deploy/lesbestanden/pokedex-starter.zip b/Les13-Cursor-Vercel-Deploy/lesbestanden/pokedex-starter.zip
new file mode 100644
index 0000000..8613deb
Binary files /dev/null and b/Les13-Cursor-Vercel-Deploy/lesbestanden/pokedex-starter.zip differ
diff --git a/Les14-Agents/lesbestanden/research-agent-finished.zip b/Les14-Agents/lesbestanden/research-agent-finished.zip
new file mode 100644
index 0000000..29c620c
Binary files /dev/null and b/Les14-Agents/lesbestanden/research-agent-finished.zip differ
diff --git a/Les14-Agents/lesbestanden/research-agent-starter.zip b/Les14-Agents/lesbestanden/research-agent-starter.zip
new file mode 100644
index 0000000..e7c75b1
Binary files /dev/null and b/Les14-Agents/lesbestanden/research-agent-starter.zip differ
diff --git a/Les15-RAG-Embeddings/lesbestanden/rag-finished.zip b/Les15-RAG-Embeddings/lesbestanden/rag-finished.zip
new file mode 100644
index 0000000..b7e41b1
Binary files /dev/null and b/Les15-RAG-Embeddings/lesbestanden/rag-finished.zip differ
diff --git a/Les15-RAG-Embeddings/lesbestanden/rag-starter.zip b/Les15-RAG-Embeddings/lesbestanden/rag-starter.zip
new file mode 100644
index 0000000..f2b667e
Binary files /dev/null and b/Les15-RAG-Embeddings/lesbestanden/rag-starter.zip differ
diff --git a/Les16-MCP/lesbestanden/mcp-starter.zip b/Les16-MCP/lesbestanden/mcp-starter.zip
new file mode 100644
index 0000000..2ae68c3
Binary files /dev/null and b/Les16-MCP/lesbestanden/mcp-starter.zip differ
diff --git a/Les16-MCP/lesbestanden/polderfest-mcp-finished.zip b/Les16-MCP/lesbestanden/polderfest-mcp-finished.zip
new file mode 100644
index 0000000..fd4edef
Binary files /dev/null and b/Les16-MCP/lesbestanden/polderfest-mcp-finished.zip differ
diff --git a/Les17-Production-Polish/lesbestanden/polderfest-polish-finished.zip b/Les17-Production-Polish/lesbestanden/polderfest-polish-finished.zip
new file mode 100644
index 0000000..afe301f
Binary files /dev/null and b/Les17-Production-Polish/lesbestanden/polderfest-polish-finished.zip differ
diff --git a/Les17-Production-Polish/lesbestanden/polderfest-starter.zip b/Les17-Production-Polish/lesbestanden/polderfest-starter.zip
new file mode 100644
index 0000000..86b5ee7
Binary files /dev/null and b/Les17-Production-Polish/lesbestanden/polderfest-starter.zip differ
diff --git a/Les18-AI-Toolbox-Next/lesbestanden/ai-toolbox-5-demos.zip b/Les18-AI-Toolbox-Next/lesbestanden/ai-toolbox-5-demos.zip
new file mode 100644
index 0000000..2d5f1b8
Binary files /dev/null and b/Les18-AI-Toolbox-Next/lesbestanden/ai-toolbox-5-demos.zip differ