# Les 6: Next.js — QuickPoll Compleet - Docenttekst

**NOVI Hogeschool | Instructeur: Tim | Duur: 180 minuten**

---

## VOORBEREIDING

**Checklist (30 minuten voor les):**
- [ ] Demo project klaarzetten: `quickpoll-demo` uitpakken, `npm install`
- [ ] `git checkout stap-0` → startpunt
- [ ] Test: `git checkout bonus` → volledige app draait
- [ ] Terug naar `git checkout stap-0`
- [ ] Cursor openen, terminal font minimaal 16pt
- [ ] Browser met `localhost:3000` open
- [ ] **Scherm delen:** Teams → deel alleen het Cursor-venster
- [ ] Browser/notities op apart Space (voor stiekem opzoeken)
- [ ] Losse stap-zipjes als backup

**Tempo-indicatie:**
| Blok | Tijd | Inhoud |
|------|------|--------|
| Stap 0-3 (recap) | 0:00-0:40 | Project, types, layout, homepage, GET route |
| Stap 4 | 0:40-1:00 | POST vote route |
| Stap 5 | 1:00-1:15 | Poll detail pagina |
| **PAUZE** | **1:15-1:30** | |
| Stap 6 | 1:30-2:10 | VoteForm (logica + UI) |
| Stap 7 | 2:10-2:30 | Loading, Error, Not-Found |
| Stap 8 | 2:30-2:45 | Middleware |
| Afsluiting | 2:45-3:00 | Huiswerk, vragen |

---

## STAP 0-3: RECAP (0:00-0:40)

*Dit gaat snel — studenten kennen het al. Doel: iedereen op hetzelfde punt krijgen.*

### Slide 1: Titel + Plan (0:00-0:02)

> "Welkom terug! Vorige les hebben jullie kennis gemaakt met Next.js. Vandaag bouwen we de hele QuickPoll app van scratch tot werkend."

> "Ik code, jullie volgen mee. Stap 0-3 kennen jullie — dat gaat snel. Stap 4-8 is nieuw, daar nemen we de tijd voor."

---

### Slide 2: Stap 0 — Project Aanmaken (0:02-0:07)

*Tim opent terminal.*

> "Stap 0: `npx create-next-app@latest quickpoll`. TypeScript, Tailwind, App Router — alles yes."

*Tim wacht tot iedereen `npm run dev` draait.*

> "Zie je de Next.js standaard pagina? Door."

**⚡ Snelheid:** Studenten die dit al hebben kunnen alvast door naar stap 1.

---

### Slide 3: Stap 1 — Types & Data (0:07-0:15)

> "Twee bestanden: `src/types/index.ts` met de Poll interface, en `src/lib/data.ts` met onze in-memory database."

*Tim typt de interface.*

> "Een poll: id, question, options, votes. De votes array loopt parallel met options — index 0 van votes hoort bij index 0 van options."

*Tim typt data.ts.*

> "Drie helper functies: getPolls, getPollById, votePoll. Dit is onze 'database' — later vervangen we dit door Supabase."

*Checkpoint: "Beide bestanden staan er? Door."*

---

### Slide 4: Stap 2 — Layout (0:15-0:22)

> "De layout wrapt elke pagina. Navbar, main content, footer. Verandert nooit."

*Tim vervangt layout.tsx.*

> "`Link` is Next.js client-side navigatie — geen page reload. `metadata` is je SEO titel."

**⚡ Tip:** Niet te lang stilstaan, dit kennen ze.

---

### Slide 5: Stap 2 vervolg — Homepage (0:22-0:30)

> "De homepage: een Server Component die alle polls ophaalt en cards rendert."

*Tim vervangt page.tsx.*

> "Geen `'use client'`, geen useEffect, geen loading state. Server Component haalt data direct op. Dat is het voordeel."

> "De `.map()` rendert een card per poll. `.reduce()` telt totaal stemmen."

*Checkpoint: "3 cards op localhost:3000? Mooi."*

---

### Slide 6: Stap 3 — GET API Route (0:30-0:40)

> "Onze eerste API route. De folder-structuur IS je URL: `api/polls/[id]/route.ts` wordt `/api/polls/1`."

*Tim maakt het bestand.*

> "Dynamic route: `[id]` met vierkante haakjes. De waarde uit de URL wordt de `id` parameter."

> "`await params` — Next.js 15 ding. Params zijn een Promise. Vergeet de await niet."

*Tim test in browser: localhost:3000/api/polls/1.*

> "JSON! Test ook `/api/polls/999` — 404. Mooi, recap klaar. Nu het nieuwe werk."

---

## STAP 4-5: NIEUW MATERIAAL DEEL 1 (0:40-1:15)

### Slide 7: Stap 4 — POST Vote Route (0:40-1:00)

**💡 Theorie-moment: POST vs GET**

> "GET leest data, POST wijzigt data. Het patroon voor elke POST route: params, body, validatie, actie, response. Vijf stappen, altijd."

*Tim maakt src/app/api/polls/[id]/vote/route.ts.*

> "Nieuwe folder: `vote/` in de `[id]/` folder. Zo krijg je `/api/polls/1/vote`."

*Tim typt de code langzaam.*

> "`request.json()` leest de body — wat de client stuurt. Hier: welke optie."

> "Twee error checks: 400 als data ongeldig, 404 als poll niet bestaat. Altijd beide."

*Tim opent browser console, test met fetch.*

> "Plak dit in je console. Zie je? Votes veranderd in de JSON. Onze stem-API werkt."

*Checkpoint: "Heeft iedereen JSON in de console? Mooi."*

---

### Slide 8: Stap 5 — Poll Detail Pagina (1:00-1:15)

**💡 Theorie-moment: generateMetadata & notFound**

> "`generateMetadata` — speciale Next.js functie voor dynamische SEO. Elke poll krijgt z'n eigen browser tab titel."

> "`notFound()` — roep je aan als data niet bestaat. Next.js toont automatisch de 404 pagina."

*Tim maakt src/app/poll/[id]/page.tsx.*

> "Dit is de combinatie: Server Component haalt data, rendert Client Component (`VoteForm`). Server = data, client = interactie."

> "We importeren VoteForm maar die bestaat nog niet — dat bouwen we na de pauze."

*Checkpoint: "/poll/1 geeft een error — dat klopt, VoteForm bestaat nog niet."*

---

## PAUZE (1:15-1:30)

### Slide 9: Pauze

> "Pauze! 15 minuten. Stap 0-5 staan er. Na de pauze bouwen we het interactieve hart: de VoteForm."

*Tim loopt rond en helpt studenten die achterlopen.*

---

## STAP 6: VOTEFORM (1:30-2:10)

*Dit is het langste en lastigste stuk. Neem de tijd.*

### Slide 10: Stap 6 deel 1 — Logica (1:30-1:50)

**💡 Theorie-moment: Client Components & useState**

> "`'use client'` bovenaan — dit component draait in de browser. Alles met useState, onClick, of interactiviteit MOET een Client Component zijn."

*Tim maakt src/components/VoteForm.tsx.*

> "Vier states."

*Tim typt elke state en legt uit.*

> "`selectedOption: number | null` — null want er kan nog niks geselecteerd zijn."
> "`hasVoted: boolean` — toggle na stemmen."
> "`isSubmitting: boolean` — voorkom dubbel klikken."
> "`currentPoll: Poll` — de actuele data, updated na stemmen."

*Checkpoint: "Heeft iedereen de vier useState regels?"*

*Tim typt handleVote.*

> "De handleVote functie: dezelfde fetch als in de console bij stap 4. POST naar de API, update state als het lukt."

*Checkpoint: "Tot hier mee?"*

---

### Slide 11: Stap 6 deel 2 — UI (1:50-2:10)

*Tim typt de JSX rustig, stopt na elk blok.*

> "De UI heeft twee toestanden. Vóór stemmen: selecteer een optie. Na stemmen: percentage bars."

*Tim typt de map over options.*

> "Elke optie is een button. onClick selecteert de optie — maar alleen als je nog niet gestemd hebt."

> "De conditional classes: `isSelected ? 'border-purple-500' : 'border-gray-200'`. Ternary operators in className — zo style je conditioneel in Tailwind."

*Tim typt de percentage bar.*

> "De percentage bar: een div met `absolute inset-0`, breedte is percentage, `transition-all duration-500` animeert. Pure CSS."

*Tim typt stem-knop en bedankt-bericht.*

> "Stem-knop verdwijnt na stemmen. Bedankt-bericht verschijnt. Allebei conditional rendering met `hasVoted`."

*Tim test: localhost:3000/poll/1 → klik optie → stem.*

> "Werkt het? Klik een optie, klik Stem, zie de animatie!"

*Tim loopt rond en helpt. **Neem hier 10-15 min extra als nodig.** Dit is het lastigste stuk.*

**Waar lopen studenten vast?**
- `"use client"` vergeten → crash
- `selectedOption` niet nullable (`number` i.p.v. `number | null`)
- Fetch URL zonder backticks (template literal)
- Tailwind class typos
- Vergeten om het bestand op te slaan

**Snelle fix:** "Check je terminal — TypeScript error? Heb je `'use client'` bovenaan?"

---

## STAP 7-8: FINISHING TOUCHES (2:10-2:45)

### Slide 12: Stap 7 — Loading, Error & Not-Found (2:10-2:30)

**💡 Theorie-moment: Speciale bestanden**

> "In gewoon React bouw je dit zelf. In Next.js: maak een bestand aan en het werkt."

*Tim maakt loading.tsx.*

> "Skeleton loading. `animate-pulse` — Tailwind class. Vijf regels, professioneel resultaat."

*Tim maakt error.tsx.*

> "Error boundary. **`'use client'` is verplicht!** Error boundaries draaien altijd in de browser. `reset` herlaadt het gefaalde component."

*Tim maakt not-found.tsx.*

> "404 pagina. Test: `/poll/999`. Daar is 'm. Werkt door `notFound()` in de poll pagina."

*Checkpoint: "Alle drie aangemaakt en werkend?"*

---

### Slide 13: Stap 8 — Middleware (2:30-2:45)

**💡 Theorie-moment: Middleware**

> "Middleware is een portier. Elke request passeert hier eerst."

*Tim maakt src/middleware.ts.*

> "Let op de locatie: `src/middleware.ts`. Niet in `app/`, niet in een subfolder. Dit is het enige bestand waar de locatie uitmaakt."

> "De `matcher` bepaalt welke routes het raakt. API routes en poll pagina's — de homepage skipt het."

> "Open je terminal. Klik op een poll. Zie je `[GET] /poll/1`? Dat is middleware in actie."

> "Nu loggen we alleen. Bij Supabase wordt dit authenticatie: is de user ingelogd? Zo niet, redirect."

*Checkpoint: "Logs in je terminal? App compleet!"*

---

## AFSLUITING (2:45-3:00)

### Slide 14: Huiswerk & Afsluiting

> "Wat hebben we vandaag gedaan? De hele QuickPoll app van scratch: types, layout, homepage, API routes, detail pagina, VoteForm met animaties, loading states, middleware."

> "De Next.js flow: Route → Page → Client Component → API Route → Response. Dat is het hele framework in één zin."

> "Huiswerk: app niet af? Afmaken. Wel af? Probeer de bonus: een '/create' pagina met een form en POST naar `/api/polls`."

> "Volgende les: Tailwind CSS en shadcn/ui. Dan geven we QuickPoll een styling upgrade."

> "In één les van nul naar werkende app. Goed gedaan!"

---

## POST-SESSIE

**Na afloop checken:**
- [ ] Hoeveel studenten hebben werkende VoteForm?
- [ ] Was het tempo goed? (stap 0-3 snel genoeg, stap 4-8 genoeg tijd?)
- [ ] Terugkerende problemen notieren voor v2
- [ ] Was van-scratch beter dan doorgaan waar Les 5 stopte?
