[...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.
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 →