---
title: "Tag Deduplication"
description: "Automatic tag deduplication by key, name, and property. Override layout tags in pages, manage verification tags, and customize merge strategies."
canonical_url: "https://unhead.unjs.io/docs/head/guides/core-concepts/handling-duplicates"
last_updated: "2026-08-11T00:46:07.978Z"
---

Unhead deduplicates tags by their semantic identity or an explicit `key`. Meta tags normally use `name`, `property`, or `http-equiv` as that identity.

## Tag deduplication

When implementing head tags across an application hierarchy (layouts, pages, components), you'll often need to override tags. This automatic replacement of duplicate tags is called "deduplication" or "deduping."

Unhead identifies duplicates from each tag's semantic identity.

HTML permits one `title` element per document. Repeated description metadata is not an HTML syntax error, but it leaves consumers with competing descriptions. Google recommends a page-specific description when one is provided; see its [snippet guidance](https://developers.google.com/search/docs/appearance/snippet#meta-descriptions).

## Replacement behavior

When you register multiple tags that are considered duplicates, only the most recent one will be used by default. This allows page-level components to override tags defined at the layout level.

### Duplicate identities

Unhead uses several strategies to identify duplicate tags:

- **Special singleton tags**: `base`, `title`, `titleTemplate`, `bodyAttrs`, `htmlAttrs`
- **Specific link types**: `<link rel="canonical">`, one `<link rel="alternate">` per `hreflang`
- **Charset metadata**: `<meta charset="">`
- **Custom keys**: Any tag with a matching `key` attribute
- **Meta tag identifiers**: Matching `name`, `property`, or `http-equiv` attributes
- **Link identity**: Matching `rel` + `href` combination, other attributes are ignored

These are checked in order. Canonical and `hreflang` alternate links are true singletons, so they dedupe even when tags carry different `key` values. The generic `rel` + `href` rule only applies when a link has no explicit `key` or `id`, which is what makes the keyed escape hatch below work.

### Example: Meta Description Override

```ts
import { useHead } from '@unhead/dynamic-import'

// useHead: /docs/head/api/composables/use-head
// Called in a layout component (higher in the hierarchy)
useHead({
  meta: [
    {
      name: 'description',
      content: 'my site wide description',
    },
  ],
})

// Called in a page component (lower in the hierarchy)
useHead({
  meta: [
    {
      name: 'description',
      content: 'my page description',
    },
  ],
})

// Result in the rendered HTML:
// <meta name="description" content="my page description" />
```

<tip>

You can control which tag takes precedence using [Tag Priorities](/docs/head/guides/core-concepts/positions) to override the default "most recent wins" behavior.

</tip>

## Multiple values for one meta name

Some metadata allows more than one value. Verification tags from multiple services are one example.

### Using Content Arrays

For arrayable meta names such as `google-site-verification`, you can provide an array of values for `content`:

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  meta: [
    {
      name: 'google-site-verification',
      content: [
        'verification-id-1',
        'verification-id-2',
      ]
    },
  ],
})

// Result in HTML:
// <meta name="google-site-verification" content="verification-id-1">
// <meta name="google-site-verification" content="verification-id-2">
```

### Multiple Tags in a Single Entry

Unhead also preserves multiple arrayable meta tags with the same identifier when they're defined within a single `useHead()` call:

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  meta: [
    {
      name: 'google-site-verification',
      content: 'verification-id-1'
    },
    {
      name: 'google-site-verification',
      content: 'verification-id-2'
    },
  ],
})

// Result in HTML:
// <meta name="google-site-verification" content="verification-id-1">
// <meta name="google-site-verification" content="verification-id-2">
```

<tip>

Within one entry, Unhead keeps repeated `theme-color`, `google-site-verification`, and `author` tags. It also keeps Open Graph image, audio, video, and alternate locale arrays; article and book author/tag arrays; and deprecated Twitter image arrays. For every other duplicate meta name, the last tag wins.

</tip>

### Multiple Links with the Same rel and href

Link tags dedupe on their `rel` + `href` pair; other attributes are not considered. If you intentionally need two links sharing both, such as a `preconnect` with and without `crossorigin`, give each a unique `key`:

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  link: [
    { rel: 'preconnect', href: 'https://cdn.example.com', key: 'preconnect' },
    { rel: 'preconnect', href: 'https://cdn.example.com', crossorigin: '', key: 'preconnect-cors' },
  ],
})

// Result in HTML:
// <link rel="preconnect" href="https://cdn.example.com" data-hid="preconnect">
// <link rel="preconnect" href="https://cdn.example.com" crossorigin data-hid="preconnect-cors">
```

## Custom keys

Provide a custom `key` when two otherwise similar tags need separate identities.

The key identifies a tag within its tag type. A script and a link may use the same key without deduplicating each other. Canonical links, language alternates, charset, viewport, description, keywords, and robots metadata use their semantic identity even when a key is present.

### Example: Ensuring Script Uniqueness

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  script: [
    {
      src: 'https://example.com/script.js',
      key: 'my-script',
    },
  ]
})
```

### Overriding Tags Using Keys

Use the same key on a later tag of the same type to update it. Tags with matching explicit keys merge their attributes by default while the later tag supplies top-level fields such as `textContent` and `innerHTML`. Set `tagDuplicateStrategy: 'replace'` when you do not want to retain earlier attributes.

## Duplicate strategies

By default, a later duplicate `replace`s the earlier tag.

### Default Strategies

- **For most tags**: `replace`; the new tag replaces the old one
- **For attribute tags**: `merge`; `htmlAttrs` and `bodyAttrs` retain existing attributes, including `class` and `style`
- **For matching explicit keys**: Attributes merge by default; use `tagDuplicateStrategy: 'replace'` for replacement

### The `tagDuplicateStrategy` Property

You can explicitly control this behavior using the `tagDuplicateStrategy` property:

```ts
import { useHead } from '@unhead/dynamic-import'

// In a layout file we added a class
useHead({
  htmlAttrs: {
    class: 'my-class',
  },
})

// On a specific page, we want to replace instead of merge
useHead({
  htmlAttrs: {
    tagDuplicateStrategy: 'replace', // Override the default merge behavior
    class: 'my-new-class',
  },
})

// Result in HTML:
// <html class="my-new-class">
```

## Practical Examples

### Removing an entry

Keep the active entry returned by `useHead()` and call `dispose()` when that entry is no longer needed:

```ts
import { useHead } from '@unhead/dynamic-import'

const entry = useHead({
  style: [
    {
      key: 'red-bg',
      textContent: 'body { color: red }',
    }
  ]
})

entry.dispose()
```

Passing an otherwise empty tag with the same key is not a general removal API: keyed element tags carry an internal `data-hid` attribute and may render as an empty element. For a meta tag, a later duplicate with `content: null` is omitted during sanitization.

### Replacing a style

```ts
import { useHead } from '@unhead/dynamic-import'

// A layout file sets the background color to red
useHead({
  style: [
    {
      key: 'bg-color',
      textContent: 'body { color: red }',
    }
  ]
})

// In a page component, we want to change it to blue
useHead({
  style: [
    {
      key: 'bg-color',
      textContent: 'body { color: blue }',
    }
  ]
})

// Result: Only the blue style is rendered
```

## See Also

- [Tag Positions](/docs/head/guides/core-concepts/positions): Control tag ordering
- [useHead() API](/docs/head/api/composables/use-head): Deduplication options
