--- title: "ssr:rendered Hook" description: "Learn about the ssr:rendered hook in Unhead that's called after server-side rendering of head tags" navigation: title: "ssr:rendered" --- The `ssr:rendered` hook is called after the server-side rendering process has completed and all head tags have been converted to HTML strings. This hook provides access to the final HTML output and allows for post-processing of the rendered HTML. ## Hook Signature ```ts export interface Hook { 'ssr:rendered': (ctx: SSRRenderContext) => HookResult } ``` ### Parameters | Name | Type | Description | |------|------|-------------| | `ctx` | `SSRRenderContext` | Context object with rendering results | The `SSRRenderContext` interface is defined as: ```ts interface SSRRenderContext { tags: HeadTag[] html: SSRHeadPayload } interface SSRHeadPayload { headTags: string bodyTags: string bodyTagsOpen: string htmlAttrs: string bodyAttrs: string } ``` ### Returns `HookResult` which is either `void` or `Promise<void>` ## Usage Example ```ts import { createHead } from '@unhead/dynamic-import' const head = createHead({ hooks: { 'ssr:rendered': (ctx) => { // Log the rendered HTML console.log('Head rendering complete:') console.log('HTML attributes:', ctx.html.htmlAttrs) console.log('Head tags:', ctx.html.headTags) // Modify rendered HTML if needed ctx.html.headTags += `<!-- Server rendered at ${new Date().toISOString()} -->` } } }) ``` ## Use Cases ### HTML Post-processing Apply final transformations to the rendered HTML: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const htmlPostProcessingPlugin = defineHeadPlugin({ hooks: { 'ssr:rendered': (ctx) => { // Add server timing information as an HTML comment const renderTime = process.hrtime(globalThis.__UNHEAD_RENDER_START || [0, 0]) const renderTimeMs = Math.round((renderTime[0] * 1000) + (renderTime[1] / 1000000)) ctx.html.headTags += `\n<!-- Unhead SSR render time: ${renderTimeMs}ms -->` // Add server information for debugging in staging environments if (process.env.NODE_ENV === 'staging') { ctx.html.headTags += `\n<!-- Server: ${process.env.SERVER_ID || 'unknown'}, Node: ${process.version} -->` } // Apply minification to HTML in production if (process.env.NODE_ENV === 'production') { // Simple minification - remove unnecessary whitespace ctx.html.headTags = ctx.html.headTags .replace(/>\s+</g, '><') .replace(/\s{2,}/g, ' ') .trim() } } } }) ``` ### Caching Rendered Output Cache the rendered HTML for performance optimization: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const ssrCachePlugin = defineHeadPlugin({ hooks: { 'ssr:rendered': (ctx) => { // Get the current cache key (set in ssr:beforeRender) const cacheKey = globalThis.__UNHEAD_CURRENT_CACHE_KEY if (cacheKey) { // Initialize cache if needed globalThis.__UNHEAD_CACHE = globalThis.__UNHEAD_CACHE || {} // Store rendered HTML in cache globalThis.__UNHEAD_CACHE[cacheKey] = { html: { ...ctx.html }, timestamp: Date.now(), tags: ctx.tags.length } // Log caching information console.log(`Cached head HTML for key: ${cacheKey} (${ctx.tags.length} tags)`) // Set expiration time setTimeout(() => { if (globalThis.__UNHEAD_CACHE && globalThis.__UNHEAD_CACHE[cacheKey]) { delete globalThis.__UNHEAD_CACHE[cacheKey] console.log(`Expired head HTML cache for key: ${cacheKey}`) } }, 300000) // Expire after 5 minutes } } } }) ``` ### Analytics and Monitoring Collect metrics about the server rendering process: ```ts import { defineHeadPlugin } from '@unhead/dynamic-import' export const ssrAnalyticsPlugin = defineHeadPlugin({ hooks: { 'ssr:rendered': (ctx) => { // Calculate sizes for monitoring const metrics = { tagsCount: ctx.tags.length, htmlAttrsSize: ctx.html.htmlAttrs.length, headTagsSize: ctx.html.headTags.length, bodyTagsSize: ctx.html.bodyTags.length, bodyAttrsSize: ctx.html.bodyAttrs.length, totalSize: ctx.html.htmlAttrs.length + ctx.html.headTags.length + ctx.html.bodyTags.length + ctx.html.bodyAttrs.length + ctx.html.bodyTagsOpen.length } // Record metrics if (process.env.COLLECT_METRICS) { recordMetrics('unhead_ssr', metrics) } // Log warnings for unusually large payloads if (metrics.totalSize > 20000) { console.warn(`Large head payload detected: ${metrics.totalSize} bytes`) console.warn(`Tags breakdown: HTML attrs (${metrics.htmlAttrsSize}), ` + `Head tags (${metrics.headTagsSize}), ` + `Body tags (${metrics.bodyTagsSize})`) } } } }) // Placeholder for your metrics collection system function recordMetrics(name, data) { console.log(`Recording metrics for ${name}:`, data) // Your actual metrics recording logic here } ```