--- title: "tag:normalise Hook" description: "Learn about the tag:normalise hook in Unhead that processes individual tags before rendering" navigation: title: "tag:normalise" --- The `tag:normalise` hook is called for each individual tag during the normalization process. This hook gives you access to a single tag, its parent entry, and resolved options, allowing you to apply fine-grained modifications to specific tags. ## Hook Signature ```ts export interface Hook { 'tag:normalise': (ctx: { tag: HeadTag entry: HeadEntry resolvedOptions: CreateClientHeadOptions }) => HookResult } ``` ### Parameters | Name | Type | Description | |------|------|-------------| | `ctx` | Object | Context object containing the tag information | | `ctx.tag` | `HeadTag` | The head tag being normalized | | `ctx.entry` | `HeadEntry` | The entry that generated this tag | | `ctx.resolvedOptions` | `CreateClientHeadOptions` | The resolved options for the head instance | ### Returns `HookResult` which is either `void` or `Promise` ## Usage Example ```ts import { createHead } from '@unhead/dynamic-import' const head = createHead({ hooks: { 'tag:normalise': (ctx) => { const { tag, entry } = ctx // Apply specific modifications based on tag type if (tag.tag === 'link' && tag.props.rel === 'stylesheet') { // Add integrity check to all stylesheets tag.props.crossorigin = 'anonymous' } // Add source information for debugging tag._source = entry.options.source || 'unknown' } } }) ``` ## Use Cases ### Fine-tuning Specific Tag Types This hook is ideal for applying modifications to specific types of tags: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const scriptSecurityPlugin = defineHeadPlugin({ hooks: { 'tag:normalise': (ctx) => { const { tag } = ctx // Add security attributes to all script tags if (tag.tag === 'script' && tag.props.src) { // Apply Content Security Policy attributes tag.props.crossorigin = 'anonymous' // Add nonce for CSP if available if (globalThis.SCRIPT_NONCE) { tag.props.nonce = globalThis.SCRIPT_NONCE } } } } }) ``` ### Tag Transformation Based on Environment Transform tags differently based on the environment: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const environmentSpecificPlugin = defineHeadPlugin({ hooks: { 'tag:normalise': (ctx) => { const { tag, resolvedOptions } = ctx const isDevelopment = process.env.NODE_ENV === 'development' // Handle environment-specific transformations if (tag.tag === 'meta' && tag.props.name === 'robots') { // Prevent indexing in development or staging environments if (isDevelopment || resolvedOptions.environment === 'staging') { tag.props.content = 'noindex, nofollow' } } // Add debug information in development if (isDevelopment && tag.tag !== 'comment') { tag.props['data-dev-info'] = `Entry: ${ctx.entry._i}` } } } }) ``` ### Custom Attribute Processing Process custom attributes that need special handling: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const dataAttributeProcessingPlugin = defineHeadPlugin({ hooks: { 'tag:normalise': (ctx) => { const { tag } = ctx // Process data attributes with special formatting Object.keys(tag.props).forEach((prop) => { if (prop.startsWith('data-')) { // Convert camelCase values to kebab-case for data attributes if (typeof tag.props[prop] === 'string' && tag.props[prop].includes('_')) { tag.props[prop] = tag.props[prop] .replace(/_([a-z])/g, (_, char) => `-${char.toLowerCase()}`) } // Convert objects to JSON strings for data attributes if (typeof tag.props[prop] === 'object' && tag.props[prop] !== null) { tag.props[prop] = JSON.stringify(tag.props[prop]) } } }) } } }) ```