Uitleg â Hoe werkt data fetching?
In Next.js Server Components haal je data op met gewone fetch() en async/await. Geen useEffect of useState nodig.
đĄ Vergelijk met gewone React
React (Vite) â veel boilerplate:
const [workouts, setWorkouts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/workouts')
.then(res => res.json())
.then(data => {
setWorkouts(data);
setLoading(false);
});
}, []);
Next.js Server Component â simpel en direct:
const res = await fetch('http://localhost:3000/api/workouts');
const workouts = await res.json();
Stap 1 â Update de overzichtspagina
Vervang de inhoud van app/workouts/page.js zodat het echte data ophaalt van de API Ên het formulier toont:
// app/workouts/page.js
import Link from 'next/link';
import AddWorkoutForm from '../components/AddWorkoutForm';
async function getWorkouts() {
// Let op: volledige URL nodig in Server Components
const res = await fetch('http://localhost:3000/api/workouts', {
cache: 'no-store', // Altijd verse data ophalen
});
if (!res.ok) {
throw new Error('Ophalen workouts mislukt');
}
return res.json();
}
export default async function WorkoutsPage() {
const workouts = await getWorkouts();
return (
<main>
<h1>Mijn Workouts</h1>
<AddWorkoutForm />
{workouts.length === 0 ? (
<p>Nog geen workouts. Voeg er een toe!</p>
) : (
<ul>
{workouts.map((workout) => (
<li key={workout._id}>
<Link href={`/workouts/${workout._id}`}>
<strong>{workout.title}</strong>
</Link>
â {workout.reps} reps @ {workout.load}kg
</li>
))}
</ul>
)}
</main>
);
}
- Vanaf Next.js 15 is
no-storede standaard â fetch requests worden niet gecacht - Je kunt
cache: 'no-store'expliciet meegeven om dit duidelijk te maken - Wil je juist cachen? Gebruik dan
cache: 'force-cache'
Stap 2 â Maak een laadscherm aan
Maak een nieuw bestand loading.js aan in de app/workouts/ map:
// app/workouts/loading.js
export default function Loading() {
return (
<main>
<h1>Mijn Workouts</h1>
<p>Workouts laden...</p>
</main>
);
}
đĄ Automatisch!
Next.js toont loading.js automatisch terwijl je pagina data ophaalt. Je hoeft niets extra's te doen.
Stap 3 â Update de detailpagina
Vervang de inhoud van app/workouts/[id]/page.js zodat het echte data ophaalt op basis van het ID:
đĄ Wat doet notFound()?
notFound() stopt de render meteen en toont de dichtstbijzijnde not-found.js pagina. Je hoeft dus niet zelf een if (!workout) return ... in je JSX te schrijven.
// app/workouts/[id]/page.js
import { notFound } from 'next/navigation';
import Link from 'next/link';
import DeleteButton from '../../components/DeleteButton';
async function getWorkout(id) {
const res = await fetch(`http://localhost:3000/api/workouts/${id}`, {
cache: 'no-store',
});
if (res.status === 404) {
notFound(); // Stopt de render en toont app/not-found.js
}
if (!res.ok) {
throw new Error('Ophalen workout mislukt');
}
return res.json();
}
export default async function WorkoutDetailPage({ params }) {
const { id } = await params;
const workout = await getWorkout(id);
return (
<main>
<Link href="/workouts">â Terug naar overzicht</Link>
<h1>{workout.title}</h1>
<p>Reps: {workout.reps}</p>
<p>Gewicht: {workout.load}kg</p>
<p>Aangemaakt: {new Date(workout.createdAt).toLocaleDateString('nl-NL')}</p>
<DeleteButton id={workout._id} />
</main>
);
}
Stap 4 â Maak de verwijderknop aan
Maak een nieuw bestand app/components/DeleteButton.js aan. Dit heeft interactie nodig â het is een Client Component:
// app/components/DeleteButton.js
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function DeleteButton({ id }) {
const [deleting, setDeleting] = useState(false);
const router = useRouter();
async function handleDelete() {
setDeleting(true);
const res = await fetch(`/api/workouts/${id}`, {
method: 'DELETE',
});
if (res.ok) {
router.push('/workouts'); // Stuur terug naar overzicht
} else {
setDeleting(false); // Fout â zet knop terug
}
}
return (
<button onClick={handleDelete} disabled={deleting} style={{ color: 'red' }}>
{deleting ? 'Verwijderen...' : 'Verwijder Workout'}
</button>
);
}
deletingstate â knop toont "Verwijderen..." en is uitgeschakeld terwijl het bezig isrouter.push('/workouts')â stuurt de gebruiker direct terug naar het overzicht na verwijderen- Bij een fout wordt de knop weer actief zodat de gebruiker het opnieuw kan proberen
Stap 5 â Ververs de lijst na toevoegen
Na het versturen van het formulier moet de lijst ververst worden. Update AddWorkoutForm.js:
// In AddWorkoutForm.js â voeg toe aan imports
import { useRouter } from 'next/navigation';
// Voeg toe bovenaan het component (naast de andere useState regels)
const router = useRouter();
// In handleSubmit, na de succesvolle POST en het resetten van de velden:
router.refresh(); // â ververs de server data
â Wat je nu hebt
Na deze stap heb je een werkende fullstack app:
/workoutsâ alle workouts ophalen en tonen- Formulier om een nieuwe workout toe te voegen
/workouts/:idâ detailpagina per workout- Workout verwijderen en terug naar overzicht
revalidatePath
revalidatePath is de server-side manier om Next.js te vertellen dat de cache voor een bepaalde route verouderd is. Dit werkt beter dan router.refresh(), want de cache wordt aan de serverkant geleegd â niet pas als de gebruiker de pagina herlaadt.
đĄ Verschil met router.refresh()
| router.refresh() | revalidatePath() |
|---|---|
| Draait in de browser (Client Component) | Draait op de server (API route) |
| Herlaadt data voor de huidige gebruiker | Leegt de cache voor alle gebruikers |
Voeg revalidatePath toe aan je API routes, direct na een schrijfactie:
// app/api/workouts/route.js
import { NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache'; // â importeer
import connectDB from '../../../lib/mongodb';
import Workout from '../../../models/Workout';
export async function POST(request) {
await connectDB();
const body = await request.json();
const { title, reps, load } = body;
if (!title || !reps || !load) {
return NextResponse.json({ error: 'Vul alle velden in' }, { status: 400 });
}
const workout = await Workout.create({ title, reps, load });
revalidatePath('/workouts'); // â cache legen na toevoegen
return NextResponse.json(workout, { status: 201 });
}
// app/api/workouts/[id]/route.js â in de DELETE functie
export async function DELETE(request, { params }) {
await connectDB();
const { id } = await params;
const workout = await Workout.findByIdAndDelete(id);
if (!workout) {
return NextResponse.json({ error: 'Workout niet gevonden' }, { status: 404 });
}
revalidatePath('/workouts'); // â cache legen na verwijderen
return NextResponse.json({ message: 'Workout verwijderd' }, { status: 200 });
}
Let op
revalidatePath kan alleen in server-side code (API routes, Server Actions). Niet in Client Components. Gebruik router.refresh() als je het vanuit een Client Component wilt doen.
Checklist
â Check of je hebt:
- Workouts worden opgehaald van
/api/workouts - Lijst zichtbaar op
/workouts loading.jsaangemaakt- Detailpagina toont data van de juiste workout
- Verwijder knop werkt en navigeert terug
- Formulier ververst de lijst na toevoegen
Volgende Stap
App werkt! Tijd om hem mooi te maken met styling.
Voeg inloggen toe aan je Workout Tracker