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