Next.js: Routing, Server Components and Data
Next.js is a React framework that adds routing, server rendering, data fetching and deployment conventions. Pages render on the server, so they load fast and are indexable by search engines.
npx create-next-app@latest my-site
File-based routing (App Router)
app/
├── layout.jsx # shared layout (header, footer)
├── page.jsx # /
├── projects/
│ ├── page.jsx # /projects
│ └── [slug]/
│ └── page.jsx # /projects/booking-app
└── api/
└── contact/
└── route.js # POST /api/contact
Server components by default
Components in app/ run on the server unless marked otherwise. They can fetch data directly and send only HTML to the browser:
// app/projects/page.jsx
export default async function ProjectsPage() {
const res = await fetch("https://api.example.com/v1/projects", { next: { revalidate: 300 } });
const { data } = await res.json();
return (
<main>
<h1>Projects</h1>
{data.map(p => <a key={p.id} href={`/projects/${p.slug}`}>{p.title}</a>)}
</main>
);
}
revalidate: 300 caches the result and refreshes it at most every five minutes.
Server components can safely use secrets (API keys, database connections), because their code never reaches the browser.
Client components for interactivity
Anything using state, effects or event handlers needs "use client" at the top:
"use client";
import { useState } from "react";
export function LikeButton({ initial }) {
const [likes, setLikes] = useState(initial);
return <button onClick={() => setLikes(l => l + 1)}>♥ {likes}</button>;
}
Keep client components small and leafy; let the server do the rest.
Dynamic routes and metadata
// app/projects/[slug]/page.jsx
export async function generateMetadata({ params }) {
const { slug } = await params;
const project = await getProject(slug);
return { title: `${project.title} | PandaDev`, description: project.summary };
}
export default async function ProjectPage({ params }) {
const { slug } = await params;
const project = await getProject(slug);
if (!project) notFound();
return <article><h1>{project.title}</h1><p>{project.summary}</p></article>;
}
Route handlers (API routes)
// app/api/contact/route.js
export async function POST(request) {
const body = await request.json();
if (!body.email || !body.message) {
return Response.json({ error: "Email and message are required." }, { status: 400 });
}
await saveLead(body);
return Response.json({ ok: true }, { status: 201 });
}
Validate input on the server even if the form validates in the browser.
Exercise
Create a Next.js site with a home page, a /projects list fetched on the server, dynamic /projects/[slug] pages with metadata and a notFound() for unknown slugs, and a client-side like button.
Check yourself
- Where do server components run, and what can they safely use?
- When do you need
"use client"? - What does
revalidatedo?
Your progress is saved in this browser only.
Submit for review (10 points)