17 KiB
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
- Externe APIs in Next.js
- Server- vs client-side fetching
- Environment variabelen
- Cursor agents — Composer vs Background
- Vercel deployments
- Preview deployments per branch
- GitHub Actions CI
- Branch protection
- Werkstroom — feature van begin tot productie
- 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
// 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
asynczijn —awaitdirect in component - Errors moet je zelf afvangen (
!res.ok) fetchin 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)
// 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
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)
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)
"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.
// ✅ 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 pullom Vercel's dev-vars naar je.env.localte 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 bovenaanapp/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
- Push je repo naar GitHub
vercel.com→ Add New → Import Git Repository- Kies repo, klik Deploy
- ~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
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:
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
- Trigger: PR open of update, of push naar
main - Runner: Ubuntu container op GitHub infra
- Stappen:
- Checkout code
- Install pnpm + Node 20
- Install dependencies
- Run lint (faalt op fouten)
- Run build (faalt op TypeScript errors)
- 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)
- Repo settings → Branches → Add rule
- Branch name pattern:
main - Check: Require a pull request before merging
- Check: Require status checks to pass before merging → select CI workflow
- Check: Do not allow bypassing the above settings
Effect
- Niemand kan direct naar
mainpushen — 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.localin.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:
- Vercel dashboard → Deployments
- Vorige werkende deploy → "Promote to Production"
- 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