neptay
Tüm notlar

Mühendislik

1 saniyenin altında açılan bir stüdyo sitesi: Uyduğumuz performans bütçesi

Orta segment bir telefonda, 4G üzerinden ilk içeriği bir saniyenin altında gösteren stüdyo sitelerini nasıl yayına aldığımız — kendimize koyduğumuz bütçe, nelerden vazgeçtiğimiz ve Core Web Vitals için en çok önem taşıyan küçük mimari kararlar.

Bu yazıda

Belli bir tür stüdyo sitesi vardır: Her kaydırmada bir paralaks parıltısı, bir font değişimi, üç saniye boyunca arabelleğe alınan otomatik oynatmalı bir video ve üzerine gelince dişleri çıkan bir imleç. Bu siteler orta segment bir telefonda sekiz saniyede açılır ve fiber olmayan her bağlantıda bozukmuş gibi hissettirir. Biz böyle siteler yayına almıyoruz. Kendimize koyduğumuz brief sert ve basit: Pixel 6a’da, kısıtlanmış bir 4G bağlantısı üzerinden, bir saniyenin altında ilk içerik boyaması.

Bu brief’i karşılamak kahramanca optimizasyonlarla ilgili değil. Birbirini büyüten az sayıda mimari tercihle ve yapmadığımız çok daha fazla şeyle ilgili. Bu yazı çalıştığımız bütçeyi, arkasındaki kararları ve bütçenin tuttuğunu kanıtlayan saha ölçümlerini anlatıyor. Bir stüdyo sitesi, ajans sitesi ya da portfolyo benzeri herhangi bir sayfa yayına alıyorsanız, bu neredeyse olduğu gibi kopyalayabileceğiniz, gerçek dünyadan bir hedef.

Rakamlarla bütçe

Yayına aldığımız her stüdyo sitesi, Chrome DevTools’ta Fast 3G kısıtlamasıyla Pixel 6a üzerinde bu rakamları hedefler. 4G bağlantıda gerçek durum daha hızlıdır; Fast 3G’yi en kötü senaryo için alt sınır olarak kullanıyoruz. Lighthouse ile ve üretimde Vercel Analytics üzerinden gerçek kullanıcı metrikleriyle ölçüyoruz.

  • Largest Contentful Paint (LCP): laboratuvarda 1,2 sn altı, sahada p75’te 1,8 sn altı. Google’ın ‘iyi’ eşiği 2,5 sn.
  • First Contentful Paint (FCP): laboratuvarda 1,0 sn altı.
  • Cumulative Layout Shift (CLS): 0,05 altı. Google’ın ‘iyi’ eşiği 0,1; biz kendimize bunun yarısını hedef koyuyoruz.
  • Interaction to Next Paint (INP): 100 ms altı. Google’ın ‘iyi’ eşiği 200 ms.
  • İlk yüklemede toplam aktarım boyutu: HTML, CSS, fontlar, JS ve hero görseli dahil, sıkıştırılmış hâlde 150 KB altı.
  • İlk yüklemede JavaScript paketi: sıkıştırılmış hâlde 80 KB altı.

Bir sayfa bunlardan birini tutturamazsa bunu P1 hata olarak ele alıyoruz. ‘Sonra döneriz’ denecek bir şey olarak değil; site yayına çıkmadan düzelttiğimiz bir P1 olarak.

Neden bu kadar katı?

İki nedeni var. Birincisi, Core Web Vitals Google için bir sıralama faktörü. Eşitlik bozan bir ayrıntı değil — bir sıralama faktörü. Eşikleri karşılayan siteler sayfa deneyimi avantajından yararlanabilir; karşılamayanlar sınırda geride kalır. Dar anahtar kelime kümelerinde yarışan bir stüdyo için o sınır önemlidir.

İkincisi, algılanan performans bir marka sinyalidir. Anında açılan bir site yetkin görünür. Bir izleme pikseli çözülürken takılan bir site görünmez. Bütün iddiası ‘özenli, modern işler teslim ederiz’ olan bir stüdyo için site, bu iddiayı ilk temasta kanıtlamak zorundadır. Site, brief’in kendisidir.

1. karar: Her şeyi statik render etmek

En büyük tek performans kararı render stratejisidir. Next.js’i App Router ile kullanıyoruz ve stüdyo sitelerimizdeki her sayfa build sırasında statik olarak render ediliyor. Çalışma anında veritabanı sorgusu yok. HTML’i sunan edge fonksiyonunun ötesinde istek başına sunucu işi yok. Sitenin tamamı, bir CDN üzerindeki dosyalardan ibaret.

Bunun somut kazancı şu: Edge’deki TTFB, CDN’iniz ne diyorsa odur — dünyanın neresinde olursa olsun genellikle 30–80 ms. HTML’in ilk baytı, sunucuda render edilen bir alternatifte tarayıcının TLS el sıkışmasını henüz bitiremediği anda gelmiş olur. Sonrasındaki her şey — boyama, etkileşim, bir sonraki sayfa — o kadar erken başlar.

Bunun bedeli, içerik değişikliklerinin bir deploy gerektirmesi. Ayda bir güncellenen bir stüdyo sitesi için deploy olay bile sayılmaz — Vercel’de 90 saniye sürer. Saatte bir güncellenen bir yayın için hesap farklıdır ve doğru araç ISR’dir (Incremental Static Regeneration). Ama stüdyo siteleri saatte bir güncellenmez.

2. karar: Tek web fontu, iki ağırlık

Özel fontlar, ilk boyamadaki kontrol edilebilir en büyük maliyettir. Tek bir ağırlık için bir web fontu alt kümesi genellikle sıkıştırılmış hâlde 25–40 KB tutar. İki font ailesinden üç ağırlık yükleyen bir site, içeriğin ilk baytı ekrana gelmeden 150 KB font gönderir. Biz tek bir aileyi iki ağırlıkta, next/font’un kendi sunucumuzda barındırılan, hash’li teslimatıyla sunuyoruz — Google Fonts’a üçüncü taraf isteği yok, bir font CDN’i için DNS sorgusu yok.

Bunun sonucunu da kabul ediyoruz: İtalik yok, ince yok, ekstra kalın yok. Her tipografik ayrım; boyut, renk, harf aralığı ya da elimizdeki iki ağırlık arasındaki kontrastla yapılmak zorunda. Bu, kendi bedelini fazlasıyla ödeyen yaratıcı bir kısıt. Sekiz ağırlığa ihtiyaç duyan siteler, zayıf bir hiyerarşiyi tipografik çeşitliliğin altına saklama eğilimindedir; iki ağırlıkla çalışan siteler ise hiyerarşilerini yapıyla kazanmak zorundadır.

3. karar: Hero görseli bütçesi — ve nasıl harcanacağı

Bir stüdyo sitesinde LCP neredeyse her zaman hero’dur. Yani LCP bütçesi, hero bütçesidir. Hero’ya katı bir tavan koyuyoruz: masaüstü kırılma noktasında sıkıştırılmış 80 KB, mobilde 40 KB. Bundan büyüğü bütçeyi aşmak için bir bahane değil; görsel seçiminde ya da işlenişinde çözülmesi gereken yaratıcı bir problemdir.

Bizi bütçenin içinde tutan dört uygulama:

  1. Desteklenen yerlerde AVIF, yedek olarak WebP kullanın. AVIF, eşdeğer kalitede WebP’den genellikle %30–40 daha küçüktür.
  2. next/image bileşeniyle duyarlı boyutlar sunun. Telefonlar masaüstü hero’sunu asla indirmez; masaüstleri de ekran gerçekten retina değilse 2x retina sürümünü indirmez.
  3. Her görselde genişlik ve yüksekliği açıkça belirtin. Yerleşim kaymasını önleyen budur — tarayıcı, görsel gelmeden doğru piksel alanını ayırır.
  4. Hero görselini önceden yükleyin: head içinde <link rel="preload" as="image" href="…"/>. Tek satıra mal olur, LCP’de 200–400 ms kazandırır; çünkü tarayıcı görsel isteğini sayfanın geri kalanını ayrıştırmadan başlatır.

4. karar: Son çare olarak JavaScript

Her kilobayt JavaScript, sayfa etkileşimli hâle gelmeden önce ana iş parçacığında indirilmesi, ayrıştırılması ve çalıştırılması gereken bir kilobayttır. JS bütçesini kıt bir kaynak gibi ele alıyor ve iki kurala uyuyoruz:

  • Varsayılan olarak server component’ler. Bir bileşeni yalnızca gerçekten etkileşim gerektiriyorsa 'use client' olarak işaretleyin. React Server Components’teki varsayılan, içerik ağırlıklı siteler için doğru varsayılandır.
  • İlk yüklemede üçüncü taraf script yok. 80 KB’lık satıcı kodu gönderen analitik yok. Etiket yöneticisi yok. Analitik, sayfa etkileşimli hâle geldikten sonra tembel olarak yüklenir — yaklaşık 1 KB gönderen Vercel Analytics’i kullanıyoruz.

Stüdyo sitelerimize koyduğumuz bütçe, ilk yüklemede sıkıştırılmış 80 KB’ın altında JS — bunun çoğu React’in kendisi. Etkileşimli katman (gezinme menüsü, dil seçici, iletişim formu) yalnızca birkaç kilobayt tutuyor.

5. karar: Yerleşim kayması hatası üretmeyen bir CSS mimarisi

Cumulative Layout Shift, kazara en kolay kaybedilen Core Web Vital’dır; bilinçli olarak düzeltilmesi de en kolay olanıdır. CLS’yi sıfıra yakın tutan dört alışkanlık:

  1. Geç gelen her şey için yer ayırın. Görseller, fontlar, iframe’ler, reklamlar — hepsi açık boyutları olan bir en-boy oranı kapsayıcısına yerleşir.
  2. Fontun yüklenmesine bağlı CSS’ten kaçının. Değişim aralığında font-display: swap ile sistem fontu yedeklerini kullanın. Metrikleri web fontuyla örtüşen bir yedek seçin ki değişim görünmez olsun.
  3. Yükleme sonrasında ekranın üst kısmına içerik eklemeyin. İlk boyamadan sonra beliren banner’lar, çerez bildirimleri ve duyuru çubukları, gerçekleşmeyi bekleyen birer yerleşim kaymasıdır. Gerekliyse onları ilk HTML’de render edin ve animasyonla içeri alın.
  4. Yavaş ağlarda test edin. Fiberde görünmeyen CLS sorunları 3G’de görünür, çünkü geç gelen kaynak orada en geç gelir.

6. karar: Animasyon bütçesi

Harekette ölçülülük başlı başına bir performans optimizasyonudur. Ana iş parçacığında çalışan her animasyon bir INP gerilemesi riski taşır — tıklamada takılan tek bir 250 ms’lik animasyon, bütün siteyi ‘iyi’ INP diliminin dışına iter. Kurallarımız:

  • Yalnızca transform ve opacity’yi canlandırın. İkisi de GPU’da birleştirilir ve yerleşimi tetiklemez.
  • Görünme animasyonları JavaScript değil, CSS’tir. Bir className eklemek için IntersectionObserver kullanıyoruz; asıl animasyon bir transition.
  • Kaydırmaya bağlı animasyon yok. Kulağa zarif gelirler ama orta segment cihazlarda berbat bir kaydırma performansı getirirler.
  • Hero’da otomatik oynayan video yok. Video gerekiyorsa ekranın alt kısmında tembel yükleyin ya da bir etkileşimin arkasına koyun.

7. karar: Erişilebilirlik ve performansın kesiştiği yer

Erişilebilirlik kazanımlarının çoğu aynı zamanda performans kazanımıdır. Anlamsal HTML, div çorbasından daha küçüktür. Tarayıcının kendi odak halkaları, JS ile yazılmış özel odak yönetiminden daha hızlı render edilir. İçeriğe atlama bağlantıları, JavaScript gönderen klavye tuzağı düzeltmelerine gerek bırakmaz. Önce erişilebilirlik için tasarlayıp geliştiriyoruz; performans kendiliğinden geliyor.

Performans ile erişilebilirliğin gerildiği tek yer animasyon. Bazı kullanıcıların harekete ihtiyacı var; bazılarının ise midesi bulanıyor. prefers-reduced-motion tercihine saygı duyuyor ve her görünme animasyonunu, kaydırma değil solma olan bir yedekle yayına alıyoruz.

Üretimde ölçüm

Lighthouse’un laboratuvar metrikleri gerekli ama yeterli değil. Altın standart, gerçek kullanıcı izlemesidir (RUM) — gerçek cihazlardaki gerçek ziyaretçilerin deneyimi. Web Vitals için Vercel Analytics kullanıyoruz; çünkü platformla ücretsiz geliyor ve saha metriklerini yol, cihaz ve coğrafyaya göre ayrıştırarak raporluyor.

Her hafta baktıklarımız:

  • Yol başına p75 LCP. Herhangi bir yolun p75 değeri 1,8 sn’yi aşarsa inceliyoruz.
  • INP gerilemeleri. INP en sinsi metriktir — JavaScript büyüdükçe sessizce kötüleşebilir.
  • Yol başına CLS. CLS gerilemeleri genellikle geç yüklenen tek bir öğeye dayanır.
  • CI’daki bundle analizörüyle yol başına izlenen ilk yükleme JS’i. Bütçeyi aşan build’leri başarısız sayıyoruz.

Yapmadıklarımız

En az onlar kadar önemli: Sektör standardı olmalarına rağmen bilerek dışarıda bıraktığımız şeyler:

  • Küçük bir sitede SPA tarzı istemci tarafı yönlendirme yok. 4G bağlantıda tam belgeyi çekmek, paket farkını indirmekten daha hızlı. Gezinmeyi tarayıcıya bırakıyoruz.
  • Service worker yok. On sayfanın altındaki bir sitede bu karmaşıklığa değmez.
  • 30 KB’lık bir SDK ekleyen görsel barındırma servisi yok. Görselleri sitenin kendi alan adından, build sırasında optimize ederek sunuyoruz.
  • ‘Akıllı’ tembel yükleme kütüphanesi yok. Tarayıcının kendi loading="lazy" özniteliği bu işi 0 KB ile yapıyor.
  • Üzerine gelinen her bağlantıyı önceden yükleme yok. Next.js üretimde varsayılan olarak prefetch yapıyor; bunu açık bırakıp kararı framework’e bırakıyoruz.

Bir site planlıyor ve mevcut sitenizin performans denetimini ya da baştan bu bütçeyle kurulmuş bir site istiyorsanız: hello@neptay.com.

Neptay Media & Technology Services

Konuşalım

Benzer bir projeniz mi var?

Bu yazılarda anlattığımız yöntemleri müşteri projelerinde de kullanıyoruz — yazılım, yapay zekâ otomasyonu, içerik, sosyal medya ve canlı yayın prodüksiyonu. Aklınızdakini bize yazın; 24 saat içinde yanıt veriyoruz.