TypeScript
Hooks

ssr:render Hook

The ssr:render hook is called during the server-side rendering process after tags have been resolved but before they're converted to HTML strings. This hook provides access to the finalized tags and allows for last-minute modifications specific to server-side rendering.

Hook Signature

export interface Hook {
  'ssr:render': (ctx: { tags: HeadTag[] }) => HookResult
}

Parameters

NameTypeDescription
ctxObjectContext object with rendering information
ctx.tagsHeadTag[]Array of finalized head tags being rendered

Returns

HookResult which is either void or Promise<void>

Usage Example

import { createHead } from 'unhead'

const head = createHead({
  hooks: {
    'ssr:render': (ctx) => {
      // Log the tags being rendered in SSR
      console.log(`Rendering ${ctx.tags.length} tags during SSR`)

      // Add a server-specific meta tag
      ctx.tags.push({
        tag: 'meta',
        props: {
          name: 'ssr-timestamp',
          content: Date.now().toString()
        }
      })
    }
  }
})

Use Cases

Server-specific Tag Modifications

Modify tags specifically for server-side rendering:

import { defineHeadPlugin } from 'unhead'

export const ssrTagsPlugin = defineHeadPlugin({
  hooks: {
    'ssr:render': (ctx) => {
      // Add SSR-specific attributes to all tags
      ctx.tags.forEach((tag) => {
        if (tag.props) {
          // Add data attribute to indicate this was rendered on the server
          tag.props['data-ssr'] = 'true'
        }
      })

      // Add server-rendered timestamp
      ctx.tags.push({
        tag: 'meta',
        props: {
          name: 'server-render-time',
          content: new Date().toISOString()
        }
      })

      // Add cache-control meta tag for browsers
      ctx.tags.push({
        tag: 'meta',
        props: {
          'http-equiv': 'Cache-Control',
          'content': 'no-cache, no-store, must-revalidate'
        }
      })
    }
  }
})

Platform-specific Optimizations

Apply optimizations based on the server platform:

import { defineHeadPlugin } from 'unhead'

export const platformOptimizationPlugin = defineHeadPlugin({
  hooks: {
    'ssr:render': (ctx) => {
      // Add preload hints based on platform capabilities
      const isNodeJS = typeof process !== 'undefined' && process.versions && process.versions.node

      if (isNodeJS) {
        // Modern Node.js - add HTTP/2 Server Push hints through special meta tags
        const criticalResources = [
          { path: '/css/main.css', as: 'style' },
          { path: '/js/main.js', as: 'script' }
        ]

        criticalResources.forEach((resource) => {
          ctx.tags.push({
            tag: 'link',
            props: {
              rel: 'preload',
              href: resource.path,
              as: resource.as,
              crossorigin: resource.as === 'font' ? 'anonymous' : undefined
            }
          })
        })
      }

      // Remove client-only script tags during SSR
      ctx.tags = ctx.tags.filter((tag) => {
        if (tag.tag === 'script' && tag.props.type === 'application/json') {
          // Keep JSON data scripts for hydration
          return true
        }
        if (tag.tag === 'script' && tag.props['data-client-only']) {
          // Remove client-only scripts during SSR
          return false
        }
        return true
      })
    }
  }
})

Internationalization Support

Handle internationalization (i18n) aspects in server rendering:

import { defineHeadPlugin } from 'unhead'

export const i18nSsrPlugin = defineHeadPlugin({
  hooks: {
    'ssr:render': (ctx) => {
      // Get current locale from your i18n system
      const currentLocale = getCurrentLocale() // Your locale detection logic
      const defaultLocale = getDefaultLocale()

      // Find existing language tags
      const htmlLangTag = ctx.tags.find(tag =>
        tag.tag === 'htmlAttrs' && tag.props.lang
      )

      // Update or add language attributes
      if (htmlLangTag) {
        htmlLangTag.props.lang = currentLocale
      }
      else {
        // Add lang attribute to html tag
        ctx.tags.push({
          tag: 'htmlAttrs',
          props: { lang: currentLocale }
        })
      }

      // Add language alternates for SEO
      const supportedLocales = getSupportedLocales() // Your locales list
      const currentPath = getCurrentPath() // Current request path

      supportedLocales.forEach((locale) => {
        // Generate localized URL
        const localizedUrl = generateLocalizedUrl(currentPath, locale)

        // Add hreflang link
        ctx.tags.push({
          tag: 'link',
          props: {
            rel: 'alternate',
            hreflang: locale,
            href: localizedUrl
          }
        })
      })

      // Add x-default hreflang
      ctx.tags.push({
        tag: 'link',
        props: {
          rel: 'alternate',
          hreflang: 'x-default',
          href: generateLocalizedUrl(currentPath, defaultLocale)
        }
      })
    }
  }
})
Did this page help you?