--- title: "dom:rendered Hook" description: "Learn about the dom:rendered hook in Unhead that's called after all tags have been rendered to the DOM" navigation: title: "dom:rendered" --- The `dom:rendered` hook is called after all tags have been rendered to the DOM. This hook provides access to the rendered elements and is useful for post-rendering operations, measurements, or triggering side effects that depend on the DOM being updated. ## Hook Signature ```ts export interface Hook { 'dom:rendered': (ctx: { renders: DomRenderTagContext[] }) => HookResult } ``` ### Parameters | Name | Type | Description | |------|------|-------------| | `ctx` | Object | Context object containing rendering information | | `ctx.renders` | `DomRenderTagContext[]` | Array of rendered tag contexts | The `DomRenderTagContext` interface is defined as: ```ts interface DomRenderTagContext { id: string $el: Element shouldRender: boolean tag: HeadTag entry?: HeadEntry<any> markSideEffect: (key: string, fn: () => void) => void } ``` ### Returns `HookResult` which is either `void` or `Promise<void>` ## Usage Example ```ts import { createHead } from '@unhead/dynamic-import' const head = createHead({ hooks: { 'dom:rendered': (ctx) => { // Log total number of elements rendered console.log(`Finished rendering ${ctx.renders.length} tags to the DOM`) // Add a class to the document to indicate rendering is complete document.documentElement.classList.add('head-rendered') // Dispatch an event that other code can listen for window.dispatchEvent(new CustomEvent('head-rendered', { detail: { count: ctx.renders.length } })) } } }) ``` ## Use Cases ### Performance Monitoring Track and report rendering performance: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const performanceMonitorPlugin = defineHeadPlugin({ hooks: { 'dom:rendered': (ctx) => { // Calculate rendering metrics const renderCount = ctx.renders.length // Record performance entry if (window.performance && window.performance.mark) { window.performance.mark('unhead-dom-rendered') // Calculate time since navigation start const navStart = window.performance.timing.navigationStart const renderTime = Date.now() - navStart // Log performance data console.log(`Head rendering complete: ${renderCount} tags in ${renderTime}ms`) // Create a performance measure window.performance.measure('unhead-render-time', 'navigationStart', 'unhead-dom-rendered') } // Report to analytics if available if (window.dataLayer) { window.dataLayer.push({ event: 'unhead_rendered', unhead_render_count: renderCount }) } } } }) ``` ### Post-rendering Operations Perform operations that need to happen after all tags are rendered: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const postRenderPlugin = defineHeadPlugin({ hooks: { 'dom:rendered': (ctx) => { // Find all script tags that were rendered const scriptElements = ctx.renders .filter(render => render.tag.tag === 'script') .map(render => render.$el) // Set up monitoring for script completion if (scriptElements.length > 0) { let loadedCount = 0 // Track when all scripts have loaded const scriptLoadHandler = () => { loadedCount++ if (loadedCount === scriptElements.length) { console.log('All head scripts have loaded') document.body.classList.add('scripts-ready') // Notify application that scripts are ready window.dispatchEvent(new CustomEvent('head-scripts-loaded')) } } // Monitor each script scriptElements.forEach((script) => { if (script.hasAttribute('async') || script.hasAttribute('defer')) { script.addEventListener('load', scriptLoadHandler, { once: true }) script.addEventListener('error', scriptLoadHandler, { once: true }) } else { // Synchronous scripts are already loaded at this point loadedCount++ } }) // If all scripts were synchronous, trigger the event now if (loadedCount === scriptElements.length) { scriptLoadHandler() } } } } }) ``` ### DOM Cleanup Clean up old or unused elements after rendering: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const domCleanupPlugin = defineHeadPlugin({ hooks: { 'dom:rendered': (ctx) => { // Get IDs of all rendered elements const renderedIds = new Set(ctx.renders.map(render => render.id)) // Find elements with data-unhead-id that weren't in this render batch const outdatedElements = document.querySelectorAll('[data-unhead-id]') Array.from(outdatedElements).forEach((element) => { const id = element.getAttribute('data-unhead-id') if (id && !renderedIds.has(id)) { // This element was from a previous render and is no longer needed console.log('Removing outdated head element:', element) element.parentNode?.removeChild(element) } }) // Clean up any temporary markers document.querySelectorAll('[data-unhead-temp]').forEach((el) => { el.parentNode?.removeChild(el) }) } } }) ```