--- title: "tags:afterResolve Hook" description: "Learn about the tags:afterResolve hook in Unhead that runs after tags have been fully resolved" navigation: title: "tags:afterResolve" --- The `tags:afterResolve` hook is called after all tags have been resolved but before they are rendered. This is the final opportunity to modify tags before they are either rendered to the DOM (client-side) or serialized to HTML (server-side). ## Hook Signature ```ts export interface Hook { 'tags:afterResolve': (ctx: TagResolveContext) => HookResult } ``` ### Parameters | Name | Type | Description | |------|------|-------------| | `ctx` | `TagResolveContext` | Context object with the finalized tags | The `TagResolveContext` interface is defined as: ```ts interface TagResolveContext { tagMap: Map tags: HeadTag[] } ``` ### Returns `HookResult` which is either `void` or `Promise` ## Usage Example ```ts import { createHead } from '@unhead/dynamic-import' const head = createHead({ hooks: { 'tags:afterResolve': (ctx) => { // Log the final set of tags that will be rendered console.log(`Final tag count: ${ctx.tags.length}`) // Perform last-minute adjustments ctx.tags.forEach((tag) => { // Add data attribute to all tags for tracking if (tag.props && typeof tag.props === 'object') { tag.props['data-rendered-by'] = 'unhead' } }) } } }) ``` ## Use Cases ### Final Security Checks Implement last-minute security checks before tags are rendered: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const securityPlugin = defineHeadPlugin({ hooks: { 'tags:afterResolve': (ctx) => { // Perform security checks before rendering ctx.tags.forEach((tag) => { // Sanitize inline script content if (tag.tag === 'script' && tag.innerHTML) { // Prevent XSS by encoding { // Categorize tags by type for optimal ordering const preconnect = [] const preload = [] const css = [] const scripts = [] const asyncScripts = [] const otherTags = [] // Sort tags into categories ctx.tags.forEach((tag) => { if (tag.tag === 'link' && tag.props.rel === 'preconnect') { preconnect.push(tag) } else if (tag.tag === 'link' && tag.props.rel === 'preload') { preload.push(tag) } else if (tag.tag === 'link' && tag.props.rel === 'stylesheet') { css.push(tag) } else if (tag.tag === 'script' && !tag.props.async && !tag.props.defer) { scripts.push(tag) } else if (tag.tag === 'script') { asyncScripts.push(tag) } else { otherTags.push(tag) } }) // Reorder tags for optimal loading ctx.tags = [ ...preconnect, // Preconnect first for early connection establishment ...preload, // Preload critical resources ...css, // Load stylesheets ...otherTags, // Other tags ...asyncScripts, // Async scripts that don't block rendering ...scripts // Synchronous scripts last ] } } }) ``` ### Final Content Processing Process content one last time before rendering: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const finalContentProcessingPlugin = defineHeadPlugin({ hooks: { 'tags:afterResolve': (ctx) => { // Final processing for text content ctx.tags.forEach((tag) => { // Process title tag if (tag.tag === 'title' && tag.textContent) { // Ensure title doesn't exceed maximum length if (typeof tag.textContent === 'string' && tag.textContent.length > 60) { tag.textContent = `${tag.textContent.substring(0, 57)}...` } } // Ensure all meta descriptions are properly formatted if (tag.tag === 'meta' && (tag.props.name === 'description' || tag.props.property === 'og:description') && tag.props.content) { const content = tag.props.content // Clean up whitespace tag.props.content = content.replace(/\s+/g, ' ').trim() // Add ellipsis if truncated if (content.length > 160) { tag.props.content = `${content.substring(0, 157)}...` } } }) } } }) ```