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

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