---
title: "Duplicate Content SEO in Nuxt"
description: "Duplicate URLs split your ranking signals and burn crawl budget. Fix them in Nuxt with canonical tags, 301 redirects, and parameter handling."
canonical_url: "https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content"
last_updated: "2026-07-16"
---

<key-takeaways>

- Duplicate content is the same content living at more than one URL: it splits ranking signals and wastes crawl budget
- Google doesn't penalize duplicate content on its own; it picks a single version to index, and that's often not the one you want
- Use 301 redirects for permanent moves and canonical tags for duplicates you need to keep live

</key-takeaways>

Duplicate content is the same content living at more than one URL. It splits ranking signals and wastes crawl budget, since Google has to pick which version to index, and it's often not the one you want.

[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](/learn-seo/nuxt/routes-and-rendering/trailing-slashes).

Nuxt handles these redirects in `nuxt.config.ts`:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  nitro: {
    prerender: {
      autoSubfolderIndex: false
    }
  }
})
```

For HTTPS and www vs non-www redirects, configure at the server level (Netlify, [Vercel](https://vercel.com), [Cloudflare](https://cloudflare.com)) for best performance and SEO.

### Query Parameters

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

```text
/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](/learn-seo/nuxt/controlling-crawlers/canonical-urls#filter-and-sort-parameters) to point filter and sort variations to the base URL. Alternatively, [block filtered pages from indexing](/learn-seo/nuxt/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](/learn-seo/nuxt/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](/learn-seo/nuxt/routes-and-rendering/query-parameters) for the composable implementation. Better yet, [redirect tracking params at the server level](/learn-seo/nuxt/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](/learn-seo/nuxt/routes-and-rendering/pagination). Use self-referencing canonicals; don't point page 2 to page 1. See the [Pagination SEO guide](/learn-seo/nuxt/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**

```vue [pages/article.vue]
<script setup lang="ts">
const siteUrl = useSiteConfig().url

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

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

### Session IDs and Click Tracking

Session IDs in URLs create infinite variations.

```text
/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](/learn-seo/nuxt/controlling-crawlers/robots-txt):

```robots-txt [public/robots.txt]
User-agent: *
Disallow: /*?sessionid=
Disallow: /*&sessionid=
Disallow: /*?sid=
Disallow: /*&sid=
```

Or use the Robots module:

<module-card className="w-1/2" slug="robots">



</module-card>

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

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

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

**Siteliner** is a free tool that crawls up to 250 pages and shows a duplicate content percentage. **Copyscape** 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

<table>
<thead>
  <tr>
    <th>
      When to Use
    </th>
    
    <th>
      Canonical Tag
    </th>
    
    <th>
      301 Redirect
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <strong>
        Need both URLs live
      </strong>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      ❌ No
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        User should see one URL
      </strong>
    </td>
    
    <td>
      ❌ No
    </td>
    
    <td>
      ✅ Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Products in multiple categories
      </strong>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      ❌ No
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Old page no longer needed
      </strong>
    </td>
    
    <td>
      ❌ No
    </td>
    
    <td>
      ✅ Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        UTM tracking parameters
      </strong>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      ❌ No
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        www vs non-www
      </strong>
    </td>
    
    <td>
      ❌ No
    </td>
    
    <td>
      ✅ Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        HTTP vs HTTPS
      </strong>
    </td>
    
    <td>
      ❌ No
    </td>
    
    <td>
      ✅ Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Moved/renamed pages
      </strong>
    </td>
    
    <td>
      ❌ No
    </td>
    
    <td>
      ✅ Yes
    </td>
  </tr>
</tbody>
</table>

**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](/learn-seo/nuxt/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](/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">
useHead({
  link: [{ rel: 'canonical', href: 'https://mysite.com/blog' }]
})
</script>
```

[Each paginated page should reference itself](/learn-seo/nuxt/routes-and-rendering/pagination#self-referencing-canonical-tags).

**Mistake 2: Using relative canonical URLs**

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

<!-- ✅ Correct -->
<link rel="canonical" href="https://mysite.com/products/phone">
```

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

**Mistake 3: Combining canonical with noindex**

```vue
<!-- ❌ Conflicting signals -->
<script setup lang="ts">
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](/learn-seo/nuxt/controlling-crawlers/canonical-urls).

**Mistake 4: Canonical chains**

```text
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. Nuxt renders canonical tags on the server by default, so this isn't an issue.

## Preventing Duplicate Content

### Configure Trailing Slashes

Nuxt handles trailing slashes via `nuxt.config.ts`:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  // Force trailing slashes for prerendered routes
  nitro: {
    prerender: {
      autoSubfolderIndex: true
    }
  }
})
```

For dynamic redirects, use [server middleware](/learn-seo/nuxt/controlling-crawlers/redirects#using-server-middleware).

### Validate Parameter Values

Prevent infinite URL variations by whitelisting allowed parameter values:

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

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

### Block Low-Value Pages

Use [robots.txt](/learn-seo/nuxt/controlling-crawlers/robots-txt) to block search results, filtered pages, and admin sections:

```robots-txt [public/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=
```

Or configure via the Robots module:

<module-card className="w-1/2" slug="robots">



</module-card>

## Automatic Handling with Nuxt SEO Utils

Nuxt SEO Utils handles canonical URLs, trailing slashes, and parameter normalization automatically through site config:

<module-card className="w-1/2" slug="seo-utils">



</module-card>

Configure once in `nuxt.config.ts`:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  site: {
    url: 'https://mysite.com',
    trailingSlash: false
  }
})
```

The module automatically:

- Generates canonical URLs with correct trailing slash handling
- Strips tracking parameters from canonicals
- Normalizes URL formats across your site

## Checklist

<checklist id="nuxt-duplicate-content">

- Choose one canonical domain (www or non-www, HTTP or HTTPS) and redirect the rest
- Pick one trailing slash format 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

</checklist>
