--- title: "dom:renderTag Hook" description: "Learn about the dom:renderTag hook in Unhead that controls how individual tags are rendered to the DOM" navigation: title: "dom:renderTag" --- The `dom:renderTag` hook is called for each individual tag as it's being rendered to the DOM. This hook gives you fine-grained control over how each tag is rendered, with access to the DOM element, tag data, and the ability to track side effects. ## Hook Signature ```ts export interface Hook { 'dom:renderTag': (ctx: DomRenderTagContext, document: Document, track: any) => HookResult } ``` ### Parameters | Name | Type | Description | |------|------|-------------| | `ctx` | `DomRenderTagContext` | Context object with tag information | | `document` | `Document` | The DOM document where rendering occurs | | `track` | `any` | Utility for tracking side effects | The `DomRenderTagContext` interface is defined as: ```ts interface DomRenderTagContext { id: string $el: Element shouldRender: boolean tag: HeadTag entry?: HeadEntry markSideEffect: (key: string, fn: () => void) => void } ``` ### Returns `HookResult` which is either `void` or `Promise` ## Usage Example ```ts import { createHead } from '@unhead/dynamic-import' const head = createHead({ hooks: { 'dom:renderTag': (ctx, document) => { const { tag, $el } = ctx // Log each tag being rendered console.log(`Rendering ${tag.tag} to the DOM`) // Add additional attribute to all rendered elements $el.setAttribute('data-rendered-at', new Date().toISOString()) // Prevent rendering specific tags if (tag.tag === 'meta' && tag.props.name === 'robots' && tag.props.content === 'noindex') { ctx.shouldRender = false } } } }) ``` ## Use Cases ### Custom Element Creation Customize how DOM elements are created and configured: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const customElementPlugin = defineHeadPlugin({ hooks: { 'dom:renderTag': (ctx, document) => { const { tag, $el } = ctx // Apply special handling for script tags if (tag.tag === 'script') { // Add custom attribute to track script loading performance $el.setAttribute('data-load-start', Date.now().toString()) // Set up performance tracking ctx.markSideEffect('script-tracking', () => { $el.addEventListener('load', () => { const loadStart = Number.parseInt($el.getAttribute('data-load-start') || '0') const loadTime = Date.now() - loadStart console.log(`Script loaded in ${loadTime}ms:`, tag.props.src) }, { once: true }) }) } // Enhance meta tags with additional data if (tag.tag === 'meta') { // Add data attribute for debugging $el.setAttribute('data-unhead-id', ctx.id) } } } }) ``` ### Conditional Rendering with Feature Detection Conditionally render tags based on browser capabilities: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const featureDetectionPlugin = defineHeadPlugin({ hooks: { 'dom:renderTag': (ctx) => { const { tag } = ctx // Skip rendering preload fonts if the browser doesn't support font loading API if (tag.tag === 'link' && tag.props.rel === 'preload' && tag.props.as === 'font' && !('FontFace' in window)) { ctx.shouldRender = false return } // Conditionally apply WebP images for supported browsers if (tag.tag === 'meta' && (tag.props.property === 'og:image' || tag.props.name === 'twitter:image')) { // Check for WebP support const supportsWebP = document.createElement('canvas') .toDataURL('image/webp') .indexOf('data:image/webp') === 0 // Modify image URLs for WebP-capable browsers if (supportsWebP && tag.props.content && !tag.props.content.endsWith('.webp') && !tag.props.content.includes('?format=')) { tag.props.content = `${tag.props.content}?format=webp` } } } } }) ``` ### Advanced Event Handling Set up sophisticated event handling for rendered elements: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const eventHandlingPlugin = defineHeadPlugin({ hooks: { 'dom:renderTag': (ctx) => { const { tag, $el } = ctx // Handle script loading with retries if (tag.tag === 'script' && tag.props.src) { ctx.markSideEffect(`script-retry-${tag.props.src}`, () => { // Track failures let attempts = 0 const maxAttempts = 3 // Function to handle errors and retry loading function handleScriptError() { attempts++ if (attempts < maxAttempts) { console.warn(`Script failed to load, retrying (${attempts}/${maxAttempts}):`, tag.props.src) // Create a new script element const newScript = document.createElement('script') // Copy all attributes Array.from($el.attributes).forEach((attr) => { newScript.setAttribute(attr.name, attr.value) }) // Replace the failed script $el.parentNode?.insertBefore(newScript, $el) $el.parentNode?.removeChild($el) // Update reference to the new element ctx.$el = newScript } else { console.error(`Script failed to load after ${maxAttempts} attempts:`, tag.props.src) } } // Add error handler $el.addEventListener('error', handleScriptError, { once: true }) }) } } } })