Tag Sorting & Placement
Use tagPosition: 'head' | 'bodyOpen' | 'bodyClose' to choose a render location. Use tagPriority: 'critical' | 'high' | number | 'low' to influence order within a location.
Tag placement
The <link>, <script>, <noscript>, and <style> inputs accept an optional tagPosition property:
head: Render in the<head>(default)bodyOpen: Render at the start of the<body>bodyClose: Render at the end of the<body>
bodyClose lets the parser build the preceding body first. A script without async, defer, or type="module" still pauses parsing when the browser reaches it. See MDN's script loading reference.Placement examples
- Analytics scripts: Placing a script at
bodyCloselets the parser process the preceding body markup first, although downloading and executing the script still has a cost - Critical CSS: Place essential styles in
headwith high priority - Early body scripts: Use
bodyOpenonly when a script must run before the rest of the body; a classic synchronous script there still blocks parsing
import { useHead } from 'unhead'
// useHead: /docs/head/api/composables/use-head
useHead({
unheadInstance, script: [
{
src: '/my-lazy-script.js',
tagPosition: 'bodyClose',
},
],
})
// renders
// ...
// <script src="/my-lazy-script.js"></script>
// </body>
Server and client sort order
During server rendering, each tag receives a weight. Lower weights render first.
Capo.js weights are automatically applied to server-rendered tags to reduce critical request chains. The client applies explicit numeric and alias priorities, but does not recompute Capo ordering. The server weights are:
- -30:
<meta http-equiv="content-security-policy" ...> - -20:
<meta charset ...> - -15:
<meta name="viewport" ...> - -10:
<base> - 10:
<title> - 20:
<link rel="preconnect" ...> - 25:
<script type="importmap"> - 30:
<script async ...> - 40:
<style>@import ...</style> - 50: sync
<script>(inline orsrc) - 60:
<style>,<link rel="stylesheet" ...> - 70:
<link rel="preload" ...>,<link rel="modulepreload" ...> - 80:
<script defer ...>,<script type="module" ...> - 90:
<script type="speculationrules">,<link rel="prefetch" ...>,<link rel="dns-prefetch" ...>,<link rel="prerender" ...>
All other tags have a default priority of 100.
<script type="importmap"> is pinned at weight 25 so it is emitted before async scripts, module scripts, and modulepreload. The HTML Standard's import-map processing model allows multiple maps and prevents later maps from changing module resolutions that already occurred. Unhead therefore does not force-dedupe import maps. Set an explicit key for last-wins replacement instead.Override these default weights with the tagPriority property.
Setting tag priority
The tagPriority property can be set to an explicit weight, a string alias or a string to target a specific tag.
Sorting with Aliases
Aliases adjust a tag's current weight instead of replacing it. The server starts from its Capo.js weight; the client starts from a baseline of 100:
critical: subtracts 8high: subtracts 1low: adds 2
import { useHead } from 'unhead'
useHead({
unheadInstance, script: [
{
src: '/my-lazy-script.js',
tagPriority: 'low',
},
],
})
Sort by number
When providing a number, refer to the priorities set for critical tags above.
import { useHead } from 'unhead'
// A layout registers a script
useHead({
unheadInstance, script: [
{
src: '/not-important-script.js',
},
],
})
// but in our page we want to run a script before the above
useHead({
unheadInstance, script: [
{
src: '/very-important-script.js',
tagPriority: 0,
},
],
})
// <script src=\"/very-important-script.js\"></script>
// <script src=\"/not-important-script.js\"></script>
Sort with before: and after:
To place one tag relative to another, use the optional Alias Sorting Plugin.
Custom server weights
For custom ordering, pass a tagWeight function when creating the server head. It receives each tag and returns a numeric weight; lower values render first.
The default capoTagWeight function is exported from unhead/server so you can wrap it:
import { createHead, capoTagWeight } from 'unhead/server'
const head = createHead({
tagWeight(tag) {
// Promote SEO meta above styles for bot requests
if (isBot && tag.tag === 'meta' && tag.props.property?.startsWith('og:'))
return 55 // just above styles (60)
return capoTagWeight(tag)
}
})
The function can vary ordering by request context. For example, it can promote social metadata for crawler requests while retaining CAPO order for browsers.
meta-beyond-1mb heuristic that warns when large inline content pushes metadata beyond its default 1MB inspection threshold.Preserved order during hydration
When hydrating the state (e.g., SSR or page switch), Unhead replaces existing tags in their current position to avoid a flash of content.
This may cause tagPriority to be ignored during hydration. For client-side-only applications or SPAs, this isn't an issue, but for SSR applications, be aware that the initial render positions may be preserved during hydration.
Programmatic reordering
The tags:afterResolve hook receives tags after resolution and before DOM rendering. Modify ctx.tags when tagPriority is not expressive enough:
import { defineHeadPlugin } from 'unhead/plugins'
export const fontPreloadFirstPlugin = defineHeadPlugin({
key: 'font-preload-first',
hooks: {
'tags:afterResolve': (ctx) => {
const fontPreloads = ctx.tags.filter(tag =>
tag.tag === 'link'
&& tag.props.rel === 'preload'
&& tag.props.as === 'font'
)
const otherTags = ctx.tags.filter(tag =>
!(tag.tag === 'link'
&& tag.props.rel === 'preload'
&& tag.props.as === 'font')
)
ctx.tags = [...fontPreloads, ...otherTags]
}
}
})
See Also
- Alias Sorting Plugin: Sort tags by alias
- Handling Duplicates: Deduplication strategies
- useHead() API:
tagPriorityoption