fix: all
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Vorige les:** Les 14 — RAG + Embeddings
|
||||
**Vorige les:** Les 12 — Tool 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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user