This episode dissects data fetching in the App Router: server-side fetch with async server components, static generation using generateStaticParams, Incremental Static Regeneration with revalidate, and route handlers for building a backend API.

A real application needs data. The key question isn't only how to fetch data, but when and where — on the server at build time, on the server per request, or on the client after the page loads. Episode 6 answers that question.
We'll cover server-side data fetching in the App Router, static generation with generateStaticParams, Incremental Static Regeneration (ISR) with revalidate, and route handlers for building a simple backend API.
In the App Router, a server component can be async and use fetch directly:
export default async function Posts() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts")
const posts = await res.json()
return (
<ul>
{posts.slice(0, 5).map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}The direct fetch inside the server component above runs on the server, without loading the client down. The key in list rendering from episode 0 — key={post.id} — avoids React warnings when a list is rendered.
Next.js's built-in fetch function supports caching and time-based revalidation:
const res = await fetch("https://api.example.com/products", {
next: { revalidate: 3600 },
})The next: { revalidate: 3600 } option caches the data and refreshes it in the background every hour. This is the basic mechanism of Incremental Static Regeneration.
For dynamic pages whose data rarely changes, combine generateStaticParams with static mode. All pages are built during npm run build:
export async function generateStaticParams() {
const res = await fetch("https://api.example.com/articles")
const articles = await res.json()
return articles.map((a) => ({ slug: a.slug }))
}
export default async function Article({ params }) {
const { slug } = await params
const res = await fetch(`https://api.example.com/articles/${slug}`)
const article = await res.json()
return <h1>{article.title}</h1>
}All article slugs are registered in generateStaticParams and prerendered at build time. Pages that aren't registered automatically return a 404 — unless the dynamicParams option is enabled.
ISR keeps static pages up to date. There are two ways to trigger revalidation: time-based with next: { revalidate: 60 }, or on-demand by calling an API:
import { revalidatePath } from "next/cache"
export async function POST(request) {
await request.json()
revalidatePath("/articles")
return new Response("Cache dibersihkan", { status: 200 })
}revalidatePath("/articles") forces the /articles page to be rebuilt when the endpoint above is called. This pattern is useful after data in a CMS is updated, without waiting for a time window.
The App Router builds APIs through a route.ts file that exports handlers based on HTTP methods. Create app/api/items/route.ts:
import { NextResponse } from "next/server"
export async function GET() {
return NextResponse.json({ items: ["apel", "mangga"] })
}
export async function POST(request) {
const body = await request.json()
return NextResponse.json({ created: body }, { status: 201 })
}The endpoint above is available at /api/items. NextResponse.json simplifies JSON responses, and the 201 status indicates the resource was created successfully. These route handlers are the foundation of a Next.js backend API that we'll secure in episode 13.
Here's what to take away:
In the next episode, episode 7, we'll discuss state management and hooks — local state with React hooks, the use client directive and server-client boundaries, shared state with Context API, Zustand, and TanStack Query, as well as the client component versus server component lifecycle. Your application's interactivity will start to take shape.