Files
novi-lessons/Les14-Cursor-Vercel-Deploy/Les14-Lesstof.md
2026-06-09 18:02:04 +02:00

518 lines
17 KiB
Markdown

# Les 13 — Lesstof
## Externe APIs + Cursor agents + Vercel deploy
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Vorige les:** Les 12 — Tool Calling
**Volgende les:** Les 14 — Agents
---
## Inhoud
1. [Externe APIs in Next.js](#1-externe-apis-in-nextjs)
2. [Server- vs client-side fetching](#2-server--vs-client-side-fetching)
3. [Environment variabelen](#3-environment-variabelen)
4. [Cursor agents — Composer vs Background](#4-cursor-agents--composer-vs-background)
5. [Vercel deployments](#5-vercel-deployments)
6. [Preview deployments per branch](#6-preview-deployments-per-branch)
7. [GitHub Actions CI](#7-github-actions-ci)
8. [Branch protection](#8-branch-protection)
9. [Werkstroom — feature van begin tot productie](#9-werkstroom--feature-van-begin-tot-productie)
10. [Productie-overwegingen](#10-productie-overwegingen)
---
## 1. Externe APIs in Next.js
Een externe API is een HTTP-endpoint van iemand anders waar je data of functionaliteit kunt ophalen. Voor de meeste apps die je gaat bouwen heb je er minstens één nodig.
### Wat valt eronder
- **Data-APIs** — PokéAPI (Pokemon), Open-Meteo (weer), GitHub API (repos), TMDB (films)
- **AI-providers** — OpenAI, Anthropic, Tavily (web search)
- **Payment** — Stripe, Mollie
- **Auth** — Clerk, Auth0, Supabase Auth
- **Email** — Resend, Postmark, SendGrid
### Drie smaken qua authenticatie
- **Geen key** — PokéAPI, Open-Meteo. Direct fetchen.
- **API key in URL of header** — Tavily, TMDB. Key beheren als env-variabele.
- **OAuth flow** — Google, GitHub. Complexer, vaak via libraries.
### Basis-pattern in Next.js
```typescript
// Server Component
async function getPokemon(name: string) {
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
if (!res.ok) throw new Error("Niet gevonden");
return res.json();
}
export default async function Page({ params }) {
const data = await getPokemon(params.name);
return <div>{data.name}</div>;
}
```
Drie dingen om te onthouden:
- Server Components mogen `async` zijn — `await` direct in component
- Errors moet je zelf afvangen (`!res.ok`)
- `fetch` in een Server Component **wordt automatisch gecached** door Next.js
---
## 2. Vier manieren om een externe API te fetchen
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/page.tsx — Server Component
async function getPokemon() {
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
return res.json();
}
```
- 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
### 2.2 ISR — Incremental Static Regeneration
```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";
import { useEffect, useState } from "react";
export function LivePokemon({ name }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`/api/pokemon/${name}`).then((r) => r.json()).then(setData);
}, [name]);
return data ? <div>{data.name}</div> : <p>Loading...</p>;
}
```
- 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.
### Spiekbriefje
| 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 — server-only vs client-exposed
### Twee soorten variabelen
| 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 met `NEXT_PUBLIC_` prefix wordt ingebakken in de browser-bundle. Zonder prefix = server-only.
```typescript
// ✅ Veilig — server-side gebruik
process.env.OPENAI_API_KEY
// ❌ NOOIT
"use client";
const key = process.env.NEXT_PUBLIC_OPENAI_KEY; // staat in de HTML response
```
### 3.1 Vercel — drie omgevingen, drie scopes
Op Vercel heb je per project drie environments. Bij elke env-var kies je in welke scopes hij wordt geïnjecteerd:
| 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` |
### 3.2 Per-omgeving andere waarden
In **Vercel → Project → Settings → Environment Variables** zet je per variabele drie keer een waarde:
| 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.
---
## 4. Cursor agents — Composer vs Background
Cursor heeft twee soorten "agents". Niet hetzelfde, niet uitwisselbaar.
### Composer
- Lokaal in je editor (`Cmd+I` / `Ctrl+I`)
- **Synchroon** — jij wacht, agent werkt, jij ziet diffs
- Multi-file edits in één keer
- Terminal-commands met approval
- Voor: pair programming, snelle changes, exploreren
### Background Agent
- Runt in **Cursor's cloud sandbox**
- **Asynchroon** — jij geeft prompt, gaat iets anders doen
- Maakt zelf een branch + commit + opent PR
- Kan tests draaien, dependencies installeren
- Verbonden via Cursor cloud (eenmalige setup)
- Voor: welomschreven features, parallel werk, PR-prep
### Aanroep
```
Cursor Composer: Cmd+I (Mac) of Ctrl+I (Win/Linux)
Cursor Chat: Cmd+L (kan switchen naar Agent mode)
Background Agent: Cmd+Shift+P → "Open Background Agent"
Of via Slack-integratie
```
### Mentale model
- **Composer = pair programming.** Jij + AI samen aan dezelfde plek tegelijk.
- **Background Agent = delegeren.** Je geeft taak, gaat iets anders doen, komt terug als-ie klaar is.
### Wanneer welke
| Scenario | Tool |
|----------|------|
| Snel iets aanpassen tijdens coden | Composer |
| Refactor over 5 files — wil zien wat hij doet | Composer |
| Onbekend gebied / leren | Composer |
| Welomschreven feature, can-be-async | Background Agent |
| Parallel werken aan 3 features | 3x Background Agent |
| Tijdens vergadering PR voorbereiden | Background Agent |
### Hoe schrijf je een goede Background Agent prompt
Een Background Agent ziet je niet — je moet specifiek zijn.
**Slecht:**
> "Voeg search toe."
**Goed:**
> "Maak een nieuwe branch `feature/search`. Voeg een controlled input toe bovenaan `app/page.tsx`. Filter de Pokémon-lijst op naam terwijl gebruiker typt. Case-insensitive. Gebruik Tailwind. Houd het stijl-consistent met bestaande cards. Push de branch en open een PR met titel 'Add search bar' en korte beschrijving in PR body."
Specifiek: bestandsnamen, gedrag, edge cases, output-formaat (branch + PR + titel).
---
## 5. Vercel deployments
Vercel is hosting platform door Next.js team (zelfde bedrijf). Voor Next.js apps de simpelste deploy.
### Eerste deployment
1. Push je repo naar GitHub
2. `vercel.com` → Add New → Import Git Repository
3. Kies repo, klik **Deploy**
4. ~60-90s later: live URL
**Geen config nodig.** Vercel detecteert Next.js automatisch.
### Build settings (als je ze nodig hebt)
- Framework: Next.js (auto)
- Build command: `pnpm build` (auto)
- Output directory: `.next` (auto)
- Install command: `pnpm install`
- Root directory: alleen aanpassen als app in subfolder zit (monorepo)
### CLI alternatief
```bash
pnpm i -g vercel
vercel login
vercel # preview deploy van current dir
vercel --prod # production deploy
vercel env pull # download .env.local van Vercel
```
---
## 6. Preview deployments per branch
Hier komt de magie. Vercel maakt automatisch een preview deploy voor **elke branch en elke PR**.
### Hoe werkt het
```
git push origin main → Production deploy
(je-app.vercel.app)
git push origin feature/search → Preview deploy
(je-app-git-feature-search-user.vercel.app)
PR open → Vercel comment in PR:
"🚀 Preview: <URL>"
```
### Wat krijg je hiermee
- **Reviewers** klikken op de URL in de PR, testen de feature live
- **Background Agents** maken PR → Vercel zet preview neer → klikbaar
- **Stakeholders** zien een feature voor merge, niet alleen screenshots
- **Geen "werkt op mijn laptop"** — alles draait op Vercel infra
### Belangrijk om in te stellen
- **Environment scoping:** preview-deploys mogen geen productie-keys gebruiken — gebruik aparte env vars per scope
- **Database:** in productie wijs je naar prod-DB, in preview kun je naar test-DB wijzen — meer dan een Vercel-feature, eigen architectuur
### Comment-bot
Vercel installeert een GitHub App. Die plakt een comment op je PR met:
```
🚀 Preview Deployment: https://...
✅ Build successful — 1m 23s
View build log
```
Per push naar de branch wordt deze comment ge-update.
---
## 7. GitHub Actions CI
Een **CI** (Continuous Integration) check draait automatisch op je code. Minimaal: lint + build.
### Waarom
- Voor merge weet je: code lint clean, types kloppen, build slaagt
- Cursor Background Agent maakt PR → CI draait → groene check of feedback
- Voorkomt dat broken code in main komt
### Minimaal workflow
`.github/workflows/ci.yml`:
```yaml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm build
```
### Wat gebeurt er
1. **Trigger:** PR open of update, of push naar `main`
2. **Runner:** Ubuntu container op GitHub infra
3. **Stappen:**
- Checkout code
- Install pnpm + Node 20
- Install dependencies
- Run lint (faalt op fouten)
- Run build (faalt op TypeScript errors)
4. **Status:** groene check of rode X op de PR
### Tijdsbudget
Voor een kleine Next.js app: 1-2 minuten. Voor grote apps met tests: 3-10 minuten.
### Caching
Met `cache: pnpm` herbruikt GH Actions de node_modules-cache tussen runs — scheelt 60-80% tijd.
---
## 8. Branch protection
Standaard kan iedereen pushen naar main. Voor productie wil je dat niet.
### Branch protection rules (op GitHub)
1. Repo settings → Branches → Add rule
2. Branch name pattern: `main`
3. Check: **Require a pull request before merging**
4. Check: **Require status checks to pass before merging** → select CI workflow
5. Check: **Do not allow bypassing the above settings**
### Effect
- Niemand kan direct naar `main` pushen — moet via PR
- PR kan pas merge als CI groen is
- Force-push naar main geblokkeerd
- Background Agents werken automatisch via PRs (zo bedoeld)
### Voor solo-projecten
Beetje overkill, maar oefenen is goed. En het voorkomt 'fluitje van een cent' bugs naar main.
---
## 9. Werkstroom — feature van begin tot productie
De volledige flow zoals je 'm in dit vak gebruikt:
```
1. Idee voor feature
2. Background Agent prompt (specifiek!)
3. Cursor opent branch + maakt commits + opent PR
4. GitHub Actions CI draait — lint + build
5. Vercel zet preview deploy neer — URL in PR comment
6. Jij/team reviewt: code-diff + live preview
7. Eventueel: commentaar in PR, Background Agent retried
8. Merge PR → main
9. Vercel productie-deploy is automatisch
10. Klaar — live op je-app.vercel.app
```
Tijdsindicatie voor één feature: 10-30 minuten end-to-end (afhankelijk van complexiteit).
### Wat als CI rood is
- Klik op rode X in PR → naar GitHub Actions logs
- Lees error: lint-error, build-error, of test-failure
- Twee opties:
- **Composer:** open feature branch lokaal, fix met Composer, push
- **Background Agent:** nieuwe prompt: "fix de CI error in deze PR — lees de logs en repareer"
### Wat als preview niet werkt
- Open Vercel dashboard → project → Deployments → klik op preview build
- Build logs lezen
- Vaak: env-var mist (Vercel preview scope leeg)
- Of: dependency die op Vercel niet beschikbaar is
---
## 10. Productie-overwegingen
### Costs
| Service | Free tier | Wanneer betalen |
|---------|-----------|-----------------|
| **Vercel** | Hobby tier — gratis voor persoonlijk gebruik | Commercieel of >100GB bandwidth |
| **GitHub Actions** | 2000 min/maand gratis (public repos unlimited) | Bij grote teams of veel runs |
| **Cursor** | Hobby (gratis), Pro ($20/mnd), Business | Background Agents zitten in Pro/Business |
| **PokéAPI** | Volledig gratis, geen rate-limit issues | n.v.t. |
### Performance
Vercel doet veel automatisch:
- Edge CDN voor static assets
- ISR (Incremental Static Regeneration) voor `revalidate`
- Image optimization via `next/image`
- Automatische gzip/brotli
Voor sub-1s page loads: meestal niks doen.
### Observability
- **Vercel Analytics** — page views + Core Web Vitals (gratis tier beperkt)
- **Vercel Logs** — server-side console.log zichtbaar
- **Sentry / LogRocket** — externe tools voor error tracking
- **GitHub Actions logs** — bewaard 90 dagen
### Security
- API keys NOOIT in client (geen `NEXT_PUBLIC_`)
- `.env.local` in `.gitignore` (vooraf checken!)
- Rotate keys regelmatig — vooral na PR's van Background Agents
- Vercel environment scoping om dev/prod te scheiden
### Rollback
Als productie kapot is:
1. Vercel dashboard → Deployments
2. Vorige werkende deploy → "Promote to Production"
3. Klaar, ~10 seconden — geen rebuild nodig
---
## Bronnen
- **Next.js fetching:** https://nextjs.org/docs/app/building-your-application/data-fetching
- **Next.js caching:** https://nextjs.org/docs/app/building-your-application/caching
- **PokéAPI:** https://pokeapi.co/
- **Cursor docs:** https://docs.cursor.com/
- **Cursor Background Agents:** https://docs.cursor.com/background-agent
- **Vercel docs:** https://vercel.com/docs
- **Vercel preview deployments:** https://vercel.com/docs/deployments/preview-deployments
- **Vercel environment variables:** https://vercel.com/docs/environment-variables
- **GitHub Actions:** https://docs.github.com/en/actions
- **GitHub branch protection:** https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches