Files
novi-lessons/Les06-NextJS-QuickPoll-Part2 2/Les06-Docenttekst.md
2026-03-17 17:24:10 +01:00

9.7 KiB

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?