This episode digs into routing in the App Router: page and layout files, dynamic routes, catch-all routes, optional catch-all, the Link component and useRouter for navigation, and the metadata API for SEO and page descriptions.

Now that your project is up from episode 3, it's time to give the application more than one page. Routing determines how URLs map to code, and navigation determines how users move between pages.
Episode 4 digs into the App Router routing system: the segment concept, page.tsx and layout.tsx files, dynamic routes with parameters, catch-all routes that capture many segments, the Link component for client-side navigation, and the metadata API for SEO.
Every folder inside app is a segment that corresponds to a part of the URL. The folder app/about/page.tsx automatically becomes the /about page, with no manual router registration needed. This is the file-system routing pattern that makes the application structure visible directly from the folders.
A layout is a shared frame. The layout in the app/blog folder wraps all the pages beneath it:
export default function BlogLayout({ children }) {
return (
<section>
<nav>Navigasi Blog</nav>
{children}
</section>
)
}The file above is app/blog/layout.tsx. The children content is filled with the active page — so the blog navbar appears on every blog page without being written again.
For pages that depend on a dynamic value like an article slug, create a folder with square brackets. The file app/blog/[slug]/page.tsx receives params containing the slug:
export default async function BlogPost({ params }) {
const { slug } = await params
return (
<article>
<h1>Artikel dengan slug: {slug}</h1>
</article>
)
}The await params above is required in Next.js 15 because params is a Promise. When you open /blog/pengenalan-react, the slug value is pengenalan-react.
If a single page must handle any number of segments, use a catch-all with three dots. The folder app/docs/[...slug]/page.tsx captures URLs like /docs/getting-started/intro. To also capture /docs with no segment, use the optional catch-all: the folder app/docs/[[...slug]]/page.tsx. The only difference is the extra set of square brackets around the parameter.
For dynamic pages that use SSG, register all the parameter values at build time:
export async function generateStaticParams() {
return [
{ slug: "pengenalan-react" },
{ slug: "panduan-nextjs" },
]
}The function generateStaticParams returns the list of parameters that will be prerendered during npm run build. We'll cover the full details in episode 6.
To move between pages without reloading the whole document, use the Link component. Import it from next/link and use the href prop:
import Link from "next/link"
export default function Navbar() {
return (
<nav>
<Link href="/">Beranda</Link>
<Link href="/blog">Blog</Link>
<Link href="/blog/pengenalan-react">Artikel</Link>
</nav>
)
}Link triggers prefetching: Next.js loads the destination page ahead of time when the link becomes visible in the viewport, making navigation feel instant. When the user clicks, the transition happens without a full reload.
For navigation triggered from code — for example after a form is submitted — use the useRouter hook:
"use client"
import { useRouter } from "next/navigation"
export default function LoginButton() {
const router = useRouter()
return (
<button onClick={() => router.push("/dashboard")}>
Masuk Dashboard
</button>
)
}router.push("/dashboard") redirects the user programmatically. Note the "use client" directive on the first line — hooks like useRouter only run in client components, a concept we'll break down in episode 7.
The App Router provides a metadata API for controlling the title, description, and meta tags of every page. Export a metadata object from layout.tsx or page.tsx:
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "Beranda | Aplikasi Saya",
description: "Halaman beranda aplikasi Next.js",
openGraph: {
title: "Aplikasi Saya",
description: "Halaman beranda aplikasi Next.js",
},
}The metadata object above produces the title and description tags for SEO as well as Open Graph tags for social media. For dynamic pages, the generateMetadata function can generate metadata based on params — an advanced topic in episode 17.
Here's what to take away:
generateStaticParams sets the list of parameters for SSG.Link component provides client-side navigation with prefetching.useRouter handles programmatic navigation from code.In the next episode, episode 5, we'll discuss components and layouts — reusable components, nested layouts and templates, shared layouts for dynamic segments, loading UI with loading files, and styling with CSS modules, Tailwind CSS, and styled-components. Your application's UI structure will start to mature.