Web performansını lab ortamında ölçmek ile gerçek kullanıcı verisini toplamak arasındaki fark, kontrollü koşullarla rastgele koşullar arasındaki fark kadar büyüktür. Lighthouse puanı iyi, ama kullanıcılar yavaşlıktan söz ediyorsa sorun çoğunlukla gerçek cihaz çeşitliliğinde, ağ dalgalanmalarında veya sayfanın belirli etkileşim noktalarında gizlidir. PerformanceObserver, bu açığı kapatır: tarayıcının kendi ölçüm altyapısını kod çalışırken dinler ve ham veriyi size iletir.

API'nin temel mantığı abonelik modeliyle çalışır. Bir gözlemci tanımlarsınız, hangi tür girişleri izlemek istediğinizi belirtirsiniz ve her yeni giriş tarayıcı tarafından kayıt altına alındığında tanımladığınız callback tetiklenir. Eski yöntem olan performance.getEntries() anlık bir fotoğraf sunarken, PerformanceObserver canlı bir akış gibi davranır: sayfa yüklendikten sonra gerçekleşen olaylar da yakalanır.

Bu akış yaklaşımı, özellikle longtask, largest-contentful-paint ve layout-shift gibi geç oluşan metriklerde fark yaratır. Sayfa yükleme tamamlandıktan sonra da kaydolmaya devam eden bu metrikler, anlık sorgulama yönteminde büyük olasılıkla gözden kaçar. Veriyi toplamak yalnızca ilk adımdır; asıl değer, bu veriyi kendi analiz altyapınıza aktarmakta yatar.

PerformanceObserver Neden performance.getEntries()'den Farklıdır?

performance.getEntries()'i çağırdığınız anda sayfanın performans tamponundaki tüm girişler döner. Kullanımı kolaydır, ama iki önemli sınırı vardır. İlk sorun zamanlamadır: sorguyu çok erken yaparsanız henüz oluşmamış girdiler listeye girmez. İkincisi, largest-contentful-paint gibi bazı giriş türleri yalnızca bir kez değil, sayfa boyunca birden fazla kez güncellenebilir. Anlık sorgulama bu güncellemeleri yakalamanıza imkan tanımaz.

PerformanceObserver, gözlemciyi bir kez başlattıktan sonra tarayıcı her yeni giriş ürettiğinde callback'i tetikler. Bu model iki senaryoda özellikle kritik öneme sahiptir: uzun süre açık kalan tek sayfalık uygulamalar ve yavaş bağlantılarda geç tamamlanan işlemler.

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.entryType, entry.startTime, entry.duration);
  }
});

observer.observe({ type: 'longtask', buffered: true });

buffered: true seçeneği, gözlemciyi kaydetmeden önce gerçekleşmiş girişleri de listeye dahil eder. Bu seçenek olmadan, kodunuz bir bileşenin içinde geç yükleniyorsa erken dönem verileri kaybolabilir.

Long Task'ları Yakalamak: Ana İş Parçacığı Nerede Tıkandı?

50 milisaniyeyi aşan JavaScript işlemleri "long task" olarak sınıflandırılır. Bu eşiği önemli kılan şey, tarayıcının kullanıcı etkileşimlerine - tıklama, kaydırma, yazma - yanıt verememesidir; ana iş parçacığı meşgulken gelen tüm girişler kuyruğa girer. Kullanıcı için bu donma veya gecikme olarak hissedilir. Donma hissedilir.

const longTaskObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const data = {
      type: 'longtask',
      duration: entry.duration,
      startTime: entry.startTime,
      attribution: entry.attribution.map((a) => ({
        name: a.name,
        containerType: a.containerType,
        containerSrc: a.containerSrc,
      })),
    };
    sendToAnalytics(data);
  }
});

longTaskObserver.observe({ type: 'longtask', buffered: true });

attribution alanı, hangi betik veya iFrame'in bu engellemeye yol açtığına dair ipucu verir. Her tarayıcıda tam detay gelmeyebilir, ama temel konteyner bilgisi genellikle mevcuttur. Çoğu durumda long task sorununun kaynağı üçüncü taraf scriptlerdir: reklam, chat widget veya analitik kütüphaneleri. Bu veriyle hangi sayfanın hangi zaman diliminde yoğun task ürettiğini haritaya çıkarabilirsiniz; düzeltme önceliğini belirlemek için lab profiler çıktısından daha güvenilir bir kılavuzdur.

LCP'yi Koddan Okumak

largest-contentful-paint giriş türü, görünür alanda en büyük içerik öğesinin ne zaman render edildiğini milisaniye cinsinden sunar. Önemli bir nüans var: tarayıcı LCP değerini sayfa boyunca birden fazla kez güncelleyebilir. İlk render edilen büyük öğe aday olur, ama daha büyük bir öğe sonradan yüklenirse aday değişir. Yalnızca son değeri alıp göndermek gerekir.

let lcpValue = 0;

const lcpObserver = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  lcpValue = lastEntry.startTime;
});

lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    sendToAnalytics({ type: 'lcp', value: lcpValue });
    lcpObserver.disconnect();
  }
});

visibilitychange olayına bağlanmak, veriyi sayfa kapanmadan hemen önce göndermenin güvenilir yoludur. beforeunload yerine bu olayı tercih etmek gerekir; mobil tarayıcıların büyük bölümünde beforeunload tetiklenmeyebilir. LCP değeri 2500 milisaniyenin altındaysa iyi, 4000 milisaniyenin üstündeyse zayıf kategorisine girer. Laboratuvar ölçümünüz bu sınırın altında olsa bile, gerçek kullanıcı verinizde belirli cihaz grupları veya coğrafi bölgeler için yüksek değerler görebilirsiniz.

CLS Skorunu Anlık İzlemek

Kümülatif düzen kayması (Cumulative Layout Shift - CLS), beklenmedik görsel kaymalar için bir puandır. layout-shift giriş türü her kayma olayında tetiklenir; gözlemci yaklaşımı burada özellikle önemlidir, çünkü kaymalar sayfa yükleme tamamlandıktan çok sonra gerçekleşebilir. Kaydırma sırasında geç yüklenen reklam alanları veya geç gelen web font'ları bunun klasik kaynaklarıdır.

let clsScore = 0;
let clsSessionValue = 0;
let clsSessionEntries = [];

const clsObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      const firstEntry = clsSessionEntries[0];
      const lastEntry = clsSessionEntries[clsSessionEntries.length - 1];

      if (
        clsSessionEntries.length === 0 ||
        entry.startTime - lastEntry.startTime < 1000 ||
        entry.startTime - firstEntry.startTime < 5000
      ) {
        clsSessionValue += entry.value;
        clsSessionEntries.push(entry);
      } else {
        clsScore = Math.max(clsScore, clsSessionValue);
        clsSessionValue = entry.value;
        clsSessionEntries = [entry];
      }

      clsScore = Math.max(clsScore, clsSessionValue);
    }
  }
});

clsObserver.observe({ type: 'layout-shift', buffered: true });

hadRecentInput kontrolü atlanamaz. Kullanıcı bir şeye tıkladıktan veya yazdıktan sonraki 500 milisaniye içinde gerçekleşen kaymalar CLS hesabına dahil edilmez; bunlar kasıtlı etkileşimdir. Bu kontrolü atlamak skoru yanıltır. Arka arkaya gelen kaymalar arasında 1 saniyeden az boşluk varsa ve toplam pencere 5 saniyeyi aşmıyorsa aynı oturuma dahil edilir; bu pencerenin maksimum skoru CLS değerinizdir.

Özel Zamanlama Metrikleri: performance.mark() ve performance.measure()

Tarayıcının yerleşik metrikleri dışında, uygulamanıza özgü noktaları ölçmek için performance.mark() ve performance.measure() kullanabilirsiniz. Bu ikisi PerformanceObserver ile birlikte çalışır; mark ve measure giriş türlerini dinleyerek kendi kilometre taşlarınızı takip edebilirsiniz.

// Sepete ekle butonuna tıklandığında
performance.mark('add-to-cart-start');

// API yanıtı döndüğünde ve UI güncellendiğinde
performance.mark('add-to-cart-end');
performance.measure('add-to-cart-duration', 'add-to-cart-start', 'add-to-cart-end');

const measureObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.name === 'add-to-cart-duration') {
      sendToAnalytics({
        type: 'custom-measure',
        name: entry.name,
        duration: entry.duration,
      });
    }
  }
});

measureObserver.observe({ type: 'measure', buffered: true });

Yerleşik metrikler ne zaman yavaşladığınızı söyler; özel ölçümler hangi özelliğin yavaşladığını söyler. E-ticaret sitelerinde ödeme adımlarını, SaaS uygulamalarında kritik iş akışlarını veya veri yoğun panellerde tablo render sürelerini bu yöntemle izleyebilirsiniz. İzlenmeye değer noktaları aşırı artırmamak gerekir: callback gürültüsünü azaltmak için yalnızca kullanıcının beklediği ve hissedebildiği işlemleri - görsel geri bildirim gerektiren adımları - işaretleyin.

Veriyi Kendi Altyapınıza Göndermek

Veriyi toplamak yeterli değildir; analiz için bir hedefe iletmek gerekir. İki yöntem öne çıkar: fetch ile POST isteği ve navigator.sendBeacon(). Sayfa kapanmadan önce gönderim için sendBeacon tercih edilmelidir; tarayıcı sekme kapandıktan sonra bu isteği tamamlar, fetch ise iptal edilebilir.

function sendToAnalytics(data) {
  const payload = JSON.stringify({
    ...data,
    url: location.href,
    timestamp: Date.now(),
    connectionType: navigator.connection?.effectiveType,
  });

  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/metrics', payload);
  } else {
    fetch('/api/metrics', {
      method: 'POST',
      body: payload,
      headers: { 'Content-Type': 'application/json' },
      keepalive: true,
    });
  }
}

keepalive: true seçeneği, fetch'in sayfa kapatılsa bile tamamlanmasını sağlar. navigator.connection?.effectiveType bağlantı türünü (4g, 3g, slow-2g) veriye ekler; yüksek LCP değerlerinin yavaş bağlantıdan mı yoksa koddan mı kaynaklandığını anlamak için bu alan önemlidir. Toplu gönderim de göz önünde bulundurulmalıdır: kullanıcı başına kısa sürede çok sayıda layout-shift tetiklenebilir, her birini ayrı istek olarak göndermek sunucu yükünü artırır. Girişleri yerel bir dizide biriktirip belirli aralıklarla veya sayfa kapanmadan tek seferde göndermek daha sağlıklıdır.

Ne Zaman Gerekli, Ne Zaman Fazla?

PerformanceObserver her projeye uygun değildir. Kısa ömürlü landing page'lerde veya düşük trafikli içerik sitelerinde yatırım getirisi sınırlı kalır; lab verisi genellikle yeterlidir. Gözlemci altyapısı kurmanın anlamlı olduğu durumlar: yüksek trafikli uygulamalar (veri hacmi istatistiksel anlam kazandırır), etkileşim odaklı ürünler (sepet, form, dashboard) ve çeşitli cihaz profillerine sahip geniş kullanıcı kitlesi.

Bir ters etki de mevcuttur. Gözlemci callback'leri ana iş parçacığında çalışır. Callback içinde ağır hesaplamalar yaparsanız, performansı izlemek amacıyla kurduğunuz kod bizzat performansı düşürmeye başlar. Yalnızca biriktir, daha sonra gönder kuralı bu riski ortadan kaldırır.

Tek ihtiyaç LCP ve CLS izlemekse, bu ölçümleri hazır sunan kütüphaneler mevcuttur. Bunlar PerformanceObserver'ı arka planda kullanır, ama oturum penceresi algoritmasını, hadRecentInput kontrolünü ve visibilitychange yönetimini sizin yerinize halleder. Ham API ile başlamak, bu kütüphanelerin neden belirli kararlar aldığını anlamak açısından değerlidir; üretim kodunda ise hazır uygulamayı kullanmak daha az hata kaynağı bırakır.

Gerçek kullanıcı verisini toplamak, soyut performans puanlarını somut sorunlara dönüştürür. Hangi cihaz grubunun long task yaşadığını, hangi bağlantı hızında LCP bozulduğunu ve hangi sayfa bölümünün layout kaymasına yol açtığını doğrudan görebilirsiniz. Bu netlik, optimizasyon sırasını keyfi değil, veri odaklı kılar.

Önce long task gözlemcisi kurulur, birkaç gün veri toplanır, ardından LCP ve CLS eklenir. Tüm metrikleri aynı anda devreye almak hem hata ayıklamayı zorlaştırır hem de callback karmaşıklığını gereksiz yere artırır. Küçük başlayan bir izleme altyapısı daha güvenilir veri üretir ve sorun çıktığında nereye bakılacağını daha net gösterir.