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 WiltonHarlan Wilton12 mins read Published
What you'll learn
  • Filter parameters should be noindexed or canonical to base URL
  • Strip tracking params (utm_, fbclid, gclid) from canonical URLs
  • Move important filters to path segments for better SEO value

Query parameters (?sort=price&filter=red) create duplicate content. 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
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
useHead({
  link: [{
    rel: 'canonical',
    // Always canonical to base URL
    href: 'https://mysite.com/products'
  }]
})
pages/Products.vue - Block from Indexing
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.

Common Parameter Types

Different query parameters need different handling:

Parameter TypeExamplesSEO TreatmentWhy
Filters?color=red&size=largeCanonical to base or noindexCreates duplicates, thin content
Sorting?sort=priceInclude in canonicalChanges value, users link to sorted views
Pagination?page=2Self-referencing canonicalEach page has unique content
Tracking?utm_source=twitterStrip from canonicalNo content value, analytics only
Search?q=shoesDepends on resultsIndex if unique results, noindex if duplicates
Sessions?sessionid=abcCanonical to baseCreates 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
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:

// /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
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:

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

Pagination Parameters

Each page in a sequence has unique content. Google recommends self-referencing canonicals—don't point page 2 to page 1.

pages/Blog.vue
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 Page Numbers

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
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:

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:

composables/useCanonicalUrl.ts
export function useCanonicalUrl(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}`
    }]
  }
}

Vue Router Navigation

Force parameter order when updating query strings:

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
      })
  )

  router.push({
    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
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'
  }]
})

Block crawlers from search entirely:

public/robots.txt
User-agent: *
Disallow: /search?

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

Testing Parameter Handling

Google Search Console

  1. URL Inspection tool
  2. Enter URL with parameters
  3. Check "User-declared canonical" vs "Google-selected canonical"
  4. Verify they match your preference

Manual Verification

# 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:

URLExpected CanonicalExpected Robots
/productsSelfindex, follow
/products?sort=priceSelf or baseDepends on strategy
/products?filter=redBase URLnoindex, follow
/products?utm_source=twitterBase URLindex, follow
/products?page=2Selfindex, follow

robots.txt Parameter Blocking

Block specific parameters from crawling entirely:

public/robots.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 parameter handling in Search Console in 2022. Use robots.txt or meta robots instead.

Common Mistakes

Using client-side canonicals for SPAs: Googlebot doesn't execute JavaScript fast enough. 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?

If you're using Nuxt, check out Nuxt SEO for automatic canonical URL handling and parameter management through site config.

Learn more about query parameters in Nuxt →