---
title: "Tag Sorting & Placement"
description: "Control where head tags render with tagPosition (head, bodyOpen, bodyClose) and tagPriority for ordering. Optimize performance with Capo.js weights."
canonical_url: "https://unhead.unjs.io/docs/head/guides/core-concepts/positions"
last_updated: "2026-05-01T15:01:54.611Z"
---

**Quick Answer:** Use `tagPosition: 'head' | 'bodyOpen' | 'bodyClose'` to control where tags render. Use `tagPriority: 'critical' | 'high' | number | 'low'` to control ordering within those positions.

## Introduction

By default, tags are rendered in the document `<head>` in a [specific order](#how-does-tag-sort-order-work) 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](#how-do-i-control-where-tags-render-in-the-document): To control where the tag is rendered in the document (e.g. `head`, `bodyClose`, `bodyOpen`, etc)
- [Sort Order](#how-does-tag-sort-order-work): To control the order of tags within the document section
- [Hooks](#how-can-i-programmatically-reorder-tags): For advanced use cases, the `tags:afterResolve` hook allows programmatic reordering

## How do I control where tags render in the document?

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>`

<note>

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.

</note>

### 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

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

// useHead: /docs/head/api/composables/use-head
useHead({
  script: [
    {
      src: '/my-lazy-script.js',
      tagPosition: 'bodyClose',
    },
  ],
})
// renders
//   ...
//   <script src="/my-lazy-script.js"></script>
// </body>
```

## How does tag sort order work?

All tags are given a weight with the lower the number, the higher the priority.

[Capo.js](https://rviscomi.github.io/capo.js/) weights are automatically applied to tags to avoid [Critical Request Chains](https://web.dev/critical-request-chains/). As
well as default weights to avoid site stability issues:

- **-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 or `src`)
- **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`.

<note>

`<script type="importmap">` is pinned at weight 25 so it is always emitted before async scripts, module scripts and `modulepreload` as required by the HTML spec. Multiple importmaps are allowed — browsers merge them into a single global import map — so unhead does not force-dedupe them. Use an explicit `key` if you want last-wins replacement instead of merging.

</note>

Escaping out of these default weights can be accomplished by setting the `tagPriority` property.

### How do I set 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**

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

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.

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

// 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](/docs/head/guides/plugins/alias-sorting) which provides a more intuitive way to order your tags relative to each other.

<tip>

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

</tip>

### How do I customise the default weights?

If you need full control over tag ordering, you can provide a custom `tagWeight` function when creating the server head. This 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:

```ts
import { createHead, capoTagWeight } from '@unhead/dynamic-import/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)
  }
})
```

This is useful when you need different `<head>` ordering based on the request context — for example, prioritising SEO meta tags for social crawlers while keeping the preload-scanner-optimised CAPO order for real browsers.

<tip>

The [Validate Plugin](/docs/head/guides/plugins/validate) includes a `meta-beyond-1mb` rule that warns when meta tags are pushed past the 1MB crawler parsing limit by large inline styles.

</tip>

### Why is tagPriority ignored 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.

## How can I programmatically reorder tags?

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`.

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

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]
})
```

<tip>

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

</tip>

## Key Takeaways

<tip>

- Use `tagPosition` to control where tags render (head, bodyOpen, bodyClose)
- Use `tagPriority` for ordering: 'critical', 'high', number, 'low'
- Critical tags like charset and viewport should have highest priority
- Scripts can be moved to body end for better performance

</tip>

## See Also

- [Alias Sorting Plugin](/docs/head/guides/plugins/alias-sorting) - Sort tags by alias
- [Handling Duplicates](/docs/head/guides/core-concepts/handling-duplicates) - Deduplication strategies
- [useHead() API](/docs/head/api/composables/use-head) - tagPriority option
