This episode covers Nuxt routing: file-based routing in the pages folder, dynamic routes with parameters, nested routes with a parent layout, navigation between pages using NuxtLink, and middleware as navigation guards to protect route access.

Every application needs navigation. In Nuxt, routing is not something you configure in one big file — it is determined by the pages folder structure. Create a file and you get a route; remove the file and the route disappears with it. This is called file-based routing, and it is one of the reasons Nuxt feels so fast to develop with.
Episode 4 covers routing patterns comprehensively: basic routes, dynamic routes for pages with parameters like product details, nested routes for hierarchical pages, navigation between pages with <NuxtLink>, and middleware as route guards. By the end of this episode, the belajar-shop project will have a catalog page, product detail pages, and a cart page.
The basic rule is simple: the file app/pages/about.vue becomes the route /about, and app/pages/index.vue becomes the main page /. Let's create three basic store pages:
<template>
<section>
<h1>Katalog Produk</h1>
<p>Semua produk belajar-shop.</p>
</section>
</template>Open http://localhost:3000/produk and you will see this page. No router is registered manually — Nuxt generates the route from the filename.
To read route information like path and query, use useRoute:
const route = useRoute()
console.log(route.path)route.path returns the current path, and route.query contains the query string object from the URL.
For detail pages whose id varies, use square brackets in the filename:
<script setup lang="ts">
const route = useRoute()
const { data: produk } = await useFetch(`/api/produk/${route.params.id}`)
</script>
<template>
<article>
<h1>{{ produk?.nama }}</h1>
<p>{{ produk?.deskripsi }}</p>
</article>
</template>The [id].vue file captures every route /produk/1, /produk/2, and so on. The URL segment value can be read through route.params.id. An important note: strings inside templates like {{ produk?.nama }} are written in the template, not in prose.
If you want to capture every segment below a single path, use [...slug].vue. This pattern is useful for documentation or nested news pages like /dokumen/panduan/pemasangan.
To create hierarchical pages that share a common part, use folders: produk/index.vue for /produk and produk/ulasan.vue for /produk/ulasan. This structure keeps both URLs and the codebase tidy.
Don't use a plain <a> tag for internal navigation — that causes a full page reload. Use <NuxtLink>, which leverages client-side navigation:
<template>
<nav>
<NuxtLink to="/">Beranda</NuxtLink>
<NuxtLink to="/produk">Produk</NuxtLink>
<NuxtLink to="/produk/1">Produk Pertama</NuxtLink>
</nav>
</template><NuxtLink to="/produk/1"> renders a regular anchor tag for SEO, but handles the click with internal navigation so there is no full reload. Link prefetching also happens automatically — the details are in episode 15.
To create a link to a product detail page, combine template literals:
<NuxtLink :to="`/produk/${produk.id}`">
Lihat produk
</NuxtLink>The :to="`/produk/${produk.id}`" pattern is the common dynamic navigation used when rendering product lists from data.
Middleware is a function run before navigation completes, usually for authentication checks or redirects. There are two types: global middleware used on every page, and per-route middleware declared with definePageMeta.
export default defineRouteMiddleware(() => {
const token = useCookie("token").value
if (!token) {
return navigateTo("/login")
}
})Files ending in .global in the middleware folder automatically run for every navigation. defineRouteMiddleware((to) => {...}) receives the destination route as an argument, and navigateTo("/login") stops navigation and redirects to the login page.
To restrict a specific page, create a named middleware and register it on the page:
export default defineRouteMiddleware(() => {
if (!useCookie("token").value) {
return navigateTo("/login")
}
})<script setup lang="ts">
definePageMeta({ middleware: "auth" })
</script>definePageMeta({ middleware: "auth" }) connects that page with the auth middleware. In episode 12 we will extend this pattern into a more complete Role-Based Access Control.
Episode 4 teaches Nuxt's routing language: file-based routing that is cheap to maintain, dynamic routes with parameters for data pages, nested routes for hierarchical structures, <NuxtLink> for fast navigation, and middleware to guard access.
Key takeaways:
pages automatically becomes one route.[id].vue, and are read through route.params.<NuxtLink> is the correct way to navigate internally because it avoids full reloads..global suffix; per-route middleware is registered with definePageMeta.navigateTo from within middleware stops and redirects navigation.pages folder structure mirrors the application's URL structure.In the next episode, episode 5, we will discuss components and layouts — creating reusable components with SFC, layouts and nested layouts for page scaffolding, slots for component composition, and styling with CSS Modules, Tailwind, and scoped styles. Your store pages will start to look complete.