# 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
{data.name}
; } ``` 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 ?
{data.name}
:

Loading...

; } ``` - 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: " ``` ### 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