--- title: Implement getSSRProps for Custom Directives in SSR impact: MEDIUM impactDescription: Custom directives without SSR handling cause hydration mismatches or missing functionality type: best-practice tags: [vue3, ssr, directives, custom-directive, server-side-rendering, nuxt] --- # Implement getSSRProps for Custom Directives in SSR **Impact: MEDIUM** - Custom directives only have access to the DOM on the client side. During SSR, the directive's `mounted` and `updated` hooks never run. If your directive sets attributes or modifies the element, you must implement `getSSRProps` to return equivalent attributes for server rendering. Without `getSSRProps`, the server-rendered HTML won't include the directive's effects, causing hydration mismatches when the client applies the directive. ## Task Checklist - [ ] Add `getSSRProps` hook to directives that modify element attributes - [ ] Return an object with HTML attributes to render on server - [ ] Test directive behavior in both SSR and client-only contexts - [ ] Consider using components instead of directives for complex SSR cases **Incorrect - Client-Only Directive:** ```javascript // WRONG: No SSR handling - directive effects missing on server const vTooltip = { mounted(el, binding) { el.setAttribute('data-tooltip', binding.value) el.setAttribute('aria-label', binding.value) el.classList.add('has-tooltip') } } ``` Server renders: ```html ``` Client after hydration: ```html ``` **Correct - With getSSRProps:** ```javascript // CORRECT: SSR-compatible directive const vTooltip = { // Client-side implementation mounted(el, binding) { el.setAttribute('data-tooltip', binding.value) el.setAttribute('aria-label', binding.value) el.classList.add('has-tooltip') }, // SSR implementation - returns attributes to render getSSRProps(binding) { return { 'data-tooltip': binding.value, 'aria-label': binding.value, class: 'has-tooltip' } } } ``` Server now renders: ```html ``` ## Complete SSR Directive Example ```javascript // directives/vFocus.js export const vFocus = { // Client: Actually focus the element mounted(el, binding) { if (binding.value !== false) { el.focus() } }, // SSR: Add autofocus attribute so browser focuses on load getSSRProps(binding) { if (binding.value !== false) { return { autofocus: true } } return {} } } ``` ```vue ``` ## Directive with Dynamic ID ```javascript // CORRECT: Generate consistent IDs const vId = { mounted(el, binding) { el.id = binding.value || `el-${binding.instance?.$.uid}` }, getSSRProps(binding, vnode) { // Use the same ID generation logic return { id: binding.value || `el-${vnode.component?.uid || 'ssr'}` } } } ``` ## Handling Complex Directives For directives that do more than set attributes, consider: ```javascript // Directive that only makes sense on client (e.g., drag-and-drop) const vDraggable = { mounted(el, binding) { // Complex client-side logic initDragAndDrop(el, binding.value) }, unmounted(el) { destroyDragAndDrop(el) }, // SSR: Just mark element as draggable for styling/semantics getSSRProps(binding) { return { draggable: 'true', 'data-draggable': '', role: 'listitem' } } } ``` ## Directives That Cannot Have SSR Equivalents Some directives have no meaningful server-side representation: ```javascript // Directive that tracks mouse position - no SSR equivalent const vMousePosition = { mounted(el, binding) { el.addEventListener('mousemove', (e) => { binding.value?.(e.clientX, e.clientY) }) }, // Nothing meaningful to render on server getSSRProps() { return {} // Empty object - no attributes } } ``` ## Nuxt.js Directive Registration ```javascript // plugins/directives.ts export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.directive('tooltip', { mounted(el, binding) { el.setAttribute('data-tooltip', binding.value) }, getSSRProps(binding) { return { 'data-tooltip': binding.value } } }) }) ``` ## Testing SSR Directives ```javascript import { renderToString } from 'vue/server-renderer' import { createSSRApp, h } from 'vue' import { vTooltip } from './directives/vTooltip' test('vTooltip renders attributes during SSR', async () => { const app = createSSRApp({ directives: { tooltip: vTooltip }, template: '' }) const html = await renderToString(app) expect(html).toContain('data-tooltip="Help text"') expect(html).toContain('aria-label="Help text"') }) ``` ## Reference - [Vue.js Custom Directives - SSR](https://vuejs.org/guide/reusability/custom-directives.html#custom-directive-api) - [Vue.js SSR - Custom Directives](https://vuejs.org/guide/scaling-up/ssr.html#custom-directives)