Catch-All Routes

[...slug] Captures Multiple Segments

Catch-All Routes

A bracketed filename with a spread prefix matches any number of segments — perfect for docs, CMS content, and file browsers.

4 min read Level 2/5 #nuxt#routing#catch-all
What you'll learn
  • Create pages/docs/[...slug].vue
  • Read the array of segments from useRoute
  • Use catch-all routes for slug-based CMS content

A regular [slug] matches exactly one segment. A catch-all uses the spread prefix — [...slug] — and matches any number of segments. It is the right tool for docs sites, CMS-rendered pages, and arbitrary path hierarchies.

Anatomy of a Catch-All

pages/docs/[...slug].vue

This single file matches every URL that begins with /docs/:

/docs params.slug = []
/docs/getting-started params.slug = ['getting-started']
/docs/api/reference/cli params.slug = ['api', 'reference', 'cli']

The captured segments arrive as an array of strings.

A CMS-Style Page

<!-- pages/docs/[...slug].vue -->
<script setup lang="ts">
const route = useRoute()
const path = (route.params.slug as string[]).join('/')

const { data: doc } = await useFetch(`/api/docs/${path}`)
</script>

<template>
  <article v-if="doc">
    <h1>{{ doc.title }}</h1>
    <div v-html="doc.html" />
  </article>
  <p v-else>Not found</p>
</template>

The page hands the joined path to an API endpoint that loads markdown, MDX, or CMS content for that slug.

Optional Segments

Sometimes you want a parameter that may or may not be present. Double-bracket syntax marks it optional:

pages/blog/[[page]].vue
# matches both /blog and /blog/2

Inside the page, route.params.page is either a string or undefined.

Catch-alls are also commonly paired with a custom error.vue so unknown CMS slugs render a friendly 404 — we will get there in the error page lesson.

NuxtLink & Prefetching →