26 KiB
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:
- De Supabase JavaScript client gebruiken in een Next.js project
- Data ophalen via Supabase queries (select met relaties, eq, single)
- Het Server Component + Client Component patroon toepassen
- Uitleggen wat authenticatie vs autorisatie is
- Supabase Auth functies gebruiken (signUp, signIn, signOut, getUser)
- Een login/registratie flow bouwen in Next.js
Lesvoorbereiding (voor docent)
Zorg dat je volgende zaken hebt voorbereiding:
- Een werkend Supabase project met
pollsenoptionstabellen (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:00–09: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:
- DEEL 1 (65 min): Supabase client installeren en opzetten → data uit database halen in Next.js
- DEEL 2a (30 min): Uitleg over authenticatie, autorisatie en Supabase Auth features
- 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:10–10: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:10–09:15 | Installatie (5 min)
Open terminal in het QuickPoll project en run:
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:15–09:25 | Environment Variables (10 min)
Zorg dat alle studenten hun Supabase credentials veilig opslaan.
-
Open in Supabase Dashboard: Settings → API
-
Kopieer:
Project URL(eindigt op.supabase.co)anonpublic key
-
Maak/open
.env.localin je Next.js project root:
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here
Belangrijk:
.env.localstaat 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, dannpm 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:25–09:35 | Supabase Client aanmaken (10 min)
Maak lib/supabase.ts:
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
createClientuit@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:35–09:45 | Database Types (10 min)
Maak lib/types.ts handmatig:
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:45–10:00 | Async Data functies (15 min)
Update lib/data.ts — alle functies worden nu async en halen data uit Supabase:
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 opawait— 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:
// ❌ FOUT: promise niet awaited!
const data = supabase.from('polls').select('*')
// ✅ GOED:
const data = await supabase.from('polls').select('*')
10:00–10:10 | Homepage als Server Component (10 min)
Update app/page.tsx — dit wordt een Server Component:
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:10–10:15 | PollItem Component (5 min)
Update components/PollItem.tsx — ook een Server Component:
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.tsxziet alleen alle polls (geen details)PollItemwordt 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:15–10: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:30–11: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:
- Open Supabase Dashboard → Authentication → Providers
- Toon dat Email/Password is ingeschakeld
- Toon de instelling "Confirm email" (nu UIT voor dev)
- 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
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password'
})
signInWithPassword(email, password) — inloggen
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'secure-password'
})
signOut() — uitloggen
await supabase.auth.signOut()
getUser() — huidge user ophalen
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:
- User logt in op
/loginpage - Cookie wordt gezet
- Middleware ziet op volgende request: "Er is een session cookie!"
- Middleware refreshed de session
- App ziet dat user ingelogd is
Handige links
Toon op slides:
11:00–11: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)
npm install @supabase/ssr
Stap 2: Server Client (3 min)
Maak lib/supabase-server.ts:
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:
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:
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:
'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:
'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:
'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:
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
userbestaat (ingelogd): toon email + Logout button - Anders: toon Login + Sign Up buttons
Stap 9: Layout updaten (2 min)
Update app/layout.tsx:
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:30–11: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 hetNEXT_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:
- Klopt je email/password echt?
- Is je account in Supabase Dashboard → Authentication → Users?
- Is Email provider ingeschakeld?
- 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:45–12:00 | Huiswerk & Afsluiting (15 min)
Huiswerk (voor Les 9):
Verplicht:
-
/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()ensupabase.from('options').insert()- Zorg dat je eigen
user_idmeestuurt
- Maak
-
RLS INSERT policy — alleen authenticated users mogen polls toevoegen
- Supabase Dashboard → Authentication → Policies
- Voeg policy toe:
INSERTvoor authenticated users user_id = auth.uid()
-
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
- Next.js Server Components
- Environment Variables in Next.js
- Alle code snippets uit deze docenttekst
Einde docenttekst Les 8