Uitleg — Server vs Client Components

💡 Simpele uitleg

  • Server Component — draait op de server. De browser krijgt alleen HTML. Kan direct data ophalen, heeft geen useState of onClick.
  • Client Component — draait in de browser. Kan useState, useEffect en event handlers gebruiken. Voeg 'use client' bovenaan toe.
Server Component Client Component
Waar draait het? Server Browser
Directive nodig? Nee (standaard) 'use client' bovenaan
useState / useEffect ❌ Niet mogelijk ✅ Ja
Event handlers (onClick) ❌ Niet mogelijk ✅ Ja
Direct data ophalen ✅ Ja (async/await) Via useEffect
Browser APIs (localStorage) ❌ Niet mogelijk ✅ Ja

💡 Vuistregel

Begin altijd met een Server Component. Voeg 'use client' alleen toe als je state, events of browser-APIs nodig hebt.

Uitleg — Hoe ziet een Server Component eruit?

Een Server Component is een gewone async functie — geen extra code nodig. Je kunt direct await gebruiken om data op te halen.

// app/workouts/page.js
// Geen 'use client' → dit is automatisch een Server Component

async function getWorkouts() {
  const res = await fetch('http://localhost:3000/api/workouts');
  return res.json();
}

export default async function WorkoutsPage() {
  const workouts = await getWorkouts(); // ← direct awaiten, geen useEffect nodig

  return (
    <main>
      <h1>Mijn Workouts</h1>
      <ul>
        {workouts.map((workout) => (
          <li key={workout._id}>
            {workout.title} — {workout.reps} reps @ {workout.load}kg
          </li>
        ))}
      </ul>
    </main>
  );
}

Uitleg — Hoe ziet een Client Component eruit?

Zodra je useState, useEffect of event handlers (zoals onClick) nodig hebt, zet je 'use client' als allereerste regel bovenaan het bestand.

// app/components/Voorbeeld.js
'use client'; // ← eerste regel, dit maakt het een Client Component

import { useState } from 'react';

export default function Voorbeeld() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

⚠️ Veelgemaakte fout

Als je useState of onClick gebruikt zonder 'use client', krijg je een foutmelding:
"You're importing a component that needs useState. It only works in a Client Component..."

Oplossing: Voeg 'use client' toe als eerste regel van het bestand.

Uitleg — Wanneer gebruik je wat?

Server Component als je:

  • Data ophaalt van een API of database
  • Geen interactie nodig hebt (geen knoppen, forms)
  • Pagina's of layouts bouwt

Client Component als je:

  • useState of useReducer nodig hebt
  • useEffect gebruikt
  • Event handlers hebt (onClick, onChange, onSubmit)
  • Browser APIs gebruikt (localStorage, window)

Uitleg — Server en Client samen gebruiken

Een Server Component kan een Client Component renderen. Zo houd je data fetching op de server en zet je alleen het interactieve deel als Client Component.

// app/workouts/page.js  ← Server Component
import AddWorkoutForm from '../components/AddWorkoutForm'; // ← Client Component

export default async function WorkoutsPage() {
  // data fetching op de server...

  return (
    <main>
      <AddWorkoutForm />  {/* Client Component in Server Component: ✅ */}
      {/* rest van de pagina */}
    </main>
  );
}

Stap 1 — Maak het formulier component aan

Maak een nieuwe map components aan in app/ en maak daarin een nieuw bestand:

app/
└── components/
    └── AddWorkoutForm.js  ← nieuw bestand aanmaken

Zet deze code in app/components/AddWorkoutForm.js:

// app/components/AddWorkoutForm.js
'use client';

import { useState } from 'react';

export default function AddWorkoutForm() {
  const [title, setTitle] = useState('');
  const [reps, setReps] = useState('');
  const [load, setLoad] = useState('');
  const [error, setError] = useState(null);

  async function handleSubmit(e) {
    e.preventDefault();
    setError(null);

    const workout = { title, reps: Number(reps), load: Number(load) };

    const res = await fetch('/api/workouts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(workout),
    });

    if (!res.ok) {
      const data = await res.json();
      setError(data.error);
      return;
    }

    // Reset form
    setTitle('');
    setReps('');
    setLoad('');
  }

  return (
    <form onSubmit={handleSubmit}>
      <h3>Workout Toevoegen</h3>

      <input
        type="text"
        placeholder="Oefening (bijv. Push Day)"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <input
        type="number"
        placeholder="Reps"
        value={reps}
        onChange={(e) => setReps(e.target.value)}
      />
      <input
        type="number"
        placeholder="Gewicht (kg)"
        value={load}
        onChange={(e) => setLoad(e.target.value)}
      />

      <button type="submit">Toevoegen</button>

      {error && <p style={{ color: 'red' }}>{error}</p>}
    </form>
  );
}

✅ Wat je nu hebt

Een formulier klaar voor gebruik. Het stuurt straks POST requests naar /api/workouts — die route bouw je in de API Routes stap.

Checklist

✅ Check of je hebt:

  • Verschil tussen Server en Client Components begrepen
  • app/components/AddWorkoutForm.js aangemaakt met 'use client'
  • Formulier zichtbaar op de workouts pagina
  • Geen errors in de console over ontbrekende 'use client'

Volgende Stap

Components begrijpen we nu. Tijd om echte data op te halen!

API Routes →

Bouw eerst de backend endpoints voordat je data ophaalt