Files
novi-lessons/Les08-Supabase-Auth/Les08-Docenttekst.md
2026-03-31 16:06:18 +02:00

894 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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**