--- 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)