Tag Sorting & Placement
Introduction
By default, tags are rendered in the document <head>
in a specific order for optimal performance and compatibility.
However, this is not always useful, say if you need to render a script at the end of the document or have a specific placement of a tag.
To solve these issues we have three options:
- Document Placement: To control where the tag is rendered in the document (e.g.
head
,bodyClose
,bodyOpen
, etc) - Sort Order: To control the order of tags within the document section
- Hooks: For advanced use cases, the
tags:afterResolve
hook allows programmatic reordering
Document Placement
For the <script>
, <noscript>
and <style>
tags you may provide an optional tagPosition
property with the possible values:
head
- Render in the<head>
(default)bodyOpen
- Render at the start of the<body>
bodyClose
- Render at the end of the<body>
bodyClose
for scripts that aren't critical for page rendering can significantly improve page load performance, as these scripts won't block the initial render.Common Use Cases
- Analytics Scripts: Place tracking scripts at
bodyClose
to avoid impacting page performance - Critical CSS: Place essential styles in
head
with high priority - Polyfills: Place in
bodyOpen
when they need to be loaded early but not block rendering
import { useHead } from '@unhead/react'
useHead({
script: [
{
src: '/my-lazy-script.js',
tagPosition: 'bodyClose',
},
],
})
// renders
// ...
// <script src="/my-lazy-script.js"></script>
// </body>
Sort Order
All tags are given a weight with the lower the number, the higher the priority.
Capo.js weights are automatically applied to tags to avoid Critical Request Chains. As well as default weights to avoid site stability issues:
- -20:
<meta charset ...>
- -10:
<base>
- 0:
<meta http-equiv="content-security-policy" ...>
- 10:
<title>
- 20:
<link rel="preconnect" ...>
All other tags have a default priority of 100
.
Escaping out of these default weights can be accomplished by setting the tagPriority
property.
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
Using an alias to set the position of a tag is the best practice as it allows you to retain the existing capo.js weights that are configured for performance.
critical
: -8high
: -1low
: 2
import { useHead } from '@unhead/react'
useHead({
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/react'
// some layout we have a js file that is ran
useHead({
script: [
{
src: '/not-important-script.js',
},
],
})
// but in our page we want to run a script before the above
useHead({
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:
If you'd like to place a tag before or after another tag, you can use the optional Alias Sorting Plugin which provides a more intuitive way to order your tags relative to each other.
Hydration Caveats
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.Using Hooks for Tag Reordering
For advanced use cases where you need programmatic control over tag ordering, Unhead provides a powerful hook system.
The tags:afterResolve
hook gives you access to the tags after they've been resolved but before they're rendered to the DOM. This allows for custom ordering logic beyond what's possible with tagPriority
.
import { injectHead } from '@unhead/react'
const head = injectHead()
// Hook into the tags:afterResolve lifecycle
head.hooks.hook('tags:afterResolve', (ctx) => {
// ctx.tags is an array of all tags that will be rendered
// You can reorder, filter, or modify them before they are rendered
// Example: Move all font preloads to the beginning
const fontPreloads = ctx.tags.filter(tag =>
tag.tag === 'link'
&& tag.props.rel === 'preload'
&& tag.props.as === 'font'
)
// Remove the font preloads from their current position
ctx.tags = ctx.tags.filter(tag =>
!(tag.tag === 'link'
&& tag.props.rel === 'preload'
&& tag.props.as === 'font')
)
// Add them to the beginning of the array
ctx.tags = [...fontPreloads, ...ctx.tags]
})
- Complex ordering logic that depends on runtime conditions
- Dynamic reordering based on user preferences or device capabilities
- Implementation of custom sorting algorithms for specific tag types