TypeScript
Core Concepts

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.

Proper tag positioning is critical for performance optimization and can significantly impact page load times and user experience.

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>
Using 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'

useHead(unheadInstance, {
  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: -8
  • high: -1
  • low: 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'

// some layout we have a js file that is ran
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:

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.

The Alias Sorting Plugin is particularly useful when you need precise control over tag order but don't want to manage numerical priorities directly.

Hydration Caveats

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.

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'

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]
})
The hooks approach is particularly useful for:
  • 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
Did this page help you?