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/suspense-ssr-hydration-issues.md
1# Suspense SSR Hydration Issues and Workarounds23## Rule45`<Suspense>` has known issues with SSR hydration, particularly with async components. During initial hydration, Suspense may not properly include child components within its "cloak of suspense," leading to hydration mismatches, flickering, or runtime crashes.67## Why This Matters89In SSR applications, hydration mismatches cause:10- Visual flickering as the client re-renders11- Loss of state in affected components12- Console warnings in development (silent failures in production)13- Potential runtime crashes in edge cases14- Poor user experience, especially on slower networks1516## Bad Code1718```vue19<template>20<!-- Async component directly in Suspense can fail hydration -->21<Suspense>22<AsyncDashboard />23<template #fallback>24Loading...25</template>26</Suspense>27</template>2829<script setup>30import { defineAsyncComponent } from 'vue'3132const AsyncDashboard = defineAsyncComponent(33() => import('./Dashboard.vue')34)35</script>36```3738## Good Code3940### Solution 1: Wrap Async Components with Suspense4142```vue43<template>44<!-- Each async component wrapped in its own Suspense -->45<div class="dashboard">46<Suspense>47<AsyncHeader />48<template #fallback><HeaderSkeleton /></template>49</Suspense>5051<Suspense>52<AsyncContent />53<template #fallback><ContentSkeleton /></template>54</Suspense>55</div>56</template>57```5859### Solution 2: Use ClientOnly Wrapper (Nuxt/SSR Frameworks)6061```vue62<template>63<!-- Prevent SSR for problematic async components -->64<ClientOnly>65<Suspense>66<AsyncDashboard />67<template #fallback>68Loading dashboard...69</template>70</Suspense>7172<template #fallback>73<DashboardSkeleton />74</template>75</ClientOnly>76</template>77```7879### Solution 3: Prefetch with Proper Stale Time (with TanStack Query)8081```vue82<script setup>83import { useQuery, useQueryClient } from '@tanstack/vue-query'8485// IMPORTANT: All useQuery calls must be BEFORE any await86const { data, suspense } = useQuery({87queryKey: ['dashboard'],88queryFn: fetchDashboardData,89staleTime: 1000 * 60 * 5, // 5 minutes - prevents refetch after hydration90})9192// Wait for suspense AFTER all useQuery calls93await suspense()9495// Now safe to use data96</script>97```9899### Solution 4: Handle Hydration Errors Gracefully100101```vue102<script setup>103import { ref, onErrorCaptured, onMounted } from 'vue'104105const hydrationError = ref(false)106const isClient = ref(false)107108onMounted(() => {109isClient.value = true110})111112onErrorCaptured((err) => {113if (err.message?.includes('hydration')) {114hydrationError.value = true115return false116}117})118</script>119120<template>121<div v-if="hydrationError" class="hydration-recovery">122<!-- Force client-only re-render -->123<Suspense v-if="isClient">124<AsyncContent />125<template #fallback>Recovering...</template>126</Suspense>127</div>128129<Suspense v-else>130<AsyncContent />131<template #fallback>Loading...</template>132</Suspense>133</template>134```135136## Common SSR + Suspense Issues137138| Issue | Cause | Solution |139|-------|-------|----------|140| Hydration mismatch | Async chunk not loaded in time | Wrap with Suspense or use ClientOnly |141| Empty flash on Safari | Slow chunk loading | Preload critical chunks, use skeleton |142| useQuery after await error | Vue context lost after await | Put all useQuery calls before any await |143| Immediate refetch after hydration | staleTime too low | Set appropriate staleTime value |144145## Key Points1461471. Suspense + SSR has known edge cases - test thoroughly1482. Safari has slower chunk loading that triggers more hydration issues1493. With data-fetching libraries, ensure queries are set up before awaiting suspense1504. Consider ClientOnly wrappers for non-critical async content1515. Set appropriate staleTime to prevent unnecessary refetches after hydration1526. Use skeleton screens that match server-rendered content structure153154## References155156- [Vue.js Suspense Documentation](https://vuejs.org/guide/built-ins/suspense)157- [Vue Issue #6638 - Suspense hydration](https://github.com/vuejs/core/issues/6638)158- [Vue Issue #7672 - defineAsyncComponent SSR](https://github.com/vuejs/core/issues/7672)159- [TanStack Query SSR Discussion](https://github.com/TanStack/query/discussions/4870)160