Fix: Catch All Routes Must Use Param with Custom Regexp in Vue Router

Error message:
Catch all routes (\"*\") must now be defined using a param with a custom regexp.
navigation 2025-01-25

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 3Vue Router 4Nuxt 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].vue for catch-all pages in Nuxt
  • Use [[...slug]].vue for 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