Nuxt.js projelerinde SSR ve SSG karma kullanımında ortaya çıkan hydration maliyetini, payload şişkinliğini ve bundle sorunlarını Nuxt DevTools, nuxt analyze ve Lighthouse kombinasyonuyla nasıl tanılayacağınızı adım adım açıklıyoruz.

Nuxt.js Projelerinde Performans Sorunları Nasıl Tanılanır?

Nuxt.js projesinde bir şeyler yavaş gidiyorsa, ilk adım doğru yere bakmaktır. Çoğu zaman geliştirici ağ sekmesine bakıp bağlantı sorununa yorar, ama asıl neden bundle büyüklüğü, hydration maliyeti veya sunucu tarafındaki veri aktarımı olabilir. Nuxt'ın SSR ve SSG modlarını bir arada kullandığınız projelerde bu sorunlar daha sık ve daha karmaşık biçimde ortaya çıkar, çünkü her sayfanın farklı bir render stratejisi izlemesi tanılamayı doğrudan güçleştirir.

SSR (Server-Side Rendering) ile SSG (Static Site Generation) arasında geçiş yapmak esneklik sağlar; ancak her sayfanın hangi modda çalıştığını ve bu modun performans üzerindeki etkisini takip etmek güçleşir. Hydration süreci - sunucudan gelen HTML ile istemcideki JavaScript'in eşleşmesi - yanlış yapılandırıldığında tarayıcıda ek iş yükü oluşturur ve LCP ile ilk girdi gecikmesi değerlerini ciddi biçimde etkiler.

Tanılama araçları üçe ayrılır: Nuxt DevTools gerçek zamanlı çalışma verisini gösterir, nuxt analyze derleme çıktısının anatomisini ortaya koyar, Lighthouse ise son kullanıcının deneyimini sayısal olarak yansıtır. Üçünü ayrı ayrı kullanmak yerine belirli bir sırayla birleştirmek, bulguların birbirini doğrulamasını sağlar ve yanlış yorumları azaltır.

Nuxt DevTools ile ilk tanılamayı başlatmak

Nuxt DevTools, 3.0 sürümünden itibaren varsayılan olarak geliştiricilere açık bir panel sunar. Sayfayı açtığınızda alt köşedeki N ikonuna tıklamanız yeterlidir; DevTools tarayıcı sekmesinde doğrudan açılır ve üretim dışı ortamlarda ek kurulum gerektirmez.

Panel içindeki Pages sekmesi her route için SSR, SSG ve SPA modlarından hangisinin aktif olduğunu gösterir. Hangi sayfanın sunucu tarafında render edildiğini, hangisinin statik olarak oluşturulduğunu buradan okuyabilirsiniz. Beklenmedik bir sayfanın SPA modunda kaldığını görürseniz, nuxt.config.ts içindeki routeRules tanımını kontrol edin; çoğu zaman bir glob kalıbının yanlış sırada yazıldığı görülür ve alt route'lar üst kuraldan etkilenir.

Modules sekmesi yüklü tüm modülleri ve bunların başlangıç süresine katkısını listeler. Bir modülün aşırı uzun başladığını fark ederseniz, o modülün yaptığı işi inceleyin. Image optimization veya i18n modülleri ilk yüklemede beklenenden fazla zaman alabilir; bu beklenen bir davranış olduğu sürece sorun değildir, ama geliştirici modunda her hot-reload sonrası yavaşlama yaşanıyorsa modülün konfigürasyonunu gözden geçirmek vakit kazandırır.

State bölümü en kullanışlı kısımdır. Burada useState ile useAsyncData çağrılarının anlık durumu görünür ve payload olarak ne kadar veri taşındığı da bu ekranda izlenebilir. Fazla büyük bir state nesnesi hydration sırasında istemciye aktarılan payload'ı şişirir; ancak tek başına ham boyuta bakmak yanıltıcı olabilir, çünkü gzip sonrası aktarım boyutu ham boyutun çok altında kalabilir. Sıkıştırılmış boyutu görmek için Network sekmesindeki "transfer size" sütununu kullanın.

nuxt analyze ile bundle şişkinliğini saptamak

Bundle analizi, görünmez bir sorunu görünür hale getirmenin en doğrudan yoludur. npx nuxi analyze komutu projeyi production modunda derler ve .nuxt/analyze/ klasörüne bir HTML rapor bırakır. Bu rapor, her chunk'ın ne kadar yer kapladığını ve hangi bağımlılığı içerdiğini blok haritası biçiminde gösterir; renk ve boyut, hangi kütüphanenin ne kadar yer aldığını sezgisel biçimde aktarır.

npx nuxi analyze

Raporu açtığınızda ilk bakacağınız yer vendor chunk'tır. Üçüncü parti kütüphaneler genellikle burada toplanır ve beklenmedik bağımlılıklar bu katmanda görünür hale gelir. Moment.js locale dosyaları veya lodash'ın tamamı ilk bakışta göze çarpmayabilir; ama harita üzerinde orantısız bir alan kapladıklarında sorun kaynağını somut biçimde saptamış olursunuz ve hangi paketi tree-shaking'e açmanız gerektiğini görürsünüz.

SSR modundaki sayfalarda dikkat edilmesi gereken ikinci nokta: sunucu bundle'ı ile istemci bundle'ının boyut farkıdır. Nuxt, SSR için server ve client olmak üzere iki ayrı bundle üretir. Sunucu bundle'ı normalde daha küçük olmalıdır; eğer ikisi de büyükse ve boyutları birbirine yakınsa, istemciye özgü olması gereken kodun sunucuya sızdığı ya da tam tersi bir durum var demektir. server/ klasörü dışında kalan istemci-özgü importların koşullu olup olmadığını kontrol edin.

Dinamik import, chunk bölmenin temel aracıdır. Büyük bir bileşen ya da kütüphane yalnızca belirli bir sayfada kullanılıyorsa, defineAsyncComponent veya import() ile lazy yüklemek ana bundle'ı küçük tutar. Analiz raporunda tek bir chunk içinde beklenmedik biçimde büyük bir bağımlılık görünüyorsa, o bağımlılığı lazy import'a taşımak ilk denenmesi gereken adımdır.

SSR ve SSG karma kullanımında hydration maliyetini ölçmek

Hydration maliyeti ölçülmeden azaltılamaz. Chrome DevTools'un Performance sekmesinde sayfayı kayda alın ve zaman çizelgesinde Hydration etiketli uzun görevleri arayın. Hydration işlemi DOM düğümlerini teker teker gezdiği için, sunucudan gelen HTML ne kadar büyükse tarayıcının eşleştirmesi o kadar uzun sürer.

Ters sırayla başlamak daha verimlidir: önce hangi sayfanın TBT (Total Blocking Time) değerinin yüksek olduğunu Lighthouse'dan öğrenin, ardından o sayfayı Performance kaydıyla inceleyin. TBT yüksekliği çoğunlukla ya büyük bir JavaScript chunk'ının parse maliyetiyle, ya da uzun süren hydration göreviyle ilişkilidir; ikisi aynı anda yaşanabilir ama birbirinden bağımsız nedenlerle gerçekleşir.

useAsyncData çağrılarında lazy: true seçeneği hydration sırasındaki bloklamayı erteleyebilir. Bu seçenek, verinin sayfa render edilmeden önce beklenip beklenmeyeceğini kontrol eder. İlk ekranda görünmeyecek içerik için lazy: true doğru tercih olabilir; ama kullanıcının hemen göreceği bir bileşendeki veri için bu seçenek beklenmedik boş alan bırakır ve layout kaymasına (CLS) yol açar.

Karma modda - bazı sayfalar SSR, bazıları SSG - hydration yalnızca SSR sayfaları için gerçekleşir. SSG sayfaları zaten statik HTML sunar ve istemcide JavaScript yalnızca etkileşim için devreye girer. Bu nedenle SSG sayfasında yüksek TBT görünüyorsa, suçlu hydration değil büyük olasılıkla parse edilen JavaScript miktarıdır; yol ikiye ayrılır ve her birinin tedavisi farklıdır.

Payload şişkinliğinin kaynağını bulmak

Nuxt, SSR sırasında sunucuda çekilen veriyi HTML içine gömer ve istemciye __NUXT_DATA__ adlı bir JSON nesnesi olarak aktarır. Sayfa kaynağını görüntüleyip bu nesneyi aradığınızda ne kadar veri taşındığını ham olarak görebilirsiniz. Fazla büyük bir payload, hem ağ aktarım süresini uzatır hem de tarayıcının JSON'ı parse etmesi için ek zaman harcar.

Şişkin payload'ın en sık kaynağı, API yanıtının tamamının state'e yazılmasıdır. Bir liste sayfasında 50 ürün geliyor ve her ürün için 30 alan varsa, bunların büyük bölümü sayfada hiç gösterilmemiş olabilir. Sunucu tarafında veriyi aldıktan sonra yalnızca kullanılacak alanları seçerek state'e yazmak - sorguda projeksiyon kullanmak ya da bir dönüşüm katmanı eklemek - payload'ı önemli ölçüde küçültür.

Küçük. Ama etkili.

useFetch ve useAsyncData içindeki transform seçeneği tam bu iş için tasarlanmıştır. Sunucudan gelen yanıtı istemciye geçmeden önce dönüştürür ve yalnızca seçilen alanlar payload'a eklenir. Büyük veri yapılarında bu yaklaşım doğrudan boyut düşürümü sağlar ve ek bir cache veya CDN değişikliği gerektirmez.

const { data } = await useFetch('/api/products', {
  transform: (products) =>
    products.map(({ id, name, price, slug }) => ({ id, name, price, slug }))
})

transform seçeneğinin bir yan etkisi vardır: dönüştürülen veri sunucuda işlenip payload'a yazılır, istemci tarafında yeniden çekilmez. Bu nedenle dönüşüm sırasında bir hata olursa suskunca boş döner; dönüşüm fonksiyonunu basit tutmak ve hata yönetimini açık yapmak bu nedenle önemlidir.

Lighthouse ile son kullanıcı perspektifine geçmek

DevTools ve bundle analizi geliştirici perspektifini verir; Lighthouse ise tarayıcının sayfayı yüklerken yaşadığı deneyimi simüle eder. Chrome DevTools içinden çalıştırılan Lighthouse, yavaşlatılmış CPU ve ağ koşullarında ölçüm alır; bu koşullar, düşük güçlü bir cihazda yaşananı yaklaşık olarak temsil eder.

Lighthouse raporundaki "Opportunities" ve "Diagnostics" bölümlerine bakın. Nuxt projelerinde sık görülen bulgular şunlardır: render-blocking kaynak olarak işaretlenen büyük JavaScript dosyaları, LCP elementini geciktiren geç yüklenen görseller ve aşırı DOM boyutu uyarısı. Aşırı DOM boyutu, özellikle SSR ile üretilen sayfalarda liste bileşenlerinde ortaya çıkar; sunucu tüm düğümleri tek seferde üretir ve istemcide hydrate ederken tarayıcı bunları topluca işler.

TTFB (Time to First Byte) değeri Lighthouse raporunda ayrı gösterilir ve SSR sayfaları için en kritik metrik budur. 800 ms üzerindeki TTFB sunucu tarafına işaret eder: ya veritabanı veya API yanıtı uzuyor, ya da SSR sırasında yapılan hesaplama beklenenden ağır. Bu durumda Lighthouse size sorunun varlığını söyler ama kaynağını söylemez; kaynağı bulmak için sunucu loglarına ve API yanıt sürelerine bakmanız gerekir.

Lighthouse'u CI süreçlerine entegre etmek anlık ölçümü sürekli izlemeye dönüştürür. Her deploy sonrası otomatik çalışan bir Lighthouse kontrolü, özellikle birden fazla geliştiricinin çalıştığı projelerde performans regresyonunu erken yakalar. Bunun için Lighthouse CI paketi @lhci/cli kullanılabilir; eşik değerleri proje deposuna eklenerek her PR'da kontrol edilebilir hale gelir.

Render stratejisini sayfaya göre doğrulamak

Karma modun asıl sorunu şudur: hangi sayfanın hangi modda çalıştığını değiştirmek kolay, takip etmek zordur. routeRules ile tanımlanan kurallar büyüdükçe bir sayfanın art arda gelen kuralların hangisinden etkilendiği belirsizleşir. Nuxt DevTools'un Pages sekmesi bunu doğrulamak için en hızlı yoldur; ama belirli bir URL'de hangi kuralın devreye girdiğini komut satırından da görebilirsiniz.

Statik ön üretim (prerender) yapılan sayfalarda içerik değiştiğinde yeniden build gerekir. Bu nedenle sık değişen içeriği SSG'ye almak uygun değildir; haber akışı, canlı fiyat veya kişiselleştirilmiş içerik SSR ya da ISR (Incremental Static Regeneration) için daha iyi adaydır. Nuxt 3'te ISR benzeri davranışı routeRules içindeki swr (stale-while-revalidate) seçeneğiyle kurmak mümkündür; bu seçenek içeriği önbellekte tutar ve arka planda yeniler, böylece TTFB düşük kalırken içerik güncelliğini korur.

Render stratejisi seçiminin performans dışında başka maliyetleri de vardır. SSR her istek için sunucu kaynağı tüketir; yüksek trafik altında bu maliyet ölçeklenebilirliği etkiler. SSG build süresini uzatır ve büyük projelerde bu süre ciddi bir engele dönüşebilir. Hybrid yaklaşım her ikisini de dengeler ama konfigürasyon karmaşıklığını artırır; bu karmaşıklığın bir kısmı da doğrudan tanılama güçlüğüne döner.

Üç aracı sırayla kullanmak

Tanılamayı yapılandırılmış bir akışa dönüştürmek tekrarlanabilirlik sağlar. Önce Lighthouse ile hangi sayfanın sorunlu olduğunu ve hangi metriğin düştüğünü belirleyin. LCP geride kalıyorsa görseller ve render-blocking kaynaklar önceliklidir; TBT yüksekse JavaScript parse ve hydration maliyeti incelenmelidir; yüksek TTFB sunucu tarafına işaret eder.

İkinci adımda Nuxt DevTools'u açın ve sorunlu sayfanın render modunu, aktif state boyutunu ve modül yükleme sürelerini gözden geçirin. Burada anomali görünüyorsa - beklenmedik SPA modu, aşırı büyük state nesnesi, uzun modül başlatma - doğrudan müdahale edilebilecek bir bulgu elde etmiş olursunuz.

Üçüncü adım nuxt analyze ile bundle'ı incelemektir. Bu adım özellikle TBT ve LCP sorunlarında gereklidir; büyük vendor chunk'ları veya yanlış bölünmüş dinamik importlar burada görünür hale gelir. Analiz raporu değişmez - her seferinde yeniden build gerekir - bu yüzden her küçük değişiklikten sonra değil, belirli bir hipotezi test ederken çalıştırmak daha verimlidir.

Üç araç birbirini tamamlar ama birbirinin yerine geçemez. Lighthouse sorunun var olduğunu söyler; DevTools nerede aranacağını gösterir; nuxt analyze derleme çıktısının gerçek anatomisini ortaya koyar. Birini atlayıp doğrudan kod değiştirmek çoğunlukla yanlış yere yapılan bir iyileştirmeyle sonuçlanır ve gerçek sorun olduğu yerde kalmaya devam eder.

Nuxt.js performans tanılaması, doğru araçla doğru katmana bakmaktır. Hydration maliyeti istemci tarafında gizlenir, payload şişkinliği ağ katmanında görünür, bundle sorunu ise derleme çıktısında saklanır; bunları tek bir araçla yakalamak mümkün değildir. Hangi metriğin kötü gittiğini bilmek başlangıç noktasını belirler, ama asıl iş o metriği hangi katmandaki hangi kararın etkilediğini izlemektir.

SSR ve SSG'yi bir arada kullanan projeler için düzenli bir tanılama rutini oluşturmak, tek seferlik iyileştirmeden daha fazla değer üretir. Render stratejisi kararları zamanla birikerek karmaşıklaşır; bir sayfanın neden yavaş olduğunu anlamak için üç ay önceki routeRules değişikliğine gitmek zorunda kalmak, tanılamanın gerçek maliyetidir. Araçları erken ve düzenli kullanmak bu maliyeti düşürür.