fix: add les 8

This commit is contained in:
2026-03-31 16:06:18 +02:00
parent a0c7413c14
commit ca11a67016
7 changed files with 3758 additions and 0 deletions

View File

@@ -0,0 +1,893 @@
# Les 8 — Docenttekst
## Supabase × Next.js + Auth
---
## Lesoverzicht
| Gegeven | Details |
|---------|---------|
| **Les** | 8 van 18 |
| **Onderwerp** | Supabase koppelen + Auth introductie |
| **Duur** | 3 uur (09:00 12:00) |
| **Voorbereiding** | Werkend QuickPoll project, Supabase project met polls/options tabellen, RLS ingesteld |
| **Benodigdheden** | Laptop, Cursor/VS Code, browser, Supabase account |
---
## Leerdoelen
Na deze les kunnen studenten:
1. De Supabase JavaScript client gebruiken in een Next.js project
2. Data ophalen via Supabase queries (select met relaties, eq, single)
3. Het Server Component + Client Component patroon toepassen
4. Uitleggen wat authenticatie vs autorisatie is
5. Supabase Auth functies gebruiken (signUp, signIn, signOut, getUser)
6. Een login/registratie flow bouwen in Next.js
---
## Lesvoorbereiding (voor docent)
Zorg dat je volgende zaken hebt voorbereiding:
- Een werkend Supabase project met `polls` en `options` tabellen (uit Les 7)
- RLS ingeschakeld op beide tabellen met policies voor SELECT (anon) en UPDATE (anon op options)
- De Next.js QuickPoll app uit Les 7 werkend op je machine
- De slides gereed voor uitleg authenticatie vs autorisatie
- Test je eigen Supabase credentials vooraf
---
## 09:0009:10 | Welkom & Terugblik (10 min)
**Doel:** Studenten krijgen duidelijk wat we vandaag doen en waar we van vorige week waren.
### Wat we hebben gedaan in Les 7:
- ✅ Stemmen werkend gemaakt (votePoll functie, state update in poll detail page)
- ✅ Supabase introductie: account aangemaakt, project gemaakt
- ✅ Database: polls + options tabellen aangemaakt
- ✅ Foreign keys + CASCADE ingesteld
- ✅ RLS policies ingesteld (SELECT voor anon, UPDATE voor anon op options)
- ✅ Testdata ingevoerd via Table Editor
### Wat we NIET hebben afgemaakt in Les 7:
- ❌ Supabase is NIET aan het Next.js project gekoppeld
- ❌ Data wordt nog niet uit Supabase opgehaald
- ❌ Geen authenticatie
### Vandaag gaan we:
1. **DEEL 1 (65 min):** Supabase client installeren en opzetten → data uit database halen in Next.js
2. **DEEL 2a (30 min):** Uitleg over authenticatie, autorisatie en Supabase Auth features
3. **DEEL 2b (30 min):** Studenten bouwen auth zelf in hun project (signup, login, logout)
**Motivatie:** "Tot nu toe zijn je polls hardcoded in geheugen. Straks halen we echte data uit Supabase en kunnen people inloggen. Dat is een echt web app!"
---
## 09:1010:15 | DEEL 1: Supabase Koppelen — Live Coding (65 min)
Dit deel volgt een stap-voor-stap aanpak met live coding. Alle studenten coderen mee.
### 09:1009:15 | Installatie (5 min)
Open terminal in het QuickPoll project en run:
```bash
npm install @supabase/supabase-js
```
**Teacher Tip:** Controleer dat de installatie slaagt. Als students `npm ERR!` zien, laat ze eerst `npm clean-install` doen en daarna opnieuw proberen.
### 09:1509:25 | Environment Variables (10 min)
Zorg dat alle studenten hun Supabase credentials veilig opslaan.
1. Open in Supabase Dashboard: **Settings****API**
2. Kopieer:
- `Project URL` (eindigt op `.supabase.co`)
- `anon` public key
3. Maak/open `.env.local` in je Next.js project root:
```env
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here
```
**Belangrijk:**
- `.env.local` staat al in `.gitignore` (check even)
- Keys die beginnen met `NEXT_PUBLIC_` zijn zichtbaar in browser (maar anon keys zijn daarvoor bedoeld)
- **ALTIJD de dev server herstarten na wijzigen van `.env.local`** (Ctrl+C, dan `npm run dev`)
**Teacher Tip:** Dit is een veelvoorkomende fout. Zeg hardop: "Als jullie een leeg array zien in plaats van polls, check EERST of je dev server herstarten hebt!"
### 09:2509:35 | Supabase Client aanmaken (10 min)
Maak `lib/supabase.ts`:
```typescript
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
export const supabase = createClient(supabaseUrl, supabaseKey)
```
**Wat gebeurt hier:**
- We importeren `createClient` uit `@supabase/supabase-js`
- We halen URL en key uit environment variables
- We geven deze aan `createClient`
- We exporteren de client zodat we het overal kunnen gebruiken
**Teacher Tip:** TypeScript geeft mogelijk een warning over "null assertion (!)" — dat is OK. Dit zeggen we tegen TypeScript: "Deze values bestaan echt, vertrouw me."
### 09:3509:45 | Database Types (10 min)
Maak `lib/types.ts` handmatig:
```typescript
export interface Poll {
id: string
created_at: string
question: string
}
export interface Option {
id: string
poll_id: string
text: string
votes: number
}
```
**Waarom:** Dit helpt TypeScript begrijpen welke data we uit Supabase krijgen.
**Teacher Tip:** In een echt project zou je `npx supabase gen types typescript` gebruiken, maar dat kost extra setup. Voor deze les is handmatig OK.
### 09:4510:00 | Async Data functies (15 min)
Update `lib/data.ts` — alle functies worden nu async en halen data uit Supabase:
```typescript
import { supabase } from './supabase'
import { Poll, Option } from './types'
export async function getPolls(): Promise<Poll[]> {
const { data, error } = await supabase
.from('polls')
.select('*')
.order('created_at', { ascending: false })
if (error) {
console.error('Error fetching polls:', error)
return []
}
return data || []
}
export async function getOptions(pollId: string): Promise<Option[]> {
const { data, error } = await supabase
.from('options')
.select('*')
.eq('poll_id', pollId)
.order('votes', { ascending: false })
if (error) {
console.error('Error fetching options:', error)
return []
}
return data || []
}
```
**Wat betekent dit:**
- `.from('polls')` — welke tabel
- `.select('*')` — alle kolommen
- `.eq('poll_id', pollId)` — filter op poll_id
- `.order()` — sorteer op
- `await` — wacht op het resultaat van de database call
- Error handling — log en return empty array
**Teacher Tip:** Veel students maken hier fouten met async/await:
```typescript
// ❌ FOUT: promise niet awaited!
const data = supabase.from('polls').select('*')
// ✅ GOED:
const data = await supabase.from('polls').select('*')
```
### 10:0010:10 | Homepage als Server Component (10 min)
Update `app/page.tsx` — dit wordt een Server Component:
```typescript
import { getPolls } from '@/lib/data'
import PollItem from '@/components/PollItem'
export default async function Home() {
const polls = await getPolls()
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-8">QuickPoll</h1>
<div className="space-y-4">
{polls.map((poll) => (
<PollItem key={poll.id} poll={poll} />
))}
</div>
{polls.length === 0 && (
<p className="text-gray-500">Geen polls beschikbaar.</p>
)}
</div>
)
}
```
**Belangrijk:** Page.tsx is nu een **Server Component** — geen `'use client'` directive! We kunnen hier `async/await` rechtstreeks gebruiken.
**Teacher Tip:** Students vragen: "Maar hoe krijgen we de options?" — Goed punt! Die halen we in PollItem.
### 10:1010:15 | PollItem Component (5 min)
Update `components/PollItem.tsx` — ook een Server Component:
```typescript
import { getOptions } from '@/lib/data'
import VoteForm from './VoteForm'
import { Poll } from '@/lib/types'
export default async function PollItem({ poll }: { poll: Poll }) {
const options = await getOptions(poll.id)
return (
<div className="border rounded-lg p-4">
<h2 className="text-lg font-semibold mb-3">{poll.question}</h2>
<div className="space-y-2">
{options.map((option) => (
<VoteForm
key={option.id}
option={option}
pollId={poll.id}
/>
))}
</div>
</div>
)
}
```
**Waarom twee Server Components?**
- `page.tsx` ziet alleen alle polls (geen details)
- `PollItem` wordt per poll gerenderd en haalt zelf de options op (parallel!)
- Dit patroon is efficient en schaalbaar
**Teacher Tip:** Dit is het "Suspended Components" patroon van React 18 — Server Components voeren dit automatisch in parallel uit.
---
## 10:1510:30 | PAUZE (15 min)
Goed moment om even weg te lopen. Tussendoor kun jij:
- Rondlopen en kijken wie nog errors heeft
- Checken of iedereen env vars juist ingesteld heeft
- Dev servers herstarten voor wie vergeten zijn
- Voorbereiding treffen voor DEEL 2
---
## 10:3011:00 | DEEL 2a: Uitleg Auth (30 min)
Dit is uitleg — geen live coding nog. Zorg dat alle laptops dicht zijn, focus op slides en beamer.
### Authenticatie vs Autorisatie
**Authenticatie (Authentication):**
- "Wie ben je?" — identity verification
- Voorbeeld: Je logt in met email + password
- Supabase Auth zorgt hiervoor
**Autorisatie (Authorization):**
- "Wat mag je?" — permissions
- Voorbeeld: Je mag alleen je eigen polls aanpassen
- RLS (Row Level Security) in Supabase zorgt hiervoor
**Voorbeeld:**
- Auth: "Je email en password kloppen, je bent Alice."
- RLS: "Alice mag haar eigen polls zien en updaten, maar niet die van Bob."
### Supabase Auth Features
Demo op beamer:
1. Open Supabase Dashboard → **Authentication****Providers**
2. Toon dat **Email/Password** is ingeschakeld
3. Toon de instelling **"Confirm email"** (nu UIT voor dev)
4. Ga naar **Users** tab — hier zie je ingelogde users
**Supabase Auth ondersteunt:**
- Email/Password (wat we vandaag gebruiken)
- OAuth (Google, GitHub, etc.) — volgende week
- Magic Links (passwordless login)
- Session management (Supabase beheert cookies automatisch)
### @supabase/ssr vs @supabase/supabase-js
**@supabase/supabase-js:**
- Browser-side client
- Voor onClick handlers, forms, interactie
**@supabase/ssr:**
- Server-side client (SSR = Server-Side Rendering)
- Voor middleware, cookies, server actions
- Handelt sessions automatisch af
**Waarom twee?**
- Browser kan niet veilig geheimen beheren
- Server kan veilig cookies zetten
- Supabase SSR packages zorgen dat beide veilig werken
### Supabase Auth Functies
**signUp(email, password)** — nieuwe account aanmaken
```typescript
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password'
})
```
**signInWithPassword(email, password)** — inloggen
```typescript
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'secure-password'
})
```
**signOut()** — uitloggen
```typescript
await supabase.auth.signOut()
```
**getUser()** — huidge user ophalen
```typescript
const { data: { user } } = await supabase.auth.getUser()
// user is null als niemand ingelogd, anders is het een User object
```
### Server vs Browser Client
**Browser Client (createBrowserClient):**
- Voor 'use client' components
- Kan useState gebruiken
- Kan useRouter gebruiken
- Kan user events luisteren
**Server Client (createServerClient):**
- Voor server components en middleware
- Leest/schrijft cookies
- Kan getUser() veilig aanroepen
- Geen access tot browser APIs
### Middleware & Session Refresh
**Wat doet middleware?**
- Draait op elke request naar je app
- Refreshed de Supabase session
- Zorgt dat user state altijd up-to-date is
**Voorbeeld flow:**
1. User logt in op `/login` page
2. Cookie wordt gezet
3. Middleware ziet op volgende request: "Er is een session cookie!"
4. Middleware refreshed de session
5. App ziet dat user ingelogd is
### Handige links
Toon op slides:
- [Supabase Auth docs](https://supabase.com/docs/guides/auth/server-side/nextjs)
- [Next.js Server Components docs](https://nextjs.org/docs/getting-started/react-essentials)
---
## 11:0011:30 | DEEL 2b: Zelf Doen — Auth Implementeren (30 min)
Nu gaan studenten zelf auth bouwen in hun project. Dit is niet meer live coding — docent loopt rond en helpt.
**Instructie voor studenten:**
Volg deze stappen. Docent loopt rond als je vragen hebt.
#### Stap 1: SSR Package Installeren (2 min)
```bash
npm install @supabase/ssr
```
#### Stap 2: Server Client (3 min)
Maak `lib/supabase-server.ts`:
```typescript
import { cookies } from 'next/headers'
import { createServerClient } from '@supabase/ssr'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// Handle error
}
},
},
}
)
}
```
**Wat is dit?** Dit is een helper zodat Supabase cookies kan beheren in Next.js. Copy-paste voor nu.
#### Stap 3: Browser Client (1 min)
Maak `lib/supabase-browser.ts`:
```typescript
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
```
**Wat is dit?** Dit gebruiken we in 'use client' components.
#### Stap 4: Middleware (5 min)
Maak `middleware.ts` in project root:
```typescript
import { type NextRequest, NextResponse } from 'next/server'
import { createServerClient } from '@supabase/ssr'
export async function middleware(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
supabaseResponse.cookies.set(name, value, options)
})
},
},
}
)
// Refresh user session
await supabase.auth.getUser()
return supabaseResponse
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.svg|.*\\.png|.*\\.jpg|.*\\.jpeg).*)',
],
}
```
**Wat is dit?** Dit draait op elke request en refreshed de session. Copy-paste, don't worry.
#### Stap 5: Signup Page (5 min)
Maak `app/auth/signup/page.tsx`:
```typescript
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase-browser'
export default function SignUpPage() {
const router = useRouter()
const supabase = createClient()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError('')
const { error } = await supabase.auth.signUp({
email,
password,
})
if (error) {
setError(error.message)
setLoading(false)
} else {
router.push('/auth/login')
}
}
return (
<div className="flex items-center justify-center min-h-screen">
<form onSubmit={handleSignUp} className="w-full max-w-md p-6 border rounded-lg">
<h1 className="text-2xl font-bold mb-6">Sign Up</h1>
{error && <div className="text-red-600 mb-4">{error}</div>}
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border rounded mb-4"
required
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 border rounded mb-6"
required
/>
<button
type="submit"
disabled={loading}
className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Signing up...' : 'Sign Up'}
</button>
</form>
</div>
)
}
```
**Belangrijk:** `'use client'` directive bovenaan — dit is een interactive component!
#### Stap 6: Login Page (5 min)
Maak `app/auth/login/page.tsx`:
```typescript
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase-browser'
import Link from 'next/link'
export default function LoginPage() {
const router = useRouter()
const supabase = createClient()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError('')
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
setError(error.message)
setLoading(false)
} else {
router.push('/')
}
}
return (
<div className="flex items-center justify-center min-h-screen">
<form onSubmit={handleLogin} className="w-full max-w-md p-6 border rounded-lg">
<h1 className="text-2xl font-bold mb-6">Login</h1>
{error && <div className="text-red-600 mb-4">{error}</div>}
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border rounded mb-4"
required
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 border rounded mb-6"
required
/>
<button
type="submit"
disabled={loading}
className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Logging in...' : 'Login'}
</button>
<p className="mt-4 text-center text-sm">
Nog geen account? <Link href="/auth/signup" className="text-blue-600 hover:underline">Sign up</Link>
</p>
</form>
</div>
)
}
```
#### Stap 7: Logout Button (3 min)
Maak `components/LogoutButton.tsx`:
```typescript
'use client'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase-browser'
export default function LogoutButton() {
const router = useRouter()
const supabase = createClient()
const handleLogout = async () => {
await supabase.auth.signOut()
router.refresh()
}
return (
<button
onClick={handleLogout}
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
Logout
</button>
)
}
```
**Belangrijk:** `router.refresh()` na logout zorgt dat page de nieuwe state ziet!
#### Stap 8: Navbar met Auth State (3 min)
Update `components/Navbar.tsx`:
```typescript
import { createClient } from '@/lib/supabase-server'
import Link from 'next/link'
import LogoutButton from './LogoutButton'
export default async function Navbar() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
return (
<nav className="bg-gray-800 text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<Link href="/" className="text-2xl font-bold">
QuickPoll
</Link>
<div className="flex gap-4 items-center">
{user ? (
<>
<span className="text-sm">{user.email}</span>
<LogoutButton />
</>
) : (
<>
<Link href="/auth/login" className="px-4 py-2 bg-blue-600 rounded hover:bg-blue-700">
Login
</Link>
<Link href="/auth/signup" className="px-4 py-2 bg-green-600 rounded hover:bg-green-700">
Sign Up
</Link>
</>
)}
</div>
</div>
</nav>
)
}
```
**Logica:**
- Als `user` bestaat (ingelogd): toon email + Logout button
- Anders: toon Login + Sign Up buttons
#### Stap 9: Layout updaten (2 min)
Update `app/layout.tsx`:
```typescript
import type { Metadata } from 'next'
import Navbar from '@/components/Navbar'
import './globals.css'
export const metadata: Metadata = {
title: 'QuickPoll',
description: 'Vote on polls',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Navbar />
{children}
</body>
</html>
)
}
```
Voeg gewoon `<Navbar />` toe.
**Teacher Tip: Studenten vastlopen?**
- Na 5-10 minuten vastzitten: toon de referentie code op beamer
- Zeg: "Dit is complex, copy-paste is OK. Focus op begrijpen, niet op typen."
- Help met debuggen (console.log, errors lezen)
---
## 11:3011:45 | Vragen & Reflectie (15 min)
Hier zijn veelvoorkomende vragen:
### V: "Wat is het verschil tussen `createClient()` in server.ts en browser.ts?"
**A:**
- `server.ts`: kan cookies veilig beheren (server-side)
- `browser.ts`: kan UI events afhandelen (onClick, forms)
- Supabase kiest automatisch het juiste moment om te gebruiken
### V: "Waarom twee environment variables bovenaan?"
**A:**
- `NEXT_PUBLIC_SUPABASE_URL`: URL is public, iedereen ziet het
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`: anon key is public (maar kan geen private data lezen)
- Private keys (service role) zetten we NIET in .env.local, die gaan in server.ts als geheim
### V: "Mijn login werkt niet, ik krijg error"
**A:** Check:
1. Klopt je email/password echt?
2. Is je account in Supabase Dashboard → Authentication → Users?
3. Is Email provider ingeschakeld?
4. Zit "Confirm email" uit? (check dashboard)
### V: "Logout werkt niet, user staat nog ingelogd"
**A:** Vergeten `router.refresh()` na `signOut()`?
### V: "Middleware error: 'createServerClient is not defined'"
**A:** Check je import: moet `import { createServerClient } from '@supabase/ssr'` zijn
### V: "Kan ik als anonieme user stemmen?"
**A:** Ja! RLS policy staat op `FOR SELECT, UPDATE TO authenticated` — maar je Navbar toont Login/Signup want je bent nog niet ingelogd. Dat is OK. Volgende les doen we RLS policies correct.
---
## 11:4512:00 | Huiswerk & Afsluiting (15 min)
### Huiswerk (voor Les 9):
**Verplicht:**
1. **/create pagina bouwen** — studenten voegen nieuwe polls toe via een form
- Maak `app/create/page.tsx` (Server Component met form als Client Component)
- Form met: vraag + array van 2-3 opties
- `supabase.from('polls').insert()` en `supabase.from('options').insert()`
- Zorg dat je eigen `user_id` meestuurt
2. **RLS INSERT policy** — alleen authenticated users mogen polls toevoegen
- Supabase Dashboard → Authentication → Policies
- Voeg policy toe: `INSERT` voor authenticated users
- `user_id = auth.uid()`
3. **Optional extras (challenge):**
- Toon poll creator in PollItem
- Google OAuth inschakelen (zie Supabase docs)
- Edit/Delete buttons (alleen voor je eigen polls)
### Afsluitingsboodschap:
"Gefeliciteerd! Vandaag hebben jullie:
- Supabase gekoppeld aan Next.js
- Real data uit een database geladen
- Login/logout gebouwd
- Server & browser clients begrepen
Volgende week voegen we RLS policies toe zodat iedereen alleen zijn eigen polls kan aanpassen. Dat is waar authenticatie écht nuttig wordt!"
---
## Veelvoorkomende Problemen
| Probleem | Oorzaak | Oplossing |
|----------|---------|-----------|
| `Error: Cannot find module '@supabase/supabase-js'` | Package niet geïnstalleerd | `npm install @supabase/supabase-js` en dev server herstarten |
| Supabase returns leeg array | .env.local niet juist of dev server niet herstarten | Check .env.local, restart dev server (Ctrl+C + `npm run dev`) |
| TypeScript complains over `null assertion (!)` | Normale TS warning | Dit is OK, we vertellen TS dat env vars bestaan |
| `'use client' vergeten in signup/login page` | Component is interactief maar geen directive | Voeg `'use client'` bovenaan toe |
| Login page blank/geen content | Conflict met server components | Zorg ALL pages onder /auth zijn `'use client'` |
| Logout werkt niet, user nog ingelogd | `router.refresh()` niet aangeroepen | Voeg `await router.refresh()` toe na `signOut()` |
| Middleware error: "wrong params" | Onjuiste URL of key in middleware | Copy-paste van .env.local, check Format |
| "Invalid token" bij Supabase calls | Token verlopen of anon key fout | Restart dev server, check API credentials |
| User niet in Authentication → Users | Signup failed, geen account aangemaakt | Check browser console op errors, probeer opnieuw met ander email |
| `router.refresh()` werkt niet in component | Router niet geïmporteerd | `import { useRouter } from 'next/navigation'` (niet 'next/router'!) |
| Cors/network error | Supabase URL fout | Check dat URL eindigt op `.supabase.co` en https:// bevat |
| Password te kort / validation error | Supabase vereist min 6 chars | Zeg studenten: "Test met password123" |
---
## Didactische Tips
- **Pair Programming:** Zet snelle studenten samen met tragere — kennis spreidt zich uit
- **Show & Tell:** Toon je eigen werkend QuickPoll op beamer — studenten zien het doel
- **Error-driven Learning:** Zeg niet meteen het antwoord, vraag: "Wat zegt de error?"
- **Debug together:** Als iemand vastlopen, use browser console.log + devtools
- **Save time** — als >3 students dezelfde error hebben, stop even en toon op beamer
- **Celebrate wins** — als iemand eerste Signup working heeft, geef thumbs up!
---
## Referentiematerialen voor Studenten
- [Supabase Auth docs](https://supabase.com/docs/guides/auth/server-side/nextjs)
- [Next.js Server Components](https://nextjs.org/docs/getting-started/react-essentials)
- [Environment Variables in Next.js](https://nextjs.org/docs/basic-features/environment-variables)
- Alle code snippets uit deze docenttekst
---
**Einde docenttekst Les 8**