Hooks
tags:afterResolve Hook
On this page
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
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:
interface TagResolveContext {
tagMap: Map<string, HeadTag>
tags: HeadTag[]
}
Returns
HookResult
which is either void
or Promise<void>
Usage Example
import { createHead } from '@unhead/angular'
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:
import { defineHeadPlugin } from '@unhead/angular'
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 </script tags
if (typeof tag.innerHTML === 'string') {
tag.innerHTML = tag.innerHTML.replace(/<\/script/gi, '<\\/script')
}
}
// Ensure all external scripts have integrity checks in production
if (process.env.NODE_ENV === 'production'
&& tag.tag === 'script'
&& tag.props.src
&& !tag.props.integrity
&& !tag.props.src.startsWith('/')) {
console.warn(`Script missing integrity check: ${tag.props.src}`)
}
// Check for unsafe inline styles
if (tag.tag === 'style' && tag.innerHTML
&& typeof tag.innerHTML === 'string'
&& tag.innerHTML.includes('expression(')) {
console.warn('Potentially unsafe CSS detected and removed')
tag.innerHTML = tag.innerHTML.replace(/expression\([^)]*\)/g, 'none')
}
})
}
}
})
Tag Reordering
Reorder tags for optimal page performance:
import { defineHeadPlugin } from '@unhead/angular'
export const performanceOptimizationPlugin = defineHeadPlugin({
hooks: {
'tags:afterResolve': (ctx) => {
// 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:
import { defineHeadPlugin } from '@unhead/angular'
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)}...`
}
}
})
}
}
})
Did this page help you?