What Causes This Error?
This error occurs in Vue Router 4 (used by Nuxt 3) when using the old Vue Router 3 syntax for catch-all routes. The * wildcard syntax has been replaced with a parameter-based approach.
The Problem
// ❌ Old Vue Router 3 syntax (doesn't work in Vue Router 4)
const routes = [
{ path: '*', component: NotFound }
]
// ❌ Also wrong
const routes = [
{ path: '/docs/*', component: DocPage }
]
The Fix
Basic Catch-All (404 Page)
// ✅ Vue Router 4 syntax
const routes = [
{ path: '/:pathMatch(.*)*', component: NotFound }
]
In Nuxt, create a catch-all page:
pages/
└── [...slug].vue → Matches any path
<!-- pages/[...slug].vue -->
<script setup>
const route = useRoute()
// route.params.slug contains the path segments as array
// e.g., /foo/bar/baz → ['foo', 'bar', 'baz']
</script>
<template>
<div>
<h1>404 - Page Not Found</h1>
<p>Path: /{{ route.params.slug?.join('/') }}</p>
</div>
</template>
Nested Catch-All
pages/
└── docs/
└── [...slug].vue → /docs/*, e.g., /docs/getting-started/intro
<!-- pages/docs/[...slug].vue -->
<script setup>
const route = useRoute()
// /docs/getting-started/intro → ['getting-started', 'intro']
const slugPath = route.params.slug as string[]
</script>
<template>
<DocViewer :path="slugPath" />
</template>
Syntax Reference
Vue Router 3 → Vue Router 4
| Vue Router 3 | Vue Router 4 | Nuxt Pages |
|---|---|---|
* | /:pathMatch(.*)* | [...slug].vue |
/docs/* | /docs/:pathMatch(.*)* | docs/[...slug].vue |
/api/* | /api/:pathMatch(.*)* | api/[...slug].vue |
Parameter Variations
// Matches everything, including empty
'/:pathMatch(.*)*' // /foo/bar or /
// Matches at least one segment
'/:pathMatch(.*)+' // /foo/bar but NOT /
// Named parameter
'/:slug(.*)*' // Same as pathMatch, different name
Nuxt File Naming
Basic Catch-All
pages/
└── [...slug].vue → /:slug(.*)*
Optional Catch-All
pages/
└── [[...slug]].vue → /:slug(.*)* (also matches /)
Within Nested Structure
pages/
├── docs/
│ ├── index.vue → /docs
│ └── [...slug].vue → /docs/:slug(.*)*
└── [...slug].vue → /:slug(.*)* (fallback 404)
Accessing Catch-All Parameters
<script setup>
const route = useRoute()
// Type assertion for TypeScript
const slugSegments = route.params.slug as string[] | undefined
// Examples:
// URL: /docs/intro/getting-started
// slugSegments: ['intro', 'getting-started']
// URL: /docs
// slugSegments: undefined (if using [...]) or [] (if using [[...]])
// Build path back
const fullPath = slugSegments?.join('/') || ''
</script>
Priority and Ordering
Nuxt/Vue Router matches more specific routes first:
pages/
├── products/
│ ├── index.vue → /products (exact match first)
│ ├── [id].vue → /products/:id (single param)
│ └── [...slug].vue → /products/:slug(.*)* (catch-all last)
└── [...slug].vue → /:slug(.*)* (global fallback)
Manual Route Configuration
// nuxt.config.ts
export default defineNuxtConfig({
hooks: {
'pages:extend'(pages) {
// ✅ Correct catch-all syntax
pages.push({
path: '/:pathMatch(.*)*',
name: 'not-found',
file: '~/pages/404.vue'
})
}
}
})
Custom 404 Page
<!-- pages/[...slug].vue -->
<script setup>
// Set 404 status
setResponseStatus(404)
</script>
<template>
<div class="not-found">
<h1>404</h1>
<p>Page not found</p>
<NuxtLink to="/">Go home</NuxtLink>
</div>
</template>
Migration from Vue Router 3
Before (Vue Router 3)
{
path: '*',
name: '404',
component: NotFound
}
After (Vue Router 4/Nuxt 3)
{
path: '/:pathMatch(.*)*',
name: '404',
component: NotFound
}
Or just create pages/[...slug].vue in Nuxt.
Quick Checklist
- Replace
*with/:pathMatch(.*)* - Use
[...slug].vuefor catch-all pages in Nuxt - Use
[[...slug]].vuefor optional catch-all - Access params via
route.params.slug(array) - Place catch-all routes last for proper priority
- Set 404 status in catch-all pages