---
title: "Duplicate Content SEO in Vue · Nuxt SEO"
canonical_url: "https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content"
last_updated: "2026-07-16T12:00:00.000Z"
meta:
  author: "Harlan Wilton"
  description: "Duplicate URLs split your ranking signals and burn crawl budget. Fix them in Vue with canonical tags, 301 redirects, and parameter handling."
  "og:description": "Duplicate URLs split your ranking signals and burn crawl budget. Fix them in Vue with canonical tags, 301 redirects, and parameter handling."
  "og:title": "Duplicate Content SEO in Vue · Nuxt SEO"
---

Nuxt SEO on GitHub

# **Duplicate Content SEO in Vue**

Duplicate URLs split your ranking signals and burn crawl budget. Fix them in Vue with canonical tags, 301 redirects, and parameter handling.

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

**What you'll learn**

- Vue SPAs carry extra duplicate content risk from hash vs history mode routing, client-rendered pages that all return the same HTML shell, and missing server-side canonicals
- Duplicate content splits ranking signals and wastes crawl budget: Google picks a single version to index, and it's often not the one you want
- Use `**@unhead/vue**` to set canonical tags or 301 redirects to consolidate duplicates; query parameters are the most common source

Vue SPAs face additional duplicate content risks beyond the usual URL variations. Vue Router's history mode vs hash mode, client-rendered pages that return identical HTML shells, and missing server-side canonical tags all compound the problem: the same content ends up living at more than one URL, which splits ranking signals and wastes crawl budget.

[**~~Google doesn't penalize duplicate content~~**](https://developers.google.com/search/docs/crawling-indexing/canonicalization) unless you're deliberately scraping other sites. But it hurts SEO by diluting link equity across multiple URLs and confusing search engines about which page to rank.

## Common Causes

### URL Variations

**www vs non-www**

`**www.mysite.com**` and `**mysite.com**` are [**~~treated as separate sites~~**](https://yoast.com/video/ask-yoast-use-www-or-not/). Choose one, redirect the other.

**HTTP vs HTTPS**

`**http://mysite.com**` and `**https://mysite.com**` create duplicates. Always redirect HTTP to HTTPS.

**Trailing slashes**

`**/products**` and `**/products/**` are different URLs. [**~~Pick one format site-wide~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes).

```ts
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const host = req.get('host')

  // Force non-www
  if (host.startsWith('www.')) {
    return res.redirect(301, `https://${host.slice(4)}${req.path}`)
  }

  next()
})
```

### Query Parameters

[**~~URL parameters create exponential duplicates~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters). Three filters generate 8 combinations. Add sorting and pagination, and you're looking at hundreds of URLs.

```
/products
/products?color=red
/products?color=red&size=large
/products?color=red&size=large&sort=price
/products?color=red&size=large&sort=price&page=2
```

**Fix:** Set [**~~canonical tags~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls#filter-and-sort-parameters) to point filter and sort variations to the base URL. Alternatively, [**~~block filtered pages from indexing~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags) with noindex.

Parameter order matters too: `**?sort=price&filter=red**` and `**?filter=red&sort=price**` are identical content, different URLs. Enforce consistent parameter order in canonical URLs; see the [**~~Query Parameters guide~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters) for the implementation.

### Tracking Parameters and Pagination

Analytics params (`**utm_source**`, `**fbclid**`, `**gclid**`) don't change content but create duplicate URLs, and so do un-canonicalized paginated pages.

**Fix: strip tracking parameters.** Strip tracking parameters (utm\_source, fbclid, gclid) from canonical URLs. See the [**~~Query Parameters guide~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters) for the composable implementation. Better yet, [**~~redirect tracking params at the server level~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters#server-side-parameter-handling) for proper 301 status codes.

**Fix: self-reference paginated pages.** [**~~Each paginated page has unique content~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination). Use self-referencing canonicals; don't point page 2 to page 1. See the [**~~Pagination SEO guide~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination) for the implementation.

### Print and Mobile Versions

Printer-friendly URLs (`**/article?print=true**`) and mobile subdomains (`**m.mysite.com**`) create duplicates.

**Fix: Canonical to desktop version**

pages/Article.vue

```vue
<script setup lang="ts">
import { useHead } from '@unhead/vue'

useHead({
  link: [{
    rel: 'canonical',
    // Always point to main URL
    href: 'https://mysite.com/article'
  }]
})
</script>
```

For print, use CSS `**@media print**` instead of separate URLs.

### Session IDs and Click Tracking

Session IDs in URLs create infinite variations.

```
/products?sessionid=abc123
/products?sessionid=xyz789
/products?sessionid=def456
```

**Fix: Don't put session IDs in URLs.** Use cookies. If unavoidable, [**~~block with robots.txt~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt):

public/robots.txt

```robots-txt
User-agent: *
Disallow: /*?sessionid=
Disallow: /*&sessionid=
Disallow: /*?sid=
Disallow: /*&sid=
```

## Finding Duplicate Content

### Google Search Console

[**~~Use the Page indexing report~~**](https://support.google.com/webmasters/answer/7440203) to identify duplicates:

1. Open Search Console
2. Go to "Indexing" → "Pages"
3. Look for:
   - "Duplicate, Google chose different canonical than user"
   - "Duplicate without user-selected canonical"
   - "Alternate page with proper canonical tag"

Click each category to see affected URLs. If Google chose a different canonical than you specified, [**~~conflicting signals exist~~**](https://developers.google.com/search/blog/2019/03/how-to-discover-suggest-google-selected).

**Using URL Inspection:**

1. Enter any URL
2. Check "User-declared canonical" vs "Google-selected canonical"
3. If they differ, Google found stronger signals pointing to a different URL

**View page source (not DevTools)** to verify canonical tags are server-rendered:

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

**Check for canonicalization conflicts:**

- Multiple `**rel="canonical"**` tags on same page
- Canonical in `**<head>**` vs HTTP header
- Canonical points to redirect or noindexed page
- Canonical URL returns 4xx/5xx status

**Test redirect chains:**

```bash
curl -I https://mysite.com/old-url
```

Should show one 301 redirect, not a chain.

### Screaming Frog

[**~~Screaming Frog detects exact and near-duplicate content~~**](https://www.screamingfrog.co.uk/seo-spider/tutorials/how-to-check-for-duplicate-content/):

**Exact duplicates**: Pages with identical HTML (MD5 hash match)

**Near duplicates**: Pages with 90%+ similarity (minhash algorithm)

**Setup:**

1. Enable near duplicates: `**Config > Content > Duplicates**`
2. Crawl your site
3. Go to "Content" tab
4. Filter by "Exact Duplicates" or "Near Duplicates"

Check these columns:

- `**Closest Similarity Match**`: Percentage match to most similar page
- `**No. Near Duplicates**`: Count of similar pages
- `**Hash**`: MD5 hash for exact duplicate detection

[**~~Screaming Frog~~**](https://screamingfrog.co.uk) auto-excludes nav and footer elements to focus on main content. [**~~Adjust threshold~~**](https://www.screamingfrog.co.uk/seo-spider/user-guide/tabs/#content) if needed (default 90%).

### Manual and Third-Party Tools

Use Google site search to find duplicates manually:

```
site:mysite.com "exact title text"
```

If multiple URLs appear with the same title, you have duplicates.

**[**~~Siteliner~~**](https://www.siteliner.com/)** is a free tool that crawls up to 250 pages and shows a duplicate content percentage. **[**~~Copyscape~~**](https://www.copyscape.com/)** detects external duplicate content, i.e. other sites copying you. Both are useful for content audits but don't replace Search Console or Screaming Frog for technical SEO.

## Canonical vs 301 Redirect

| **When to Use** | **Canonical Tag** | **301 Redirect** |
| --- | --- | --- |
| **Need both URLs live** | ✅ Yes | ❌ No |
| **User should see one URL** | ❌ No | ✅ Yes |
| **Products in multiple categories** | ✅ Yes | ❌ No |
| **Old page no longer needed** | ❌ No | ✅ Yes |
| **UTM tracking parameters** | ✅ Yes | ❌ No |
| **www vs non-www** | ❌ No | ✅ Yes |
| **HTTP vs HTTPS** | ❌ No | ✅ Yes |
| **Moved/renamed pages** | ❌ No | ✅ Yes |

**Canonical tags** are [**~~hints, not directives~~**](https://www.searchenginejournal.com/canonical-vs-301-redirect/383124/). Google may ignore them. Both versions remain accessible. Use for duplicates you need (tracking params, multiple category paths).

**301 redirects** are permanent. Users see the redirect target. [**~~Pass the same link equity as canonicals~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects) but remove the duplicate from the index. Use for outdated or unnecessary URLs.

**Don't combine:** Using both canonical tag and 301 redirect on the same page sends conflicting signals. Pick one.

## Decision Tree

![Duplicate Content Decision Tree](https://nuxtseo.com/images/learn-seo/vue/duplicate-content-decision.svg)

**Examples:**

- `**http://mysite.com**` → `**https://mysite.com**`: **301 redirect**
- `**www.mysite.com**` → `**mysite.com**`: **301 redirect**
- `**/products?utm_source=twitter**` → `**/products**`: **Canonical tag**
- `**/products/shoes**` and `**/sale/shoes**` (same product): **Canonical tag** (one canonical, one alternate)
- `**/products?filter=red**`: **Noindex + canonical to base URL**
- `**/old-page**` → `**/new-page**`: **301 redirect**

## Common Mistakes

**Mistake 1: Canonicalizing all paginated pages to page 1**

```vue
<!-- ❌ Wrong - hides pages 2+ from search -->
<script setup lang="ts">
import { useHead } from '@unhead/vue'

useHead({
  link: [{ rel: 'canonical', href: 'https://mysite.com/blog' }]
})
</script>
```

[**~~Each paginated page should reference itself~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination#self-referencing-canonical-tags).

**Mistake 2: Using relative canonical URLs**

```html
<!-- Must be absolute -->
<link rel="canonical" href="/products/phone">
```

[**~~Google requires absolute URLs~~**](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls).

**Mistake 3: Combining canonical with noindex**

```vue
<script setup lang="ts">
import { useHead, useSeoMeta } from '@unhead/vue'

// Conflicting signals - pick one approach
useHead({
  link: [{ rel: 'canonical', href: 'https://mysite.com/page' }]
})
useSeoMeta({
  robots: 'noindex, follow'
})
</script>
```

Canonical says "this is a duplicate of X." Noindex says "don't index this." [**~~Pick one~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls).

**Mistake 4: Canonical chains**

```
Page A → canonical → Page B → canonical → Page C
```

A chain dilutes the signal and search engines may not follow it all the way through. Canonical directly to the final target instead.

**Mistake 5: Client-side canonicals in SPAs**

Googlebot doesn't execute JavaScript fast enough. [**~~Server-render canonical tags~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering) or use SSR.

## Preventing Duplicate Content

### Configure Vue Router

Use consistent trailing slash handling:

router/index.ts

```ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [/* ... */],
  strict: true // Treat /page and /page/ as different
})

// Enforce trailing slashes
router.beforeEach((to, from, next) => {
  if (!to.path.endsWith('/') && to.path !== '/') {
    next({ path: `${to.path}/`, query: to.query })
  }
  else {
    next()
  }
})
```

Or [**~~redirect at the server level~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes) for proper 301 status codes.

### Validate Parameter Values

Prevent infinite URL variations by whitelisting allowed parameter values:

```ts
const allowedSortValues = ['price', 'name', 'date', 'rating']
const sort = route.query.sort

if (sort && !allowedSortValues.includes(sort)) {
  // Redirect to base URL or default sort
  router.replace({ query: { ...route.query, sort: undefined } })
}
```

### Block Low-Value Pages

Use [**~~robots.txt~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt) to block search results, filtered pages, and admin sections:

public/robots.txt

```robots-txt
User-agent: *

# Block search results
Disallow: /search?
Disallow: /*?q=
Disallow: /*?query=

# Block filters
Disallow: /*?filter=
Disallow: /*&filter=

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

# Block session IDs
Disallow: /*?sessionid=
Disallow: /*?sid=
```

Using Nuxt? [**~~Nuxt SEO~~**](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction) handles canonical URLs automatically through site config and route rules. [**~~Learn more about duplicate content in Nuxt →~~**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers)

## Checklist

**Checklist**

- Choose one canonical domain (www or non-www, HTTP or HTTPS) and redirect the rest
- Pick one trailing slash format in Vue Router and enforce it site-wide
- Set canonical tags on filtered, sorted, and paginated URLs pointing to the preferred version
- Strip tracking parameters (utm\_source, fbclid, gclid) from canonical URLs
- Use absolute URLs in every canonical tag, never relative paths
- Never combine noindex with a canonical tag on the same page

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

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

[**Query Parameters**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters)

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

[**HTTP Redirects**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects)

[**HTTP Redirects** Implement server-side 301 redirects in Vue SSR (Express, Vite, H3) so migrated pages keep their rankings and avoid redirect chains.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects) [**llms.txt** Ship a spec-compliant llms.txt for your Vue docs, by hand, at build time, or via a VitePress/Docusaurus plugin. Full format and setup.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/llms-txt)

**On this page**

- [Common Causes](#common-causes)
- [Finding Duplicate Content](#finding-duplicate-content)
- [Canonical vs 301 Redirect](#canonical-vs-301-redirect)
- [Decision Tree](#decision-tree)
- [Common Mistakes](#common-mistakes)
- [Preventing Duplicate Content](#preventing-duplicate-content)
- [Checklist](#checklist)