# 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?