This commit is contained in:
2026-06-09 06:33:34 +02:00
parent 4f13aadc1a
commit dc284db9b6
172 changed files with 437 additions and 15151 deletions

View File

@@ -1,539 +1,455 @@
# Les 13 — Externe APIs + Cursor + Vercel
## Docenttekst (Klas A — 3 uur, fysiek, demo-driven)
# Les 13 — Docenttekst (Autocue)
**Les:** 14 van 18
**Onderwerp:** Externe APIs + Cursor agents (Composer + Background) + Vercel preview deploys + GitHub Actions CI
**Duur:** 180 minuten
**Format:** Tim demonstreert klassikaal. Studenten kijken mee. Zelf bouwen = huiswerk.
**Demo-app:** Pokédex (PokéAPI, geen key, visueel) — nieuwe kleine app
**Online les — letterlijk voorleesbaar. Slide-nummers staan tussen `[SLIDE N]` zodat je weet wanneer door te klikken.**
**Lesvorm:** online, demo-driven. Cursor + browser side-by-side delen.
**Duur:** 180 minuten met 1 pauze van 15 minuten.
> **Voor de les klaarzetten:**
> - Cursor open in lege werkmap
> - Browser tabs open: github.com, vercel.com, pokeapi.co
> - Terminal klaar
> - Cursor Pro/Business actief (voor Background Agent)
> - GitHub + Vercel ingelogd
> - Webcam check + scherm delen ingericht
---
## Hoe deze tekst werkt
- `[SLIDE X]` — klik naar slide X
- `[SCHERM: slides | cursor | terminal | browser | github | vercel]` — welk venster op de beamer
- **Vertel:** "..." — wat je zegt (mag eigen woorden)
- `*[stage direction]*` — instructie voor jezelf
- 💬 = verwachte studentenvraag
---
## VÓÓR DE LES — Setup (60 min)
### 1. Cursor versie checken
- Cursor moet `>=0.45` zijn voor Background Agents
- Pro account aktief (Tim's eigen) — Background Agents zitten daar in
- Test: `Cmd+Shift+P` → "Open Background Agent" zichtbaar in command palette
- Login bij Cursor cloud — eenmalige flow
### 2. Vercel + GitHub klaar
- Vercel account (Tim's eigen) — gekoppeld aan GitHub
- Test repo: maak vooraf 1 dummy-repo aan (om snel te kunnen demonstreren als demo crasht)
- Bookmark: vercel.com/dashboard, github.com/USER
### 3. Demo-app vooraf gemaakt (backup)
- Werkende `pokedex-demo` lokaal + op GitHub + op Vercel
- Als live coding faalt: switch hier naartoe, vertel "dit is hoe het er eindelijk uitziet"
### 4. Folder & terminal-tabs
- Werk-folder: `~/novi/novi-lessons/Les13-Cursor-Vercel-Deploy/live`
- 3 terminal tabs: project root, git-watcher (`watch git status`), Vercel CLI
- Cursor open met empty folder klaar
- Browser tabs vooraf open:
- https://pokeapi.co/
- https://vercel.com/dashboard
- https://github.com (jouw user)
- https://docs.cursor.com/background-agent (referentie)
### 5. Internet check
- Background Agents werken alleen met goede verbinding
- Vercel deploys ook
- Backup hotspot indien WiFi gisteren rare dingen deed
### 6. Backup screenshots
Heb klaar voor als demo faalt:
- Cursor Background Agent UI (run completed)
- GitHub PR met Vercel comment
- Vercel preview deploy
- GitHub Actions groene check
- Branch protection settings page
---
# HET SCRIPT — Lees mee tijdens de les
## BLOK 1 — Welkom + Terugblik (10 min)
`[SLIDE 1 — Title]` `[SCHERM: slides]`
`[SLIDE 1]`
**Vertel:** "Welkom bij les 14. Vandaag gaan we van localhost de wereld in. Externe APIs, Vercel, Cursor's coding agents, GitHub Actions CI. Heel hands-on."
Welkom bij Les 13. Vandaag is een belangrijke les, want we verlaten voor het eerst je laptop.
`[SLIDE 2 — Terugblik]`
Tot nu toe draaiden al jullie projecten op `localhost:3000`. Dat is prima om te leren, maar in de praktijk wil je dat anderen je werk kunnen zien. Klanten. Collega's. Werkgevers. Vandaag zetten we die stap.
**Vertel:** "Lessen 11 t/m 13 gingen over AI: SDK, tools, agents. Alles draaide op jouw laptop. Dat was prima om te leren, maar in het echt deploy je iets en wil je dat anderen het kunnen gebruiken.
We gaan drie dingen leren die je voor elk professioneel project nodig hebt. Eén: data ophalen van externe websites en APIs. Twee: je code automatisch online zetten via Vercel. Drie: efficiënt werken met Cursor agents.
Vandaag drie dingen tegelijk. Een — externe APIs aanroepen. Geen LLM, gewoon data ophalen. Twee — die app naar Vercel. Drie — Cursor's twee agent-modes leren kennen en daarmee feature branches openen die automatisch preview-URLs krijgen op Vercel."
Aan het einde van deze les heb je een live URL die je aan iedereen kunt delen.
`[SLIDE 3 — Planning]`
`[SLIDE 2]`
**Vertel:** "Drie uur. Eerst 45 minuten theorie verdeeld over de drie onderwerpen. Daarna vier demos. Pauze rond minuut 90. Tot slot lesopdracht en huiswerk."
Even kort terug. In de afgelopen lessen hebben we gewerkt aan AI-features. Les 11 was de Vercel AI SDK, Les 12 was Tool Calling. Allemaal mooi, maar steeds lokaal.
Vandaag stappen we even uit dat AI-pad. Geen LLM's. Geen API keys voor AI. We gaan terug naar de basis: hoe ziet een professionele Next.js-workflow eruit van localhost tot productie?
We gebruiken een eenvoudige Pokédex als demo-app. Waarom Pokémon? Omdat de PokéAPI gratis is, geen API key vraagt, en je krijgt direct visuele resultaten. Perfect om je te focussen op de workflow, niet op de data.
`[SLIDE 3]`
Dit is de planning voor vandaag. Honderdtachtig minuten.
We beginnen met theorie. Drie blokken van een kwartier: externe APIs in Next.js, Vercel met preview-omgevingen, en Cursor agents.
Dan vier live demo's. In demo één bouwen we de Pokédex met Composer. In demo twee deployen we naar Vercel en zetten we environment variables in. Na de pauze: demo drie met de Background Agent om een feature parallel te bouwen. Demo vier: GitHub Actions om CI in te richten.
Na elke demo bouw je het zelf na in je eigen repo. Vragen tussendoor altijd welkom — typ ze in de chat.
---
## BLOK 2 — Theorie Externe APIs (15 min)
## BLOK 2 — Theorie: Externe APIs (15 min)
`[SLIDE 4 — Wat is een externe API]`
`[SLIDE 4]`
**Vertel:** "Een externe API is een HTTP-endpoint van iemand anders. Je doet `fetch()`, je krijgt JSON. Klaar. Dat is het idee.
Wat is een externe API. In simpele woorden: andere mensen of bedrijven hebben data of functionaliteit, en die stellen ze beschikbaar via HTTP. Jij doet een fetch-request, krijgt JSON terug, en kunt daar dingen mee doen.
Drie smaken qua authenticatie. Geen key — PokéAPI en Open-Meteo bijvoorbeeld. Met key — Tavily, TMDB, de meeste paid APIs. OAuth — Google, GitHub user-data, dat is complexer.
Een paar voorbeelden van APIs die je in projecten zult tegenkomen.
In Next.js fetch je op twee plekken. Server-side, in een Server Component of API route. Of client-side, met `useEffect`. Server-side is bijna altijd beter voor initial data. Je key blijft veilig, het is gecached, en sneller."
PokéAPI — onze demo van vandaag. Pokemon-data, geen key nodig.
`[SLIDE 5 — Code]`
Open-Meteo — gratis weerservice, ook geen key.
**Vertel:** "Code voorbeeld. Server Component is `async`. Doe `fetch`, parse als JSON, return. Met `next: { revalidate: 3600 }` zegt je tegen Next.js: cache dit een uur. Eerste user triggert de fetch, de volgende honderd users krijgen de gecachte versie. Schaalbaar zonder dat je iets hoeft te doen.
GitHub API — informatie over repositories, gebruikers, issues.
💬 *Vraag: 'Wanneer client-side?'*
Tavily — web search. Die heeft wél een API key nodig.
**Antwoord:** "Bij user-interactie. Een zoekbalk waar je live filtert — moet client-side, want de input verandert in de browser. Real-time updates, polling — client-side. Maar de eerste page-load: server-side."
Stripe — betalingen. Twee keys: een publishable en een secret.
Voor APIs met een key onthoud je één regel: nooit, ooit, in je code committen. Altijd in environment variables. Hier komen we straks bij Vercel uitgebreid op terug.
`[SLIDE 5]`
Dit is misschien wel de belangrijkste slide van vandaag. Drie manieren om een API te fetchen in Next.js. Static, server, of client. Welke je kiest hangt af van je use case.
Eerste optie: **static**. De fetch gebeurt tijdens `next build`. Eén keer, en daarna is het HTML. Snel, want geen server-werk per request. Goed voor data die zelden verandert: een blog-post, een marketing-pagina, een lijst van producten.
Tweede optie: **server, oftewel dynamic**. De fetch gebeurt bij elke request opnieuw. Altijd vers, maar trager. Goed voor data die per gebruiker anders is, of die altijd up-to-date moet zijn.
Derde optie: **ISR**, Incremental Static Regeneration. Een mix van de twee. Wordt gecached zoals static, maar elke X seconden mag Next.js de cache vervangen. Het beste van beide werelden. Voor onze Pokédex de juiste keuze voor de detail-pagina's: snel, maar elk uur kijkt Next.js of de PokéAPI iets nieuws heeft.
Vierde optie: **client-side fetching**, met useEffect. Dit doe je alleen als de data live moet, of als je user-interactie nodig hebt. Belangrijk: bij client-side staat je API key in de browser. Dus alleen voor publieke APIs of voor data die geen geheim is.
Kijk naar het code-blokje onderaan. Het verschil tussen die drie server-modes is letterlijk één optie in je fetch-call. Verder verandert je code helemaal niet. Dat is de kracht van Next.js: je kiest het render-gedrag per fetch.
Vandaag gebruiken we static voor de Pokédex-lijst en ISR voor de detail-pages.
---
## BLOK 3 — Theorie Cursor Agents (15 min)
## BLOK 3 — Theorie: Vercel + Cursor + GitHub (15 min)
`[SLIDE 6 — Composer vs Background]`
`[SLIDE 6]`
**Vertel:** "Cursor heeft TWEE soorten agents. Niet hetzelfde. Niet uitwisselbaar. Vandaag gaan we beide doen.
Korte sidetrack — straks komen we terug op Vercel. Even Cursor: twee soorten agents.
Composer. `Cmd+I`. Lokaal in je editor. Jij wacht, agent werkt, je ziet de diffs verschijnen. Je accepteert per file. Synchroon — je bent erbij. Dit is pair programming.
Composer is de agent die je al kent. Cmd+I openen, prompt typen, hij maakt aanpassingen in je editor. Synchroon. Jij wacht, hij werkt, jij kijkt, je accepteert de diff. Pair programming.
Background Agent. `Cmd+Shift+P` → 'Open Background Agent'. Runt in Cursor's cloud, niet jouw machine. Je geeft een prompt, gaat iets anders doen, komt terug. De agent maakt een branch, doet commits, opent een PR op GitHub. Asynchroon — je hoeft er niet bij te zijn.
Background Agent is anders. Die draait in Cursor's cloud. Je geeft een opdracht, hij gaat aan de slag, hij maakt een branch, hij pusht naar GitHub, hij opent een Pull Request. Allemaal terwijl jij iets anders doet. Asynchroon dus.
Het verschil zit in waar je bent in je workflow. Composer voor exploreren, voor pair-programming, voor 'ik wil even meekijken'. Background Agent voor 'ik weet wat ik wil, voer maar uit terwijl ik vergader'."
Het verschil komt straks duidelijk in de demo. Voor nu: onthoud dat je twee modes hebt. Eén voor naast je, één voor namens je.
💬 *Vraag: 'Kost Background Agent geld?'*
`[SLIDE 7]`
**Antwoord:** "Ja, zit in Cursor Pro. Twintig dollar per maand. Hobby-tier — gratis Cursor — heeft alleen Composer. Voor de huiswerk-opdracht: trial van Pro werkt prima, of vraag credits via Cursor support voor studenten."
Nu Vercel. Dit is het hart van de les. Drie omgevingen, drie scopes — en het is cruciaal dat je dit begrijpt.
💬 *Vraag: 'Hoe vergelijkt dit met Claude Code of OpenCode?'*
Productie is de live versie. Wordt gedeployed elke keer dat je naar de `main` branch pusht. URL is `je-app.vercel.app`. Dit ziet iedereen.
**Antwoord:** "Allemaal coding agents, allemaal goed. Cursor's Background Agent is uniek in dat-ie autonoom een PR opent — Claude Code en OpenCode draaien lokaal en doen geen PR-flow zelf. Het hangt af van je workflow. We zien beide in deze les."
Preview is wat je krijgt voor élke andere branch. Push naar `feature/search` en je krijgt automatisch een URL terug. Iets als `je-app-git-feature-search-jouwnaam.vercel.app`. Ook publiek bereikbaar, maar alleen mensen met de link.
Development is je lokale `pnpm dev` op localhost. Alleen jij ziet dat.
Nu de belangrijke vraag: hoe ga je daar mee om als je verschillende databases, of verschillende API keys hebt per omgeving? Want dat wil je. Je wilt absoluut niet dat een experimentele preview-branch in je productie-database gaat schrijven.
Bekijk de tabel met environment variables. In Vercel ga je naar Project, Settings, Environment Variables. En daar zet je per variabele drie keer een waarde. Voor productie. Voor preview. En voor development.
Een paar voorbeelden. Je `DATABASE_URL` is voor productie je echte database. Voor preview een staging-database. Voor development je lokale Postgres. Drie totaal verschillende waarden, één variabele-naam.
Je `OPENAI_API_KEY` is in productie de echte key met hoge limit. In preview een test-key zodat experimenten je niet failliet maken. In development je persoonlijke key.
Voor Stripe heel concreet: productie is `sk_live_...`, preview en development zijn allebei `sk_test_...`. Een experiment dat per ongeluk een betaling doet komt nooit op een echte creditcard.
Onderaan staat één killer-combo: Cursor Background Agent maakt een Pull Request, Vercel ziet die PR en bouwt automatisch een preview URL, die URL komt automatisch in een comment op de PR. Reviewer klikt, opent in de browser, test live. Geen lokale setup nodig. Dit is voor mij persoonlijk een van de redenen dat ik altijd met Vercel werk.
`[SLIDE 8]`
GitHub Actions, snel. Waarom: voordat je iets merget, weet je zeker dat de code lint en bouwt op een schone machine. Niet "het werkt op mijn laptop", maar "het werkt op een verse ubuntu". Belangrijk omdat Background Agents soms lint-errors maken — CI vangt dat op.
Kijk naar het code-blok. Een minimale workflow. Bij elke pull request en bij elke push naar main: checkout, pnpm install, pnpm lint, pnpm build. Duurt ongeveer twee minuten op een kleine app.
In demo vier gaan we dit bouwen.
---
## BLOK 4 — Theorie Vercel + CI (15 min)
## BLOK 4 — Wat we vandaag bouwen (5 min)
`[SLIDE 7 — Vercel preview deploys]`
`[SLIDE 9]`
**Vertel:** "Vercel is hosting platform van de Next.js-makers. Voor Next.js apps de simpelste deploy ter wereld. Push naar GitHub, een minuut later: live.
Dit is wat we vandaag bouwen. Een Pokédex.
Het magische zit in **preview deploys**. Elke branch krijgt automatisch een eigen URL. Niet alleen `main` — letterlijk elke. Push naar `feature/search``je-app-git-feature-search-user.vercel.app` is live. PR's krijgen een comment met de URL.
De stack: Next.js 16 met TypeScript en Tailwind. Geen Shadcn vandaag, we houden het simpel. PokéAPI als data-bron, geen API key. Cursor voor de code. GitHub voor de versie-controle. Vercel voor de deploys.
Waarom is dit zo krachtig? Background Agent maakt een PR — preview URL is direct klikbaar. Reviewer test de feature live, niet alleen code-diff. Stakeholders kunnen iets zien. Geen 'werkt op mijn laptop'-discussies meer.
Vier features die we bouwen. Een lijst met de eerste 151 Pokémon. Detail-pages per Pokémon. Een zoek-functie. Type-filter chips.
Environments — drie scopes. Production = main. Preview = alle andere branches. Development = lokaal. Per env-variabele kies je waar-ie beschikbaar is."
De eerste twee bouwen we met Composer, op de `main` branch. De andere twee met de Background Agent op feature-branches.
`[SLIDE 8 — GitHub Actions CI]`
**Vertel:** "Last theory bit. GitHub Actions. Een script dat draait wanneer er iets gebeurt — push, PR, merge.
Minimaal voor dit vak: lint en build per PR. Eerst tien regels YAML, daarna heb je voor elke PR: lint draait, TypeScript build slaagt, anders rode X.
Waarom belangrijk? Background Agent kan typo's maken. CI vangt dat op voor je merget. Plus: 'werkte op mijn laptop' bestaat niet meer."
Aan het einde heb je: een productie-URL, twee preview-URL's, drie Pull Requests, een werkende CI-pipeline, en een geverifieerd begrip van static versus ISR.
---
## BLOK 5 — Demo 1: Pokédex met Composer (30 min)
## BLOK 5 — LIVE DEMO 1: Pokédex met Composer (30 min)
`[SLIDE 9 — Wat we bouwen]`
`[SLIDE 10]`
**Vertel:** "Tijd voor demo. We bouwen vandaag een Pokédex. PokéAPI heeft data van duizend Pokémon, geen key nodig, gratis. Vier features in totaal — twee met Composer nu, twee met Background Agent na de pauze."
Tijd voor de eerste demo. Open Cursor, lege werkmap, terminal openen.
`[SLIDE 10 — DEMO 1]` `[SCHERM: terminal + cursor]`
**Vertel:** "Klas, kijk mee. Vragen tussendoor mogen."
`*[Terminal:]*`
Eerste commando, typ mee:
```bash
cd ~/novi/novi-lessons/Les13-Cursor-Vercel-Deploy
pnpm create next-app@latest pokedex \
--typescript --tailwind --app --no-src-dir --import-alias "@/*"
cd pokedex
cursor .
pnpm create next-app pokedex --typescript --tailwind --app
```
`*[Cursor opens]*`
Beantwoord de vragen: geen src directory, gebruik Turbopack, default alias is prima.
**Vertel:** "Cursor open. Nu Composer. Op Mac: `Cmd+I`. Op Windows: `Ctrl+I`."
Stap in de map: `cd pokedex` en open in Cursor met `cursor .` of File → Open Folder.
`*[Cmd+I — Composer panel opens]*`
Nu het magische moment. Druk op Cmd+I — of Ctrl+I op Windows — voor Composer. Type deze prompt letterlijk over:
**Vertel:** "Composer is een tab in Cursor — niet hetzelfde als de gewone Chat. Composer kan meerdere files tegelijk wijzigen. Chat alleen één antwoord per keer."
> Bouw een lijst-pagina met de eerste 151 Pokémon van PokéAPI. URL is `https://pokeapi.co/api/v2/pokemon?limit=151`. Gebruik Tailwind cards in een grid met de naam en sprite van elke Pokémon. De sprite staat op `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png` waar {id} het index-nummer is. Gebruik een static fetch — dus géén revalidate optie meegeven.
`*[Type in Composer:]*`
Composer gaat aan de slag. Je ziet diffs verschijnen in `app/page.tsx`. Lees ze met me mee. Accepteer alle wijzigingen.
> Bouw een Pokédex-startpagina op `app/page.tsx`. Haal de eerste 151 Pokémon op van `https://pokeapi.co/api/v2/pokemon?limit=151` als async Server Component. Toon in een grid van cards (Tailwind, 4 kolommen op desktop, 2 op tablet, 1 op mobile). Elke card: naam (capitalized), sprite van `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png`, en het Pokemon nummer (#001). Hover effect: card lichtjes vergroten en schaduw. Gebruik `next/image`. Voeg PokéAPI's sprite domain toe aan `next.config.ts`.
**Vertel:** "Klik 'Submit'. Composer denkt na. Je ziet diffs verschijnen — `app/page.tsx`, eventueel `next.config.ts`."
`*[Accept all diffs]*`
Start lokaal:
```bash
pnpm dev
```
`*[Open browser → localhost:3000]*`
Open `localhost:3000`. Daar zou nu een grid van 151 Pokémon moeten staan. Werkt het? Mooi.
**Vertel:** "Daar staan onze 151 Pokémon. Werkt out of the box."
Tweede prompt in Composer:
💬 *Vraag: 'Hoe weet Composer welke files te wijzigen?'*
> Voeg een detail-pagina toe op `/pokemon/[id]`. Fetch van `https://pokeapi.co/api/v2/pokemon/{id}`. Toon de official artwork — die staat in `sprites.other["official-artwork"].front_default`. Toon ook de types als chips. Gebruik ISR met `revalidate: 3600`. Zorg dat de cards op de homepage linken naar `/pokemon/[id]`.
**Antwoord:** "Cursor heeft toegang tot je hele workspace. Composer ziet de file-structure, package.json, alles. Hij beslist zelf welke files relevant zijn voor de prompt. Soms vraagt hij om bevestiging — vooral bij nieuwe files."
Wacht op de diffs. Accepteer ze. Open een random Pokémon. Werkt? Goed.
`*[Tweede Composer prompt:]*`
Belangrijke uitleg terwijl studenten kijken:
> Voeg detail-pagina toe op `app/pokemon/[name]/page.tsx`. Toon: grote sprite van `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/{id}.png`, de naam, types als gekleurde chips (gebruik officiële Pokemon type colors zoals fire = red, water = blue, etc), en stats (HP, Attack, Defense, Sp.Atk, Sp.Def, Speed) als progress bars die de waarde tonen. Voeg height + weight onderaan. Link de homepage-cards naar `/pokemon/{name}`.
Kijk naar het verschil tussen `app/page.tsx` en `app/pokemon/[id]/page.tsx`. De homepage gebruikt gewoon `fetch(URL)`. De detail-pagina gebruikt `fetch(URL, { next: { revalidate: 3600 } })`. Dat is het hele verschil tussen static en ISR. Eén optie.
`*[Accept diffs, refresh browser, klik op een card]*`
Open de browser Network tab — F12. Klik door wat detail-pages. Je ziet dat er geen calls vanuit de browser naar PokéAPI gaan. Alles is server-side. Dat betekent: als PokéAPI morgen omvalt, blijft jouw site werken tot de revalidate triggert.
**Vertel:** "Detail-pagina werkt. Stats als balkjes. Types met kleurtjes. Composer heeft type-colors zelf gevonden — die zitten in zijn training data."
Optioneel — en als de tijd het toelaat — voeg een derde route toe met `cache: "no-store"`. Bijvoorbeeld een "random pokemon" pagina. Zo zien studenten alle drie de modes naast elkaar.
`*[Optioneel — als tijd:]*`
Commit alles in de terminal:
> De stats balkjes zijn te dun en de kleur is wat saai. Maak ze dikker en gebruik een paarse gradient.
**Vertel:** "Composer kan ook polish doen. Twee minuten later: betere UI. Dit is wat ik bedoelde met 'pair programming' — snel iteraten op visuals."
```bash
git add . && git commit -m "Initial Pokédex met Composer"
```
---
## BLOK 6 — Demo 2: Vercel deploy (15 min)
## BLOK 6 — LIVE DEMO 2: Vercel deploy + env vars (20 min)
`[SLIDE 11 — DEMO 2]` `[SCHERM: terminal + github + vercel]`
`[SLIDE 11]`
**Vertel:** "App werkt lokaal. Nu de wereld in."
Tijd om dit live te zetten.
`*[Terminal:]*`
Open github.com in een nieuwe tab. Klik "New repository". Naam `pokedex`. Public. Geen README, geen gitignore, geen license — die hebben we al lokaal.
Klik Create. GitHub laat een URL zien. Kopieer die.
Terug in de terminal:
```bash
git init
git add .
git commit -m "Initial Pokédex with Composer"
git branch -M main
```
`*[Browser → github.com/new]*`
**Vertel:** "Nieuwe repo. Naam: pokedex. Public — geen secrets in onze code dus public mag. Géén README, géén license — Next.js heeft alles al."
`*[Klik 'Create repository']*`
```bash
git remote add origin https://github.com/JOUW-USER/pokedex.git
git remote add origin https://github.com/JOUWNAAM/pokedex.git
git push -u origin main
```
`*[Refresh GitHub — code is daar]*`
Refresh github.com. Daar staat je code.
`*[Browser → vercel.com/dashboard]*` `[SCHERM: vercel]`
Open vercel.com. Log in met GitHub. Klik "Add New" en kies "Project". Vercel toont een lijst met je repositories — kies `pokedex`. Klik "Deploy".
**Vertel:** "Vercel. Add New → Project. Import Git Repository. Kies pokedex."
Wacht zestig tot negentig seconden. Je ziet de build logs voorbij komen. Aan het einde: live URL. Klik. Daar staat je Pokédex op het internet. Live.
`*[Op import-screen — alle defaults laten staan]*`
Nu het belangrijkste stuk: environment variables.
**Vertel:** "Vercel detecteert Next.js automatisch. Build command: `pnpm build`. Output directory: `.next`. Niks aanpassen. Klik Deploy."
Open `app/page.tsx` lokaal in Cursor. Voeg ergens bovenaan in de return een regel toe:
`*[Wachten ~60-90s]*`
**Vertel:** "Tijdens het builden: dit is wat er gebeurt. Vercel pulled je code van GitHub, install dependencies, runt `pnpm build`. Output gaat naar hun CDN. Klaar.
`*[Build done — klik op URL]*`
**Vertel:** "Daar staat-ie. Productie URL. Live."
`*[Demonstreer: open URL op je telefoon — werkt]*`
**Vertel:** "Werkt op telefoon. Werkt overal. Internet."
`*[Terug naar Cursor — kleine wijziging:]*`
`*[Composer:]*` "Verander de h1 van 'Pokédex' naar 'Mijn Pokédex (Live!)'"
```bash
git add . && git commit -m "Update title" && git push
```tsx
<p className="mb-4 text-sm text-zinc-500">
Omgeving: {process.env.NEXT_PUBLIC_APP_ENV ?? "onbekend"}
</p>
```
`*[Browser → Vercel dashboard]*`
Push naar main:
**Vertel:** "Nieuwe deploy verschijnt direct. Wacht 30 seconden..."
```bash
git add . && git commit -m "Toon huidige omgeving"
git push
```
`*[Refresh productie URL]*`
Vercel deployt automatisch opnieuw. Maar de regel toont "onbekend" — omdat we de variabele nog niet hebben ingesteld.
**Vertel:** "Nieuwe titel zichtbaar. Geen extra commando. Push naar main = deploy."
Ga in Vercel naar je project → Settings → Environment Variables. Klik "Add new".
💬 *Vraag: 'En als de build faalt?'*
Variable name: `NEXT_PUBLIC_APP_ENV`. Value: `production`. **Belangrijk:** vink ALLEEN Production aan. Klik Save.
**Antwoord:** "Productie blijft op de laatste werkende versie. Vercel deployt alleen als build slaagt. Je krijgt mail dat de build mislukte, productie blijft online. Geen downtime."
Tweede keer toevoegen. Zelfde naam, value `preview`, en vink ALLEEN Preview aan. Save.
Ga naar de Deployments tab en redeploy de laatste deploy. Of push een kleine wijziging om automatisch te triggeren. Wacht. Open productie-URL — staat nu "production".
Nu een feature-branch. Terug in Cursor terminal:
```bash
git checkout -b chore/banner
# kleine wijziging — bv een spatie ergens
git add . && git commit -m "Banner aanpassing"
git push -u origin chore/banner
```
Open de Vercel deployments tab. Daar verschijnt een nieuwe deployment voor `chore/banner`. Klik de preview URL. Daar zie je "preview" staan, niet "production". Twee URL's, twee waarden, één variabele.
Voor `.env.local` zou je `NEXT_PUBLIC_APP_ENV=development` zetten — dat doen ze in het huiswerk.
Belangrijke regel: voor secrets — echte API keys, database passwords — vink je in Vercel **alleen** Production aan. In Preview gebruik je een aparte test-key. Dit voorkomt dat experimenten je productie-data raken.
---
## BLOK 7 — Pauze (15 min)
`[SLIDE 12 — Pauze]`
`[SLIDE 12]`
**Vertel:** "Pauze. Vijftien minuten."
`*[Tijdens pauze: open Cursor Background Agent flow alvast — zorg dat repo geconnect is]*`
Vijftien minuten pauze. Ik mute mijn microfoon en zet de camera uit. Tot zo.
---
## BLOK 8 — Demo 3: Background Agent (25 min)
## BLOK 8 — LIVE DEMO 3: Background Agent + preview (25 min)
`[SLIDE 13 — DEMO 3]` `[SCHERM: cursor + github + vercel]`
`[SLIDE 13]`
**Vertel:** "Nu de Background Agent. Dit is écht anders. Geen pair-programming meer — we delegeren."
Welkom terug. Nu het stuk wat veel mensen voor het eerst zien: de Cursor Background Agent.
`*[Cursor: Cmd+Shift+P]*`
Open Cursor. Druk Cmd+Shift+P voor het command palette. Type "Background Agent". Eerste keer: Cursor vraagt of je je repository wilt verbinden aan de cloud. Accepteer in de browser, autoriseer GitHub.
**Vertel:** "Cmd+Shift+P opent command palette. Type 'Background Agent'."
Nu kun je een Background Agent starten. Type deze prompt — letterlijk over:
`*[Select 'Open Background Agent']*`
> Maak een nieuwe branch genaamd `feature/search`. Voeg bovenaan de homepage een zoekbalk toe. Wanneer iemand typt, filter dan de Pokémon-lijst op naam. Gebruik client-side state met useState. Push de branch naar GitHub en open een Pull Request met titel "Add search bar" en een korte beschrijving in de body.
**Vertel:** "Eerste keer: Cursor vraagt verbinding met je repo. Volg de flow — opent browser, OAuth met GitHub, Cursor mag in jouw cloud sandbox draaien."
Klik start. De agent gaat aan de slag.
`*[Connect flow]*`
Wat je nu kunt doen — en dit is het hele punt: iets anders doen. Een tweede Background Agent starten met een andere taak.
**Vertel:** "Klaar. Nu de prompt. Belangrijk: SPECIFIEK zijn. De agent ziet ons niet. Hij krijgt alleen de prompt."
Nieuwe Background Agent, prompt:
`*[Schrijf:]*`
> Maak branch `feature/type-filter`. Op de homepage, naast de zoekbalk: chips om te filteren op type. Type-data moet je per Pokémon fetchen via de detail-endpoint. Push de branch en open een PR.
> Maak een nieuwe branch `feature/search`. Voeg een controlled input toe bovenaan `app/page.tsx`. Wanneer gebruiker typt, filter de Pokémon-lijst case-insensitive op naam. Houd Tailwind-styling consistent met bestaande cards (rounded corners, padding p-4). Voeg een count toe rechtsboven: "X van 151 zichtbaar". Push de branch en open een PR met titel "Add search bar". PR body: korte beschrijving + screenshot suggestie.
Beide agents lopen nu parallel. Klik op de eerste in het Background panel — je ziet wat hij doet. Bij vragen kan hij je stoppen voor input.
`*[Submit]*`
Wacht drie tot vijf minuten. Pull-Requests verschijnen op GitHub.
**Vertel:** "Agent draait nu. Cursor toont je een panel waarin je kunt zien wat-ie doet — bestanden inlezen, edits maken, terminal commands."
Open de eerste PR — "Add search bar". Onderaan zie je een comment van Vercel met de preview URL. Klik. Test de zoekbalk live in de browser. Werkt? Goed.
`*[Wachten ~3-5 minuten. Tijdens wachten:]*`
Open de code-diff in de GitHub UI. Lees mee. De agent heeft toegevoegd: een useState voor de query, een input bovenaan, een filter op de lijst. Logisch.
**Vertel:** "Belangrijk om uit te leggen — terwijl deze agent draait, ben ik vrij om iets anders te doen. Lokaal coden, Composer gebruiken voor andere features, koffie halen. Dat is het voordeel.
Als de code goed is, klik "Merge pull request". Push gaat naar `main`. Vercel deployt naar productie. Binnen een minuut staat de zoekbalk live.
Andere relevante punten. Agent draait in Cursor's sandbox — niet jouw laptop. Hij heeft toegang tot je repo, kan `pnpm install` doen, kan tests draaien, kan PR's openen. Maar hij heeft GEEN toegang tot jouw productie-secrets of services. Sandbox is geïsoleerd."
Hetzelfde voor de tweede PR. Review, test op preview-URL, merge.
`*[Agent done — PR verschijnt op GitHub]*` `[SCHERM: github]`
**Vertel:** "Pull request op GitHub. Klik."
`*[PR open]*`
**Vertel:** "Vercel heeft een comment achtergelaten — preview URL. Klik."
`*[Browser → Vercel preview URL]*`
**Vertel:** "Live, op een ANDERE URL dan productie. Test zoekbalk."
`*[Type 'pika' — Pikachu blijft over]*`
**Vertel:** "Werkt. Search live op preview URL. Productie ongewijzigd."
`*[Terug naar GitHub PR — Files Changed tab]*`
**Vertel:** "Code-review. Agent heeft `app/page.tsx` aangepast, eventueel een nieuwe component gemaakt. Lees diff, check of het ok is."
`*[Eventueel: comment in PR]*`
**Vertel:** "Stel ik wil een wijziging — bv. 'voeg ook een clear-button toe'. Twee opties. Een: open de branch lokaal, Composer fix. Twee: nieuwe Background Agent prompt op deze branch. Voor demo doen we optie twee."
`*[Cursor → Background Agent → existing or new]*`
> Op de bestaande branch feature/search: voeg een 'X' clear-button toe rechts in de zoekinput die de input leegmaakt.
`*[Wacht 2-3 min, refresh PR, refresh preview URL]*`
**Vertel:** "Update zichtbaar. Klik X — input leeg. Werkt."
`*[Eventueel: merge]*`
**Vertel:** "Tevreden? Merge PR. Vercel productie-deploy start automatisch. Een minuut later: search live op productie."
💬 *Vraag: 'Wat als agent vastloopt?'*
**Antwoord:** "Twee opties. Pakje sluiten en opnieuw beginnen met betere prompt. Of: open de branch lokaal, kijk wat agent gedaan heeft, fix met Composer. De agent is geen blackbox — hij commit echt op een branch, je kunt erin kijken."
Wat hier net gebeurde: in vijf minuten zijn er twee features autonoom gebouwd door agents, getest op aparte preview-URLs, en gemerged naar productie. Zonder dat ik één regel code zelf heb geschreven.
---
## BLOK 9 — Demo 4: GitHub Actions CI (15 min)
## BLOK 9 — LIVE DEMO 4: GitHub Actions CI (15 min)
`[SLIDE 14 — DEMO 4]` `[SCHERM: cursor + github]`
`[SLIDE 14]`
**Vertel:** "Laatste demo. CI. Eén check per PR — lint + build."
Laatste demo. We voegen CI toe. Lint en build moeten slagen voor elke PR.
`*[Cursor → Composer]*`
Open Composer in Cursor, Cmd+I, prompt:
> Voeg een GitHub Actions workflow toe: `.github/workflows/ci.yml`. Runt op pull_request en push naar main. Steps: checkout, install pnpm 9, setup node 20 met pnpm cache, install dependencies met frozen lockfile, run lint, run build.
> Voeg een GitHub Actions workflow toe op `.github/workflows/ci.yml`. Bij pull request én bij push naar main: checkout, setup pnpm met `pnpm/action-setup@v4`, setup node 22 met `actions/setup-node@v4` en `cache: pnpm`, install, run lint, run build.
`*[Accept diffs]*`
Composer maakt het bestand. Accepteer.
`*[Terminal:]*`
Push de wijziging:
```bash
git checkout -b chore/add-ci
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git add . && git commit -m "Add GitHub Actions CI"
git push -u origin chore/add-ci
```
`*[GitHub → openen PR]*` `[SCHERM: github]`
Open de PR op GitHub. Onder de PR zie je de "Checks" tab — de workflow draait. Wacht ongeveer twee minuten. Groene vinkjes — goed.
**Vertel:** "PR open. Klik tab 'Actions' — workflow draait. ~1-2 min."
Nu de demo van een falende CI. Introduceer een typo in de code. Open `app/page.tsx` lokaal, verander een opening tag in iets fouts — bijvoorbeeld `<dvi>` in plaats van `<div>`. 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 | 010 | 10 min |
| 2. Theorie Externe APIs | 1025 | 15 min |
| 3. Theorie Vercel + GH | 2540 | 15 min |
| 4. Wat we bouwen | 4045 | 5 min |
| 5. Demo 1 — Composer | 4575 | 30 min |
| 6. Demo 2 — Vercel + env | 7595 | 20 min |
| 7. Pauze | 95110 | 15 min |
| 8. Demo 3 — Background Agent | 110135 | 25 min |
| 9. Demo 4 — GitHub Actions | 135150 | 15 min |
| 10. Composer vs Background | 150160 | 10 min |
| 11. Lesopdracht + Huiswerk | 160170 | 10 min |
| 12. Afsluiting | 170175 | 5 min |
| Buffer voor vragen | 175180 | 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.

View File

@@ -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
[<d9949b7f201730e155f883ddc79dfc36><d9949b7f201730e155f883ddc79dfc36>]
[<9e0017c2fae42cc449cacedd8dc86476><9e0017c2fae42cc449cacedd8dc86476>]
% ReportLab generated PDF document -- digest (opensource)
/Info 13 0 R

View File

@@ -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
[<d6162db91d2a02a68999ed8903f4a014><d6162db91d2a02a68999ed8903f4a014>]
[<6d65c6032a302e19cbfdfbf2e67cdcbe><6d65c6032a302e19cbfdfbf2e67cdcbe>]
% ReportLab generated PDF document -- digest (opensource)
/Info 11 0 R

View File

@@ -3,7 +3,7 @@
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Vorige les:** Les 14RAG + Embeddings
**Vorige les:** Les 12Tool 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.
---

View File

@@ -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

View File

@@ -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 (
<div>
<h1>{pokemon.name}</h1>
<img src={pokemon.sprites.front_default} alt="" />
</div>
);
}
// 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
<p>Omgeving: {process.env.NEXT_PUBLIC_APP_ENV}</p>
```
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?**