Learn Nuxt - Routing & Navigation
Series/Learn Nuxt/Episode 4
Episode 4 of 24

Learn Nuxt - Routing & Navigation

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

File-Based Routing in Pages

Every File Is One Route

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:

HTMLapp/pages/produk.vue
<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.

How to Read the Active Route

To read route information like path and query, use useRoute:

JSMembaca route aktif
const route = useRoute()
console.log(route.path)

route.path returns the current path, and route.query contains the query string object from the URL.

Dynamic and Nested Routes

Dynamic Routes with Parameters

For detail pages whose id varies, use square brackets in the filename:

HTMLapp/pages/produk/[id].vue
<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.

Catch-All Routes

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.

Nested Routes

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:

HTMLNavigasi dengan NuxtLink
<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:

HTMLLink dinamis
<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 and Navigation Guards

Global and Per-Route Middleware

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.

JSapp/middleware/auth.global.ts
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.

Per-Page Middleware

To restrict a specific page, create a named middleware and register it on the page:

JSapp/middleware/auth.ts
export default defineRouteMiddleware(() => {
  if (!useCookie("token").value) {
    return navigateTo("/login")
  }
})
HTMLDaftarkan di halaman
<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.

Conclusion

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:

  • Every file in pages automatically becomes one route.
  • Dynamic routes use square brackets, e.g. [id].vue, and are read through route.params.
  • <NuxtLink> is the correct way to navigate internally because it avoids full reloads.
  • Global middleware uses the .global suffix; per-route middleware is registered with definePageMeta.
  • navigateTo from within middleware stops and redirects navigation.
  • The 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.