Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/lifecycle-ssr-awareness.md
1---2title: Mounted and Unmounted Hooks Do Not Run During SSR3impact: MEDIUM4impactDescription: SSR applications may fail if mounted-only code is essential for functionality5type: capability6tags: [vue3, lifecycle, ssr, server-side-rendering, nuxt, onMounted, mounted, hydration]7---89# Mounted and Unmounted Hooks Do Not Run During SSR1011**Impact: MEDIUM** - During server-side rendering (SSR), lifecycle hooks like `mounted`, `onMounted`, `unmounted`, and `onUnmounted` are never called on the server. This can cause differences between server-rendered and client-rendered content, hydration mismatches, and missing functionality if critical logic is placed only in these hooks.1213On the server, only `beforeCreate`, `created`, and their Composition API equivalents run. Client-specific operations (DOM access, browser APIs, third-party libraries) must be in mounted hooks, but you must handle the SSR case appropriately.1415## Task Checklist1617- [ ] Place browser-specific code (window, document, localStorage) in mounted/onMounted18- [ ] Ensure critical data fetching happens in hooks that run on server (created)19- [ ] Handle hydration mismatches for content that differs client vs server20- [ ] Use `<ClientOnly>` wrapper (Nuxt) or conditional rendering for client-only components21- [ ] Check for browser environment before using browser APIs2223**Incorrect:**24```javascript25// WRONG: Accessing browser APIs in created - breaks SSR26export default {27created() {28// These don't exist on the server!29this.width = window.innerWidth // ReferenceError: window is not defined30this.savedData = localStorage.getItem('data') // ReferenceError: localStorage is not defined31}32}33```3435```javascript36// WRONG: Critical initialization only in mounted - won't run on server37export default {38data() {39return { user: null }40},41async mounted() {42// This won't run on server - page renders without user data43// Then hydrates with user data - causes flash of content44this.user = await fetchCurrentUser()45}46}47```4849**Correct:**50```javascript51// CORRECT: Data fetching in created (runs on server), DOM in mounted52export default {53data() {54return {55user: null,56windowWidth: 057}58},59async created() {60// This runs on both server and client61this.user = await fetchCurrentUser()62},63mounted() {64// Browser-specific code safely in mounted65this.windowWidth = window.innerWidth66window.addEventListener('resize', this.handleResize)67},68unmounted() {69window.removeEventListener('resize', this.handleResize)70}71}72```7374```vue75<!-- CORRECT: Composition API with SSR awareness -->76<script setup>77import { ref, onMounted, onUnmounted } from 'vue'7879const user = ref(null)80const windowWidth = ref(0)8182// This runs on both server and client (during setup)83user.value = await useFetch('/api/user')8485// These only run on client86onMounted(() => {87windowWidth.value = window.innerWidth88window.addEventListener('resize', handleResize)89})9091onUnmounted(() => {92window.removeEventListener('resize', handleResize)93})9495function handleResize() {96windowWidth.value = window.innerWidth97}98</script>99```100101## Checking for Browser Environment102103```javascript104// CORRECT: Guard browser API access105export default {106data() {107return { theme: 'light' }108},109created() {110// Check if we're in browser before accessing browser APIs111if (typeof window !== 'undefined') {112this.theme = localStorage.getItem('theme') || 'light'113}114},115mounted() {116// mounted only runs in browser, so this is always safe117this.applyTheme()118}119}120```121122## Nuxt.js Specific Patterns123124```vue125<!-- CORRECT: Using Nuxt's ClientOnly for client-specific components -->126<template>127<div>128<!-- This content renders on both server and client -->129<h1>Dashboard</h1>130131<!-- This only renders on client - no hydration mismatch -->132<ClientOnly>133<ChartComponent :data="chartData" />134<template #fallback>135<p>Loading chart...</p>136</template>137</ClientOnly>138</div>139</template>140```141142```javascript143// CORRECT: Using Nuxt's process.client/process.server144export default {145created() {146if (process.client) {147// Only runs in browser148this.initAnalytics()149}150if (process.server) {151// Only runs on server152this.logServerRequest()153}154}155}156```157158## Handling Hydration Mismatches159160```vue161<script setup>162import { ref, onMounted } from 'vue'163164// Start with a value that matches what server renders165const currentTime = ref(null)166167onMounted(() => {168// Update to real value only on client169// This prevents hydration mismatch170currentTime.value = new Date().toLocaleTimeString()171})172</script>173174<template>175<!-- Renders null on server, then updates on client -->176<span v-if="currentTime">{{ currentTime }}</span>177<span v-else>Loading...</span>178</template>179```180181## Reference182- [Vue.js SSR Guide](https://vuejs.org/guide/scaling-up/ssr.html)183- [Nuxt.js Lifecycle](https://nuxt.com/docs/api/composables/use-nuxt-app#lifecycle-hooks)184- [Vue SSR Hydration](https://vuejs.org/guide/scaling-up/ssr.html#client-hydration)185