---
title: "Query Parameters and SEO in Vue · Nuxt SEO"
canonical_url: "https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters"
last_updated: "2026-07-16T12:00:00.000Z"
meta:
  author: "Harlan Wilton"
  description: "Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Vue Router."
  "og:description": "Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Vue Router."
  "og:title": "Query Parameters and SEO in Vue · Nuxt SEO"
---

Nuxt SEO on GitHub

# **Query Parameters and SEO in Vue**

Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Vue Router.

[Harlan Wilton](https://x.com/harlan-zw)12 mins read Published **Dec 17, 2025** Updated **Jul 16, 2026**

**What you'll learn**

- Query parameters like `**?sort=price**` create duplicate content: search engines treat each combination as a separate URL even though the content barely changes
- Set canonical URLs with `**@unhead/vue**` to tell search engines which parameter variant to index, and strip tracking params ( `**utm_**`, `**fbclid**`, `**gclid**`) from every canonical you generate
- Move SEO-valuable filters, like product categories, into path segments instead of query strings
- Validate parameter values on the server so crawlers can't generate infinite URL variations from junk input

For deciding whether content belongs in a path segment or query parameter, see [**~~URL Structure~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure#path-segments-vs-query-parameters).

Query parameters (`**?sort=price&filter=red**`) create duplicate content. In Vue, you access these through `**useRoute().query**` from `**vue-router**`, and managing their SEO impact requires manual canonical setup with `**@unhead/vue**`. The URL `**/products?sort=price**` and `**/products?sort=name**` show the same products in different orders, but search engines treat them as separate pages.

With filters, sorting, and pagination, a single page can generate hundreds of URL variations. Each variation wastes crawl budget and dilutes ranking signals across duplicates.

## Quick Setup

Handle query parameters with canonical URLs to tell search engines which version to index:

pages/Products.vue

```ts
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const { sort, filter, page } = route.query

useHead({
  link: [{
    rel: 'canonical',
    // Remove filter and page, keep sort
    href: sort
      ? `https://mysite.com/products?sort=${sort}`
      : 'https://mysite.com/products'
  }]
})
```

pages/Products.vue - Block All Parameters

```ts
useHead({
  link: [{
    rel: 'canonical',
    // Always canonical to base URL
    href: 'https://mysite.com/products'
  }]
})
```

pages/Products.vue - Block from Indexing

```ts
import { useSeoMeta } from '@unhead/vue'

// Use noindex for filtered/sorted views
useSeoMeta({
  robots: computed(() =>
    route.query.filter || route.query.page
      ? 'noindex, follow'
      : 'index, follow'
  )
})
```

For Vue applications, you'll need to [**~~install Unhead manually~~**](https://unhead.unjs.io/docs/vue/head/guides/get-started/installation).

## Common Parameter Types

Different query parameters need different handling:

| **Parameter Type** | **Examples** | **SEO Treatment** | **Why** |
| --- | --- | --- | --- |
| **Filters** | `**?color=red&size=large**` | Canonical to base or noindex | Creates duplicates, thin content |
| **Sorting** | `**?sort=price**` | Include in canonical | Changes value, users link to sorted views |
| **Pagination** | `**?page=2**` | Self-referencing canonical | Each page has unique content |
| **Tracking** | `**?utm_source=twitter**` | Strip from canonical | No content value, analytics only |
| **Search** | `**?q=shoes**` | Depends on results | Index if unique results, noindex if duplicates |
| **Sessions** | `**?sessionid=abc**` | Canonical to base | Creates infinite URLs |

## Filter Parameters

Filters create exponential URL variations. Three color filters generate 8 combinations (red, blue, green, red+blue, red+green, blue+green, red+blue+green, none).

### Block Filtered Pages

pages/Products.vue

```ts
const route = useRoute()
const hasFilters = computed(() =>
  route.query.color || route.query.size || route.query.brand
)

useHead({
  link: [{
    rel: 'canonical',
    href: 'https://mysite.com/products'
  }]
})

useSeoMeta({
  robots: hasFilters.value ? 'noindex, follow' : 'index, follow'
})
```

### Move Important Filters to Path

For SEO-valuable filters (categories, main attributes), use route paths instead of query params:

```ts
// /products?category=shoes
const routes = [{
  path: '/products',
  component: Products
}]
```

## Sort Parameters

Sorting changes presentation but not content. Users share sorted URLs ("cheapest laptops" links to `**?sort=price**`).

### Include Sort in Canonical

pages/Products.vue

```ts
const route = useRoute()
const allowedSortValues = ['price', 'name', 'date', 'rating']
const sort = computed(() =>
  allowedSortValues.includes(route.query.sort)
    ? route.query.sort
    : undefined
)

useHead({
  link: [{
    rel: 'canonical',
    href: sort.value
      ? `https://mysite.com/products?sort=${sort.value}`
      : 'https://mysite.com/products'
  }]
})
```

**Why validate sort values?** Prevents parameter manipulation creating infinite URLs (`**?sort=abc**`, `**?sort=xyz**`).

### Block Sort from Indexing

If sorted views don't add value (same content, different order), use noindex:

```ts
useSeoMeta({
  robots: route.query.sort ? 'noindex, follow' : 'index, follow'
})
```

## Pagination Parameters

Each page in a sequence has unique content. [**~~Google recommends self-referencing canonicals~~**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) rather than pointing every page back to page 1. See the [**~~Pagination SEO guide~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination) for full implementation details.

pages/Blog.vue

```ts
const route = useRoute()
const page = computed(() => route.query.page || '1')

useHead({
  link: [{
    rel: 'canonical',
    // Each page references itself
    href: page.value === '1'
      ? 'https://mysite.com/blog'
      : `https://mysite.com/blog?page=${page.value}`
  }]
})
```

Validate the page number too:

```ts
const page = computed(() => {
  const pageNum = Number.parseInt(route.query.page)
  return pageNum > 0 && pageNum <= maxPages ? pageNum : 1
})
```

Prevents crawlers requesting `**?page=999999**` and wasting server resources.

## Tracking Parameters

Analytics parameters (utm\_, fbclid, gclid) don't change content but create duplicate URLs.

### Strip Tracking Params

composables/useCanonicalUrl.ts

```ts
export function useCanonicalUrl(path: string) {
  const siteUrl = import.meta.env.VITE_SITE_URL
  const route = useRoute()

  // List of tracking params to ignore
  const trackingParams = [
    'utm_source',
    'utm_medium',
    'utm_campaign',
    'utm_term',
    'utm_content',
    'fbclid',
    'gclid',
    'msclkid',
    'mc_cid',
    'mc_eid',
    '_ga',
    'ref',
    'source'
  ]

  // Keep only non-tracking params
  const cleanParams = Object.fromEntries(
    Object.entries(route.query).filter(([key]) =>
      !trackingParams.includes(key)
    )
  )

  const queryString = new URLSearchParams(cleanParams).toString()

  return {
    link: [{
      rel: 'canonical',
      href: queryString
        ? `${siteUrl}${path}?${queryString}`
        : `${siteUrl}${path}`
    }]
  }
}
```

### Server-Side Parameter Handling

Redirect tracking parameters at the server level for proper 301 status codes:

```ts
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const trackingParams = ['utm_source', 'fbclid', 'gclid']
  const hasTracking = trackingParams.some(param => req.query[param])

  if (hasTracking) {
    const cleanQuery = Object.fromEntries(
      Object.entries(req.query).filter(([key]) =>
        !trackingParams.includes(key)
      )
    )
    const queryString = new URLSearchParams(cleanQuery).toString()
    return res.redirect(301, `${req.path}${queryString ? `?${queryString}` : ''}`)
  }
  next()
})
```

## Parameter Order Consistency

`**?sort=price&filter=red**` and `**?filter=red&sort=price**` are identical content but different URLs. Enforce consistent parameter ordering:

utils/buildCanonicalUrl.ts

```ts
export function buildCanonicalUrl(path: string, params: Record<string, string>) {
  const siteUrl = import.meta.env.VITE_SITE_URL

  // Define parameter order
  const paramOrder = ['category', 'sort', 'filter', 'page']

  // Sort params by predefined order
  const orderedParams = Object.fromEntries(
    Object.entries(params)
      .sort(([a], [b]) => {
        const indexA = paramOrder.indexOf(a)
        const indexB = paramOrder.indexOf(b)
        if (indexA === -1)
          return 1
        if (indexB === -1)
          return -1
        return indexA - indexB
      })
  )

  const queryString = new URLSearchParams(orderedParams).toString()

  return {
    link: [{
      rel: 'canonical',
      href: queryString
        ? `${siteUrl}${path}?${queryString}`
        : `${siteUrl}${path}`
    }]
  }
}
```

Apply the same order in Vue Router navigation, so links you generate never fight the canonical you set:

```ts
import { useRouter } from 'vue-router'

const router = useRouter()

function updateFilters(filters: Record<string, string>) {
  const paramOrder = ['category', 'sort', 'filter', 'page']

  const ordered = Object.fromEntries(
    Object.entries(filters)
      .sort(([a], [b]) => {
        const indexA = paramOrder.indexOf(a)
        const indexB = paramOrder.indexOf(b)
        return indexA - indexB
      })
  )

  navigateTo({
    query: ordered
  })
}
```

## Search Parameters

Search queries create unique URLs for each search term. Treatment depends on result quality:

### Block Thin Search Results

pages/Search.vue

```ts
const route = useRoute()
const query = route.query.q
const results = await searchProducts(query)

useSeoMeta({
  robots: !query || results.length < 5
    ? 'noindex, follow'
    : 'index, follow'
})

useHead({
  link: [{
    rel: 'canonical',
    href: query
      ? `https://mysite.com/search?q=${encodeURIComponent(query)}`
      : 'https://mysite.com/search'
  }]
})
```

### robots.txt for Search

Block crawlers from search entirely:

public/robots.txt

```txt
User-agent: *
Disallow: /search?

# Or use URL patterns
Disallow: /*?q=
Disallow: /*?query=
```

## Testing Parameter Handling

In Google Search Console's URL Inspection tool, enter a URL with parameters and compare "User-declared canonical" against "Google-selected canonical". A mismatch means Google disagrees with the canonical you set, so check that first before digging further.

### Manual Verification

```bash
# Check canonical in HTML
curl https://mysite.com/products?sort=price | grep canonical

# Should return:
# <link rel="canonical" href="https://mysite.com/products?sort=price">
```

### Test Parameter Variations

Create a test matrix:

| **URL** | **Expected Canonical** | **Expected Robots** |
| --- | --- | --- |
| `**/products**` | Self | index, follow |
| `**/products?sort=price**` | Self or base | Depends on strategy |
| `**/products?filter=red**` | Base URL | noindex, follow |
| `**/products?utm_source=twitter**` | Base URL | index, follow |
| `**/products?page=2**` | Self | index, follow |

## robots.txt Parameter Blocking

Block specific parameters from crawling entirely:

public/robots.txt

```txt
User-agent: *

# Block all URLs with these params
Disallow: /*?sessionid=
Disallow: /*?sid=
Disallow: /*&sessionid=
Disallow: /*&sid=

# Block tracking params
Disallow: /*?utm_source=
Disallow: /*?fbclid=
Disallow: /*?gclid=

# Block filter combinations
Disallow: /*?filter=
Disallow: /*&filter=
```

[**~~Google deprecated the URL Parameters tool in Search Console~~**](https://developers.google.com/search/blog/2022/03/url-parameters-tool-deprecated) in 2022. Use robots.txt or meta robots instead.

## Common Mistakes

**Using client-side canonicals for SPAs:** A canonical tag set with `**useHead()**` only exists in the DOM after JavaScript runs. Google queues that render separately from crawling, and AI crawlers that never execute JavaScript won't see the tag at all. Server-render canonical tags or use SSR.

**Indexing every parameter variation:** Creates thin content and wastes crawl budget. Pick one canonical version.

**Inconsistent parameter handling:** Some pages canonical to base, others to self. Be consistent site-wide.

**Ignoring tracking parameters:** Analytics params create duplicate URLs. Strip them from canonicals.

**Not validating parameter values:** Allows `**?sort=anything**` creating infinite URLs. Whitelist valid values.

Using Nuxt? [**~~Nuxt SEO Utils~~**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction) generates canonical URLs automatically and strips query parameters outside a configurable whitelist, no manual composable required. [**~~Learn more about query parameters in Nuxt →~~**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/query-parameters)

## Checklist

**Checklist**

- Set self-referencing or base-URL canonicals for every query parameter variant
- noindex filtered and search-result pages that don't add unique value
- Strip tracking parameters ( `**utm_**`, `**fbclid**`, `**gclid**`) from canonical URLs
- Move SEO-valuable filters to path segments instead of query strings
- Validate parameter values server-side to block infinite URL variations
- Confirm canonical output with `**curl**` or the URL Inspection tool

[**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)

---

### **Related **

[**Canonical URLs**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls)

[**URL Structure**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure)

[**Meta Robots**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags)

[**Trailing Slashes**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes)

[**Pagination SEO**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination)

[**Trailing Slashes** Fix duplicate content from /about vs /about/ with Vue Router strict mode, edge redirects, and matching canonical tags.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes) [**Hreflang & i18n** Configure hreflang in Vue with Unhead or vue-i18n so Google shows the right language version instead of flagging duplicate content.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n)

**On this page**

- [Quick Setup](#quick-setup)
- [Common Parameter Types](#common-parameter-types)
- [Filter Parameters](#filter-parameters)
- [Sort Parameters](#sort-parameters)
- [Pagination Parameters](#pagination-parameters)
- [Tracking Parameters](#tracking-parameters)
- [Parameter Order Consistency](#parameter-order-consistency)
- [Search Parameters](#search-parameters)
- [Testing Parameter Handling](#testing-parameter-handling)
- [robots.txt Parameter Blocking](#robotstxt-parameter-blocking)
- [Common Mistakes](#common-mistakes)
- [Checklist](#checklist)