Cansu Arı Logo
Blog
Nedir?

SSR’de Hydration Mismatch Hatalarının Kök Sebebi

Nuxt ve Vue projelerinde SSR sonrası sık görülen 'Hydration Mismatch' hatası, genellikle DOM farklarından kaynaklanır. Bu hatanın nedenlerini, nasıl oluştuğunu ve nasıl çözüleceğini anlatıyoruz.

  • #Nuxt 3
  • #Vue.js

Skip to content

Hydration completed but contains mismatches.💥

SSR (Server-Side Rendering) modern framework’lerin sunduğu en büyük avantajlardan biridir -- hızlı yükleme, SEO uyumluluğu ve ilk render performansı. Ancak bu gücün bir yan etkisi vardır: Hydration Mismatch.
Eğer Vue veya Nuxt projesinde terminalde şöyle bir uyarı gördüysen, yalnız değilsin:

Hydration completed but contains mismatches.

Bu hata gizemli görünür ama aslında çok mantıklıdır. Bu yazıda “hydration” sürecinin tam olarak ne olduğunu, neden “mismatch” oluştuğunu ve bunu nasıl önleyebileceğini anlatacağız.

Hydration Nedir?

Hydration, sunucuda oluşturulan HTML’in tarayıcıda yeniden Vue instance’ı ile eşleştirilmesi (rehydrate edilmesi) sürecidir.

SSR sayfası oluşturduğunda, kullanıcıya şu akış gerçekleşir:

  1. Server Render: Nuxt, sayfayı HTML olarak üretir ve tarayıcıya gönderir.
  2. Client Hydration: Tarayıcı bu HTML’i alır, Vue uygulamasını yeniden başlatır ve aynı sanal DOM yapısını oluşturur.
  3. Vue, HTML ve sanal DOM’u karşılaştırır → her şey aynıysa “hydration başarılı” der.
Ama fark varsa... işte o zaman “mismatch” olur. 💥

Özetle: SSR HTML ≠ Client render sonucu → Hydration Mismatch.

En Yaygın Sebepler

1. Zamana veya Tarayıcıya Bağımlı Kod

<p>{{ new Date().toLocaleTimeString() }}</p>
Server zamanı 12:00, client zamanı 12:01 olabilir. Tarayıcı bu farkı görünce DOM uyuşmazlığı oluşur. 😬

Çözüm: Tarayıcıya özel kodu onMounted() içine al.

<script setup>
import { ref, onMounted } from 'vue'
const time = ref('')
onMounted(() => time.value = new Date().toLocaleTimeString())
</script>

2. Random (Rastgele) Değerler

<p>{{ Math.random() }}</p>
Server ve client tarafında farklı değerler üretildiği için DOM asla eşleşmez.

Çözüm: Random değeri ya serverda üretip prop olarak geçir, ya da client-side oluştur.

3. Window veya Document Erişimi

Server tarafında window veya document yoktur.
const width = window.innerWidth // ❌ SSR patlar
Bu tür kodlar client tarafında çalıştırılmalıdır.

Çözüm:

if (process.client) {
const width = window.innerWidth
}

veya Composition API’de:
onMounted(() => {
console.log(window.innerWidth)
})

4. Koşullu Render Farkları

<div v-if="isClient">Tarayıcı Render</div>
Eğer isClient değeri server ve client arasında farklıysa, Vue farklı DOM oluşturur.

Çözüm: SSR ile client arasında aynı başlangıç state’ini koru. Gerekirse useState() kullan.

5. Async Veri Uyuşmazlığı

useAsyncData ile çekilen veriler SSR sırasında hazır olmalı. Eğer client tarafında yeniden fetch ediliyorsa DOM farkı oluşabilir.

Çözüm: Her useAsyncData çağrısına unique key ver.

await useAsyncData('posts', () => $fetch('/api/posts'))

Aksi halde Nuxt, cache yerine yeniden fetch eder ve içerik farkı yaratır.

Hydration Akışını Anlamak

Vue şu sırayla çalışır:
  1. SSR sırasında HTML oluşturulur.
  2. Tarayıcıda Vue app yeniden başlatılır.
  3. Vue, SSR HTML ile kendi render’ını karşılaştırır.
  4. Fark varsa, konsola hydration mismatch uyarısı yazar.
  5. Ardından Vue DOM’u yeniden oluşturur (görsel fark olmasa da performans kaybı oluşur).
Hydration hataları genellikle görünürde fark yaratmaz, ama perde arkasında Vue tüm DOM’u yeniden çizmek zorunda kalır -- performans kaybı ciddi olabilir.

Hataların Tespitinde Yardımcı Araçlar

  • console.warn’da detaylı mesajlar: Vue genellikle hangi node’un uyuşmadığını belirtir.
  • nuxt dev --debug modu: Hydration farklarını satır bazında gösterir.
  • Vue Devtools: SSR’den gelen HTML ile client-render arasındaki farkları inceleyebilirsin.

Çözüm Önerileri

SorunÇözüm
Zaman veya tarih tabanlı renderonMounted içinde hesapla
Random değerlerSSR yerine client’ta oluştur
window/document erişimiprocess.client veya onMounted kullan
API verisi tutarsızlığıuseAsyncData için benzersiz key
Şartlı render farkıSSR ile aynı state’ten başla

Bonus: isHydrating & onHydrated

Nuxt 3’te useNuxtApp() üzerinden hydration durumuna erişebilirsin.
const nuxtApp = useNuxtApp()
nuxtApp.hook('app:hydrated', () => {
console.log('Hydration tamamlandı!')
})
Bu event, Vue DOM eşleşmesini tamamladığında tetiklenir. Özellikle performans ölçümlerinde faydalıdır.

Hydration ile Suspense Uyumsuzluğu

<Suspense> bileşeni asenkron içerikler barındırdığında, SSR sırasında beklenmedik içerik farkları oluşabilir. Bu durumda fallback içeriği SSR’de, asıl içerik ise client tarafında gösterilir.

Çözüm: Suspense altındaki async bileşenlerin suspensible: false olarak ayarlanması veya v-if="hydrated" kontrolüyle yüklenmesi.

Sonuç

Hydration Mismatch hataları korkutucu görünse de, sebebi her zaman aynı: Server ve client farklı DOM üretmiştir. Çözüm ise tahmin edilebilir: tarayıcıya özel kodu yalnızca client tarafında çalıştır.

Kısaca:

  • window, document, Math.random(), Date() = dikkat!
  • SSR ve client state’leri aynı olmalı.
  • onMounted ve process.client kurtarıcıdır.

All tags
  • Nuxt 3
  • Vue.js