Dynamic Routes in Nuxt for SEO · Nuxt SEO

[NuxtSEO](https://nuxtseo.com/ "Home")

- [Modules](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction)
- [Tools](https://nuxtseo.com/tools)
- [Pro](https://nuxtseo.com/pro)
- [Learn SEO](https://nuxtseo.com/learn-seo/nuxt) [Releases](https://nuxtseo.com/releases)

[1.4K](https://github.com/harlan-zw/nuxt-seo)

[Nuxt SEO on GitHub](https://github.com/harlan-zw/nuxt-seo)

Learn SEO

Master search optimization

Nuxt

 Vue

[SEO Checklist](https://nuxtseo.com/learn-seo/checklist) [Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup) [Backlinks & Authority](https://nuxtseo.com/learn-seo/backlinks)

[Mastering Meta](https://nuxtseo.com/learn-seo/nuxt/mastering-meta)

- [Titles](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/titles)
- [Meta Description](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/descriptions)
- [Image Alt Text](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/alt-text)
- [Social Sharing](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph)
- [Rich Results](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results)
- [Schema.org](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org)
- [Twitter Cards](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/twitter-cards)

[ Controlling Crawlers](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers)

- [Robots Txt](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt)
- [Sitemaps](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps)
- [Robot Meta Tag](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags)
- [Canonical Link Tag](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls)
- [HTTP Redirects](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects)
- [Duplicate Content](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content)
- [llms.txt](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt)

[ Routes & Rendering](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)

- [URL Structure](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure)
- [Pagination](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination)
- [Trailing Slashes](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes)
- [Query Parameters](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/query-parameters)
- [Hreflang & i18n](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/i18n)
- [404 Pages](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages)
- [Dynamic Routes](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes)
- [Internal Linking](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/internal-linking)
- [Programmatic SEO](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/programmatic-seo)
- [Rendering Modes](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering)
- [Security](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/security)

[ Launch & Listen](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen)

- [Getting Indexed](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live)
- [Google Search Console](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/search-console)
- [Core Web Vitals](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/core-web-vitals)
- [Indexing Issues](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues)
- [SEO Monitoring](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/seo-monitoring)
- [Site Migration](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/site-migration)
- [IndexNow](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexnow)
- [Debugging](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/debugging)
- [AI Search Optimization](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content)

1. [Learn SEO for Nuxt](https://nuxtseo.com/learn-seo)
2.
3. [Routes And Rendering](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)
4.
5. [Dynamic Routes](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes)

# Dynamic Routes in Nuxt for SEO

How to use Nuxt's file-based dynamic routes, set per-route meta tags, and avoid duplicate content issues with URL parameters.

[![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan-zw)10 mins read Published Dec 17, 2025 Updated Jan 29, 2026

Dynamic routes generate clean URLs from parameters. `/blog/vue-seo-guide` instead of `/blog?id=123`. Search engines prefer semantic paths over query parameters. In 2026, Nuxt 4's optimized **Soft Navigations** ensure that transition between dynamic routes are nearly instant and fully indexable.

## [Route Params vs Query Parameters](#route-params-vs-query-parameters)

Google treats these differently:

```
✅ /products/electronics/laptop
❌ /products?category=electronics&item=laptop
```

Route params create semantic URLs. Query parameters generate duplicate content issues. Google sees infinite URL variations when you add filters, sorting, or tracking params.

From [Google's 2025 URL structure guidelines](https://developers.google.com/search/docs/crawling-indexing/url-structure): use clean paths for important content, reserve query parameters for filters that shouldn't be indexed.

## [Basic Dynamic Routes](#basic-dynamic-routes)

Create files with `[param]` syntax in the `pages/` directory:

```
pages/
├── blog/
│   └── [slug].vue          → /blog/:slug
├── products/
│   └── [category]/
│       └── [id].vue        → /products/:category/:id
```

This generates:

- `/blog/vue-ssr-guide`
- `/products/electronics/123`

Access params in components via `route.params`:

pages/blog/[slug].vue

```
<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug // "vue-ssr-guide"
</script>
```

## [Per-Route Meta Tags](#per-route-meta-tags)

Search engines need unique titles and descriptions for each dynamic route. Fetch data and set meta tags with Nuxt's data fetching composables.

### [Dynamic Meta from Data](#dynamic-meta-from-data)

Fetch data and set specific meta tags per page:

pages/blog/[slug].vue

```
<script setup lang="ts">
const route = useRoute()

// Nuxt handles SSR automatically
const { data: post } = await useFetch(\`/api/posts/${route.params.slug}\`)

// Or with useAsyncData
const { data: post } = await useAsyncData(\`post-${route.params.slug}\`, () =>
  fetchPost(route.params.slug))

// Set SEO meta tags
useSeoMeta({
  title: post.value?.title,
  description: post.value?.excerpt,
  ogTitle: post.value?.title,
  ogDescription: post.value?.excerpt,
  ogImage: post.value?.coverImage
})
</script>

<template>
  <article>
    <h1>{{ post?.title }}</h1>
    <p>{{ post?.content }}</p>
  </article>
</template>
```

Nuxt's `useFetch()` and `useAsyncData()` work automatically during SSR. search engines see complete HTML with correct meta tags. No additional setup required.

### [Using Nuxt Content](#using-nuxt-content)

For content-driven sites, integrate with `@nuxt/content`:

pages/blog/[slug].vue

```
<script setup lang="ts">
const route = useRoute()

const { data: post } = await useAsyncData(\`post-${route.params.slug}\`, () =>
  queryContent('/blog').where({ slug: route.params.slug }).findOne())

if (!post.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Post not found'
  })
}

useSeoMeta({
  title: post.value.title,
  description: post.value.description
})
</script>

<template>
  <ContentRenderer :value="post" />
</template>
```

## [Multiple Route Params](#multiple-route-params)

Combine params with nested directories:

```
pages/
└── docs/
    └── [category]/
        └── [page].vue      → /docs/:category/:page
```

Generates: `/docs/getting-started/installation`

Access all params:

pages/docs/[category]/[page].vue

```
<script setup lang="ts">
const route = useRoute()
const { category, page } = route.params

// Fetch content based on both params
const { data: doc } = await useFetch(\`/api/docs/${category}/${page}\`)

useSeoMeta({
  title: doc.value?.title,
  description: doc.value?.excerpt
})
</script>
```

## [Optional Params](#optional-params)

Nuxt supports optional params with multiple page files:

```
pages/
└── blog/
    ├── [slug].vue          → /blog/:slug
    └── [category]/
        └── [slug].vue      → /blog/:category/:slug
```

Matches both:

- `/blog/vue-guide` (category undefined)
- `/blog/tutorials/vue-guide` (category = "tutorials")

Handle optional params in components:

```
<script setup lang="ts">
const route = useRoute()
const category = route.params.category || 'general'
</script>
```

## [Catch-All Routes](#catch-all-routes)

Use `[...slug].vue` for catch-all patterns:

```
pages/
└── [...slug].vue           → Matches any path
```

Or nested catch-all:

```
pages/
└── files/
    └── [...path].vue       → /files/* matches all nested paths
```

Access the full path:

pages/files/[...path].vue

```
<script setup lang="ts">
const route = useRoute()
const path = route.params.path // Array: ['docs', 'api.md']
const fullPath = Array.isArray(path) ? path.join('/') : path
</script>
```

### [404 Pages](#_404-pages)

Create a catch-all for 404 handling:

pages/[...slug].vue

```
<script setup lang="ts">
useHead({
  title: '404 - Page Not Found',
  meta: [
    { name: 'robots', content: 'noindex, nofollow' }
  ]
})
</script>

<template>
  <div>
    <h1>404 - Page Not Found</h1>
  </div>
</template>
```

Or use Nuxt's dedicated error page:

error.vue

```
<script setup lang="ts">
const error = useError()

useHead({
  title: \`${error.statusCode} - ${error.statusMessage}\`,
  meta: [
    { name: 'robots', content: 'noindex, nofollow' }
  ]
})
</script>

<template>
  <div>
    <h1>{{ error.statusCode }}: {{ error.statusMessage }}</h1>
  </div>
</template>
```

## [Programmatic Navigation](#programmatic-navigation)

Navigate to dynamic routes with `navigateTo()`:

```
<script setup lang="ts">
function viewPost(slug: string) {
  // Updates URL and triggers meta updates
  navigateTo(\`/blog/${slug}\`)
}

function viewProduct(category: string, id: number) {
  navigateTo(\`/products/${category}/${id}\`)
}
</script>
```

Or use `useRouter()`:

```
<script setup lang="ts">
const router = useRouter()

function viewPost(slug: string) {
  navigateTo(\`/blog/${slug}\`)
}
</script>
```

## [Common SEO Issues](#common-seo-issues)

### [Issue 1: Duplicate Content from Query Params](#issue-1-duplicate-content-from-query-params)

Mixing route params and query params creates duplicates:

```
/blog/vue-guide
/blog/vue-guide?ref=twitter
/blog/vue-guide?utm_source=newsletter
```

Google indexes these as separate pages. Fix with canonical tags:

```
<script setup lang="ts">
const route = useRoute()

// Strip query params from canonical URL
const canonicalUrl = computed(() => {
  return \`https://yoursite.com${route.path}\`
})

useHead({
  link: [
    { rel: 'canonical', href: canonicalUrl.value }
  ]
})
</script>
```

Or use Nuxt SEO Utils which handles this automatically:

[SEO Utils v8.1.72.4M125 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/docs/seo-utils/getting-started/introduction) ### [Issue 2: Missing Titles on Dynamic Routes](#issue-2-missing-titles-on-dynamic-routes)

Generic titles hurt SEO:

```
<!-- ❌ Every blog post shows "Blog | MySite" -->
<title>Blog | MySite</title>
```

Always override with specific content:

```
<script setup lang="ts">
const { data: post } = await useFetch(\`/api/posts/${route.params.slug}\`)

// ✅ Each post gets unique title
useSeoMeta({
  title: post.value?.title // "How to Build a Vue Blog"
})
</script>
```

### [Issue 3: Infinite Parameter Variations](#issue-3-infinite-parameter-variations)

Dynamic routes with filters create crawl budget waste:

```
// ❌ Generates thousands of URLs
/products/:category?sort=price
/products/:category?sort=name
/products/:category?color=red&sort=price
// ... infinite combinations
```

Use `noindex` on filtered pages with route middleware:

middleware/filter-noindex.global.ts

```
export default defineNuxtRouteMiddleware((to) => {
  // Noindex pages with query params
  if (Object.keys(to.query).length > 0) {
    useHead({
      meta: [
        { name: 'robots', content: 'noindex, follow' }
      ]
    })
  }
})
```

Or block in `robots.txt` with the Robots module:

[Robots v6.0.78.9M519 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/robots/getting-started/introduction)

nuxt.config.ts

```
export default defineNuxtConfig({
  robots: {
    disallow: [
      '/*?ref=*',
      '/*?utm_*'
    ]
  }
})
```

Read more about [controlling crawlers](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers).

### [Issue 4: 404 Handling](#issue-4-404-handling)

Return proper 404 status codes for missing content:

```
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useFetch(\`/api/posts/${route.params.slug}\`)

if (!post.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Post not found'
  })
}
</script>
```

Nuxt automatically sets the response status code to 404 during SSR, preventing Google from indexing non-existent pages. If you're seeing unexpected 404s in Search Console, see the [indexing issues guide](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues) for diagnosis steps.

## [Route Params Table](#route-params-table)

Common dynamic route patterns:

| File Pattern | Matches | Params | Use Case |
| --- | --- | --- | --- |
| `pages/blog/[slug].vue` | `/blog/vue-guide` | `{ slug: 'vue-guide' }` | Blog posts, articles |
| `pages/products/[id].vue` | `/products/123` | `{ id: '123' }` | Product pages |
| `pages/docs/[category]/[page].vue` | `/docs/api/methods` | `{ category: 'api', page: 'methods' }` | Documentation |
| `pages/user/[id].vue` | `/user/42` | `{ id: '42' }` | User profiles |
| `pages/[lang]/about.vue` | `/en/about` | `{ lang: 'en' }` | Internationalized routes |
| `pages/files/[...path].vue` | `/files/docs/api.md` | `{ path: ['docs', 'api.md'] }` | Nested file paths |

## [Route Validation](#route-validation)

Validate params with `definePageMeta()`:

pages/user/[id].vue

```
<script setup lang="ts">
definePageMeta({
  validate: async (route) => {
    // Only allow numeric IDs
    return /^\d+$/.test(route.params.id as string)
  }
})
</script>
```

Invalid params result in 404, preventing indexing of malformed URLs.

## [TypeScript Support](#typescript-support)

Type route params:

```
// Type-safe route params
interface BlogParams {
  slug: string
}

const route = useRoute<BlogParams>()
const slug: string = route.params.slug // Typed
```

Or use Nuxt's generated types:

```
<script setup lang="ts">
// Nuxt auto-generates route types
const route = useRoute('blog-slug') // Type-safe params based on file structure
</script>
```

## [Verification](#verification)

Check what Google indexes:

1. **View Page Source** (not Inspect Element): Right-click → View Page Source. Should show complete HTML with title and meta tags.
2. **Google Search Console URL Inspection**: Test Live URL → View HTML. If you see `<title>Loading...</title>`, your SSR isn't working.
3. **Curl test**:

```
curl https://yoursite.com/blog/vue-guide | grep "<title>"
```

Should return full title tag, not empty or "Loading".

Nuxt's SSR works by default. you should always see complete HTML in the source.

[The 2026 SEO Checklist for Nuxt & Vue Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.](https://nuxtseo.com/learn-seo/checklist) [Haven't launched yet? Start with the Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup)

---

[404 Pages 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Nuxt applications.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages) [Internal Linking How to build an internal linking architecture in Nuxt that distributes PageRank, improves crawl depth, and helps Google discover every page.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/internal-linking)

On this page

- [Route Params vs Query Parameters](#route-params-vs-query-parameters)
- [Basic Dynamic Routes](#basic-dynamic-routes)
- [Per-Route Meta Tags](#per-route-meta-tags)
- [Multiple Route Params](#multiple-route-params)
- [Optional Params](#optional-params)
- [Catch-All Routes](#catch-all-routes)
- [Programmatic Navigation](#programmatic-navigation)
- [Common SEO Issues](#common-seo-issues)
- [Route Params Table](#route-params-table)
- [Route Validation](#route-validation)
- [TypeScript Support](#typescript-support)
- [Verification](#verification)

[GitHub](https://github.com/harlan-zw/nuxt-seo) [ Discord](https://discord.com/invite/275MBUBvgP)

### [NuxtSEO](https://nuxtseo.com/ "Home")

- [Getting Started](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction)
- [MCP](https://nuxtseo.com/docs/nuxt-seo/guides/mcp)

Modules

- [Robots](https://nuxtseo.com/docs/robots/getting-started/introduction)
- [Sitemap](https://nuxtseo.com/docs/sitemap/getting-started/introduction)
- [OG Image](https://nuxtseo.com/docs/og-image/getting-started/introduction)
- [Schema.org](https://nuxtseo.com/docs/schema-org/getting-started/introduction)
- [Link Checker](https://nuxtseo.com/docs/link-checker/getting-started/introduction)
- [SEO Utils](https://nuxtseo.com/docs/seo-utils/getting-started/introduction)
- [Site Config](https://nuxtseo.com/docs/site-config/getting-started/introduction)
- [Skew Protection](https://nuxtseo.com/docs/skew-protection/getting-started/introduction)
- [AI Ready](https://nuxtseo.com/docs/ai-ready/getting-started/introduction)

### [NuxtSEO Pro](https://nuxtseo.com/pro "Nuxt SEO Pro")

- [Getting Started](https://nuxtseo.com/pro)
- [Dashboard](https://nuxtseo.com/pro/dashboard)
- [Pro MCP](https://nuxtseo.com/pro/docs/getting-started/mcp-setup)

### [Learn SEO](https://nuxtseo.com/learn-seo "Learn SEO")

Nuxt

- [Mastering Meta](https://nuxtseo.com/learn-seo/nuxt/mastering-meta)
- [Controlling Crawlers](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers)
- [Launch & Listen](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen)
- [Routes & Rendering](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)
- [Staying Secure](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/security)

Vue

- [Vue SEO Guide](https://nuxtseo.com/learn-seo/vue)
- [Mastering Meta](https://nuxtseo.com/learn-seo/vue/mastering-meta)
- [Controlling Crawlers](https://nuxtseo.com/learn-seo/vue/controlling-crawlers)
- [SPA SEO](https://nuxtseo.com/learn-seo/vue/spa)
- [SSR Frameworks](https://nuxtseo.com/learn-seo/vue/ssr-frameworks)
- [SEO Checklist](https://nuxtseo.com/learn-seo/checklist)
- [Pre-Launch Warmup](https://nuxtseo.com/learn-seo/pre-launch-warmup)
- [Backlinks & Authority](https://nuxtseo.com/learn-seo/backlinks)

### [Tools](https://nuxtseo.com/tools "SEO Tools")

- [Social Share Debugger](https://nuxtseo.com/tools/social-share-debugger)
- [Robots.txt Generator](https://nuxtseo.com/tools/robots-txt-generator)
- [Meta Tag Checker](https://nuxtseo.com/tools/meta-tag-checker)
- [HTML to Markdown](https://nuxtseo.com/tools/html-to-markdown)
- [XML Sitemap Validator](https://nuxtseo.com/tools/xml-sitemap-validator)
- [Schema.org Validator](https://nuxtseo.com/tools/schema-validator)
- [Keyword Idea Generator](https://nuxtseo.com/tools/keyword-generator)
- [Keyword Research](https://nuxtseo.com/tools/keyword-research)
- [SERP Analyzer](https://nuxtseo.com/tools/serp-analyzer)
- [Domain Rankings](https://nuxtseo.com/tools/domain-rankings)

Copyright © 2023-2026 Harlan Wilton - [MIT License](https://github.com/harlan-zw/nuxt-seo/blob/main/license) · [mdream](https://mdream.dev)