Tag Deduplication
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.
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">perhreflang - Charset metadata:
<meta charset=""> - Custom keys: Any tag with a matching
keyattribute - Meta tag identifiers: Matching
name,property, orhttp-equivattributes - Link identity: Matching
rel+hrefcombination, 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
import { useHead } from 'unhead'
// useHead: /docs/head/api/composables/use-head
// Called in a layout component (higher in the hierarchy)
useHead(unheadInstance, {
meta: [
{
name: 'description',
content: 'my site wide description',
},
],
})
// Called in a page component (lower in the hierarchy)
useHead(unheadInstance, {
meta: [
{
name: 'description',
content: 'my page description',
},
],
})
// Result in the rendered HTML:
// <meta name="description" content="my page description" />
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:
import { useHead } from 'unhead'
useHead(unheadInstance, {
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:
import { useHead } from 'unhead'
useHead(unheadInstance, {
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">
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.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:
import { useHead } from 'unhead'
useHead(unheadInstance, {
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
import { useHead } from 'unhead'
useHead(unheadInstance, {
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 replaces the earlier tag.
Default Strategies
- For most tags:
replace; the new tag replaces the old one - For attribute tags:
merge;htmlAttrsandbodyAttrsretain existing attributes, includingclassandstyle - 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:
import { useHead } from 'unhead'
// In a layout file we added a class
useHead(unheadInstance, {
htmlAttrs: {
class: 'my-class',
},
})
// On a specific page, we want to replace instead of merge
useHead(unheadInstance, {
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:
import { useHead } from 'unhead'
const entry = useHead(unheadInstance, {
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
import { useHead } from 'unhead'
// A layout file sets the background color to red
useHead(unheadInstance, {
style: [
{
key: 'bg-color',
textContent: 'body { color: red }',
}
]
})
// In a page component, we want to change it to blue
useHead(unheadInstance, {
style: [
{
key: 'bg-color',
textContent: 'body { color: blue }',
}
]
})
// Result: Only the blue style is rendered
See Also
- Tag Positions: Control tag ordering
- useHead() API: Deduplication options