SmashingMag Performansını Nasıl İyileştirdik?
Yayınlanan: 2022-03-10Bu makale, tasarımcılar, geliştiriciler ve müşterileriniz için eksiksiz bir web barındırma çözümleri yelpazesi sunan Media Temple'daki sevgili arkadaşlarımız tarafından desteklenmektedir. Teşekkürler sevgili arkadaşlar!
Her web performans hikayesi birbirine benzer, değil mi? Her zaman uzun zamandır beklenen web sitesi revizyonu ile başlar. Tamamen cilalanmış ve dikkatlice optimize edilmiş bir projenin başlatıldığı, Lighthouse ve WebPageTest'te yüksek sıralamalarda ve performans puanlarının üzerinde yükselen bir gün. Havada hüküm süren bir kutlama ve yürekten bir başarı duygusu var - retweetlere, yorumlara, haber bültenlerine ve Slack ileti dizilerine güzel bir şekilde yansıdı.
Ancak zaman geçtikçe, heyecan yavaş yavaş kaybolur ve acil düzenlemeler, çok ihtiyaç duyulan özellikler ve yeni iş gereksinimleri içeri girer. Ve aniden, siz farkına varmadan, kod tabanı biraz fazla kilolu ve parçalı hale gelir, üçüncü taraf komut dosyalarının biraz daha erken yüklenmesi gerekir ve parlak yeni dinamik içerik, dördüncü taraf komut dosyalarının ve davetsiz misafirlerinin arka kapılarından DOM'a girer.
Smashing'de de oradaydık. Pek çok insan bilmiyor ama biz, çoğu yarı zamanlı çalışan ve çoğu belirli bir günde genellikle birçok farklı şapka takan yaklaşık 12 kişiden oluşan çok küçük bir ekibiz. Performans neredeyse on yıldır hedefimiz olsa da, hiçbir zaman gerçekten özel bir performans ekibimiz olmadı.
2017 sonundaki en son yeniden tasarımın ardından, şeylerin JavaScript tarafında (yarı zamanlı) Ilya Pukhalski, CSS tarafında (haftada birkaç saat) Michael Riethmueller ve gerçekten kritik CSS ile akıl oyunları oynayan sizinkiydi. ve çok fazla şeyi dengelemeye çalışmak.

Olduğu gibi, günlük rutinin yoğunluğunda performansın izini kaybettik. Bir şeyler tasarlıyor ve inşa ediyor, yeni ürünler kuruyor, bileşenleri yeniden düzenliyor ve makaleler yayınlıyorduk. Böylece 2020'nin sonlarında işler biraz kontrolden çıktı ve sarımsı-kırmızı Deniz Feneri puanları yavaş yavaş tahtada ortaya çıktı. Bunu düzeltmemiz gerekiyordu.
Biz oradaydık
Bazılarınız, Markdown dosyaları olarak depolanan tüm makaleler ve sayfalar, CSS'de derlenen Sass dosyaları, Webpack ile parçalara ayrılan JavaScript ve daha sonra doğrudan bir Edge CDN'den sunduğumuz statik sayfalar oluşturan Hugo ile JAMStack üzerinde çalıştığımızı biliyor olabilir. 2017'de tüm siteyi Preact ile oluşturduk, ancak daha sonra 2019'da React'e geçtik ve onu arama, yorumlar, kimlik doğrulama ve ödeme için birkaç API ile birlikte kullandık.
Sitenin tamamı aşamalı geliştirme düşünülerek oluşturulmuştur, yani siz sevgili okuyucu, uygulamayı başlatmaya gerek kalmadan her Smashing makalesini bütünüyle okuyabileceğiniz anlamına gelir. Bu da çok şaşırtıcı değil - sonuçta, yayınlanan bir makale yıllar içinde pek değişmezken, Üyelik doğrulaması ve ödeme gibi dinamik parçaların çalışması için uygulamanın olması gerekir.
Yaklaşık 2500 makaleyi canlı olarak dağıtmak için tüm yapı şu anda yaklaşık 6 dakika sürüyor. Oluşturma süreci, kritik CSS enjeksiyonları, Webpack'in kod bölme, dinamik reklam ekleme ve özellik panelleri, RSS (yeniden) oluşturma ve uçta nihai A/B testi ile zaman içinde başlı başına bir canavar haline geldi.
2020'nin başlarında, CSS mizanpaj bileşenlerinin büyük ölçüde yeniden düzenlenmesine başladık. Hiçbir zaman JS'de CSS veya stilli bileşenler kullanmadık, bunun yerine CSS'de derlenecek olan eski, bileşen tabanlı Sass modülleri sistemi kullandık. 2017'de, tüm düzen Flexbox ile oluşturuldu ve 2019'un ortalarında CSS Izgarası ve CSS Özel Özellikleri ile yeniden oluşturuldu. Ancak, yeni reklam spotları ve yeni ürün panelleri nedeniyle bazı sayfalar özel işlem gerektiriyordu. Yani düzen çalışırken, pek iyi çalışmıyordu ve bakımı oldukça zordu.
Ek olarak, ana navigasyona sahip başlık, dinamik olarak görüntülemek istediğimiz daha fazla öğeye uyum sağlamak için değişmek zorundaydı. Ayrıca, site genelinde kullanılan bazı sık kullanılan bileşenleri yeniden düzenlemek istedik ve orada kullanılan CSS'nin de biraz revizyona ihtiyacı vardı - en dikkate değer suçlu haber bülteni kutusuydu. Bazı bileşenleri yardımcı program öncelikli CSS ile yeniden düzenleyerek başladık, ancak bunun sitenin tamamında tutarlı bir şekilde kullanıldığı noktasına hiçbir zaman ulaşamadık.
Daha büyük sorun, ana iş parçacığını yüzlerce milisaniye boyunca engelleyen büyük JavaScript paketiydi. Yalnızca makaleler yayınlayan bir dergide büyük bir JavaScript paketi uygunsuz görünebilir, ancak aslında, sahne arkasında çok sayıda komut dosyası oluşturma vardır.
Kimliği doğrulanmış ve kimliği doğrulanmamış müşteriler için çeşitli bileşen durumlarına sahibiz. Oturum açtığınızda, tüm ürünleri son fiyatta göstermek istiyoruz ve sepete bir kitap ekledikçe, hangi sayfada olursanız olun, bir düğmeye dokunarak bir sepete erişilebilir olmasını istiyoruz. Reklamın, düzeni bozacak değişikliklere neden olmadan hızla gelmesi gerekir ve aynısı, ürünlerimizi öne çıkaran yerel ürün panelleri için de geçerlidir. Ayrıca, tüm statik varlıkları önbelleğe alan ve bir okuyucunun daha önce ziyaret ettiği makalelerin önbelleğe alınmış sürümleriyle birlikte bunları tekrar görünümler için sunan bir hizmet çalışanı.
Dolayısıyla tüm bu senaryo yazımının bir noktada gerçekleşmesi gerekiyordu ve senaryo oldukça geç gelmesine rağmen okuma deneyimini tüketiyordu. Açıkçası, performansı yakından takip etmeden site ve yeni bileşenler üzerinde özenle çalışıyorduk (ve 2020 için aklımızda tutmamız gereken birkaç şey daha vardı). Dönüm noktası beklenmedik bir şekilde geldi. Harry Roberts (mükemmel) Web Performansı Masterclass'ını bizimle çevrimiçi bir atölye çalışması olarak yürüttü ve tüm atölye boyunca, sahip olduğumuz sorunları vurgulayarak ve bu sorunlara faydalı araçlar ve yönergelerin yanı sıra çözümler önererek Smashing'i örnek olarak kullandı.
Çalıştay boyunca özenle notlar alıyor ve kod tabanını tekrar ziyaret ediyordum. Çalıştay sırasında, Lighthouse puanlarımız ana sayfada 60-68 ve makale sayfalarında 40-60 civarındaydı ve açıkçası mobilde daha kötüydü. Workshop bitince işe koyulduk.
Darboğazların Belirlenmesi
Ne kadar iyi performans gösterdiğimizi anlamak için genellikle belirli puanlara güvenme eğilimindeyiz, ancak çoğu zaman tek puanlar tam bir resim sağlamaz. David East'in makalesinde etkili bir şekilde belirttiği gibi, web performansı tek bir değer değildir; bir dağıtımdır. Bir web deneyimi, yoğun ve kapsamlı bir şekilde optimize edilmiş çok yönlü bir performans olsa bile, yalnızca hızlı olamaz. Bazı ziyaretçiler için hızlı olabilir, ancak nihayetinde bazıları için daha yavaş (veya yavaş) olacaktır.
Bunun nedenleri çoktur, ancak en önemlisi, dünyadaki ağ koşulları ve cihaz donanımındaki büyük farktır. Çoğu zaman bu şeyleri gerçekten etkileyemiyoruz, bu yüzden deneyimimizin bunun yerine onları barındırdığından emin olmalıyız.
Özünde, işimiz hızlı deneyimlerin oranını artırmak ve ağır deneyimlerin oranını azaltmaktır. Ancak bunun için dağılımın gerçekte ne olduğuna dair uygun bir resim elde etmemiz gerekiyor. Şimdi, analiz araçları ve performans izleme araçları, gerektiğinde bu verileri sağlayacak, ancak biz özellikle CrUX, Chrome Kullanıcı Deneyimi Raporu'nu inceledik. CrUX, Chrome kullanıcılarından toplanan trafikle zaman içindeki performans dağılımlarına ilişkin bir genel bakış oluşturur. Bu verilerin çoğu, Google'ın 2020'de duyurduğu ve aynı zamanda Lighthouse'a katkıda bulunan ve Lighthouse'da ifşa edilen Önemli Web Verileri ile ilgilidir.

Genel olarak, performansımızın yıl boyunca önemli ölçüde düştüğünü, özellikle Ağustos ve Eylül aylarında düştüğünü fark ettik. Bu çizelgeleri gördükten sonra, gerçekte ne olduğunu incelemek için canlı yayına aktardığımız bazı PR'lara tekrar bakabiliriz.
Bu sıralarda yeni bir gezinme çubuğunu canlı olarak kullanıma sunduğumuzu anlamamız uzun sürmedi. Tüm sayfalarda kullanılan bu gezinme çubuğu, bir menüdeki gezinme öğelerini dokunma veya tıklamayla görüntülemek için JavaScript'e güveniyordu, ancak bunun JavaScript kısmı aslında app.js paketi içinde toplanmıştı. Time To Interactive'i iyileştirmek için gezinme komut dosyasını paketten çıkarmaya ve satır içinde sunmaya karar verdik.
Aynı zamanlarda, manuel olarak oluşturulmuş kritik CSS dosyasından ana sayfa, makale, ürün sayfası, etkinlik, iş panosu vb. her şablon için kritik CSS üreten otomatik bir sisteme ve işlem sırasında satır içi kritik CSS'ye geçtik. inşa süresi. Yine de, otomatik olarak oluşturulan kritik CSS'nin ne kadar ağır olduğunun gerçekten farkında değildik. Daha detaylı araştırmak zorunda kaldık.
Aynı zamanda, web yazı tipi yüklemesini ayarlıyorduk, ön yükleme gibi kaynak ipuçlarıyla web yazı tiplerini daha agresif bir şekilde zorlamaya çalışıyorduk. Web yazı tipleri içeriğin oluşturulmasını geciktirdiği ve tam CSS dosyasının yanında aşırı öncelik verildiği için bu, performans çabalarımızla ters tepiyor gibi görünüyor.
Şimdi, gerilemenin yaygın nedenlerinden biri JavaScript'in yüksek maliyetidir, bu nedenle JavaScript bağımlılıklarımızın görsel bir resmini elde etmek için Webpack Bundle Analyzer ve Simon Hearne'nin istek haritasına da baktık. Başlangıçta oldukça sağlıklı görünüyordu.

Bir çerez onay hizmeti olan Cookiebot, Google Analytics ve ayrıca ürün panelleri sunmak ve özel reklamlar için dahili hizmetlerimiz olan CDN'ye birkaç istek geliyordu. Biraz daha yakından bakana kadar pek çok darboğaz varmış gibi görünmüyordu.
Performans çalışmasında, büyük olasılıkla ana sayfa ve büyük olasılıkla birkaç makale/ürün sayfası gibi bazı kritik sayfaların performansına bakmak yaygındır. Ancak, yalnızca bir ana sayfa varken, çok sayıda çeşitli ürün sayfası olabilir, bu nedenle hedef kitlemizi temsil edenleri seçmemiz gerekiyor.
Aslında, SmashingMag'de kod ağırlıklı ve tasarım ağırlıklı birkaç makale yayınladığımız için, yıllar boyunca ağır GIF'ler, sözdizimi vurgulanmış kod parçacıkları, CodePen yerleştirmeleri, video/ses içeren binlerce makale biriktirdik. gömmeler ve hiç bitmeyen yorumların iç içe geçmiş dizileri.
Bir araya getirildiğinde, çoğu, aşırı ana iş parçacığı çalışmasıyla birlikte DOM boyutunda bir patlamadan başka bir şeye neden olmuyordu - binlerce sayfadaki deneyimi yavaşlatıyordu. Yerinde reklamlarla, bazı DOM öğelerinin sayfanın yaşam döngüsünün sonlarında bir dizi stil yeniden hesaplama ve yeniden boyamaya neden olarak enjekte edildiğinden bahsetmiyoruz - ayrıca uzun görevler üretebilen pahalı görevler.
Tüm bunlar, yukarıdaki grafikte oldukça hafif bir makale sayfası için oluşturduğumuz haritada görünmüyordu. Bu yüzden elimizdeki en ağır sayfaları seçtik - her şeye gücü yeten ana sayfa, en uzun olanı, çok sayıda video gömme ve çok sayıda CodePen gömme içeren sayfa - ve onları elimizden geldiğince optimize etmeye karar verdik. Sonuçta, eğer hızlılarsa, tek bir CodePen gömülü olan sayfalar da daha hızlı olmalıdır.
Bu sayfalar göz önünde bulundurulduğunda, harita biraz farklı görünüyordu. Bir Smashing makalesinden gelen 78 istekle Vimeo oynatıcı ve Vimeo CDN'ye giden büyük kalın çizgiye dikkat edin.

Ana iş parçacığı üzerindeki etkiyi incelemek için DevTools'taki Performans paneline derinlemesine bir dalış yaptık. Daha spesifik olarak, 50 ms'den uzun süren (sağ üst köşede kırmızı bir dikdörtgenle vurgulanmıştır) ve Yeniden Hesaplama stilleri (mor çubuk) içeren görevler arıyorduk. Birincisi pahalı JavaScript yürütmesini gösterirken, ikincisi DOM ve yetersiz CSS'deki dinamik içerik enjeksiyonlarının neden olduğu stil geçersiz kılmalarını ortaya çıkaracaktır. Bu bize nereden başlayacağımıza dair eyleme geçirilebilir bazı işaretler verdi. Örneğin, web yazı tipi yüklememizin önemli bir yeniden boyama maliyeti olduğunu, JavaScript parçalarının ise ana ileti dizisini engelleyecek kadar ağır olduğunu hemen keşfettik.

Temel olarak, Önemli Web Verilerini çok yakından inceledik ve hepsinde iyi puan aldığımızdan emin olmaya çalıştık. İşin karamsar tarafında olmak için özellikle yavaş mobil cihazlara odaklanmayı seçtik — yavaş 3G, 400 ms RTT ve 400 kbps aktarım hızıyla. O zaman Lighthouse'un da sitemizden pek memnun kalmaması, en ağır makaleler için tam kırmızı puanlar vermesi ve kullanılmayan JavaScript, CSS, ekran dışı görüntüler ve boyutları hakkında yorulmadan şikayet etmesi şaşırtıcı değil.

Önümüzde bazı veriler olduğunda, kritik (ve kritik olmayan) CSS, JavaScript paketi, uzun görevler, web yazı tipi yükleme, düzen kaydırmaları ve üçüncü taraflara odaklanarak en ağır üç makale sayfasını optimize etmeye odaklanabiliriz. -gömülür. Daha sonra, eski kodu kaldırmak ve yeni modern tarayıcı özelliklerini kullanmak için kod tabanını da gözden geçirecektik. Önümüzde çok iş var gibi görünüyordu ve gerçekten de önümüzdeki aylar için oldukça meşguldük.
<head>
İçindeki Varlıkların Sırasını İyileştirme
İronik olarak, incelediğimiz ilk şey, yukarıda tanımladığımız tüm görevlerle yakından ilgili bile değildi. Performans atölyesinde Harry, her sayfanın <head>
bölümündeki varlıkların sırasını açıklamak için önemli miktarda zaman harcadı ve kritik içeriği hızlı bir şekilde sunmanın, varlıkların kaynak kodunda nasıl sıralandığı konusunda çok stratejik ve dikkatli olmak anlamına geldiğine dikkat çekti. .
Artık kritik CSS'nin web performansı için faydalı olduğu büyük bir vahiy olarak gelmemelidir. Ancak, kaynak ipuçları, web yazı tipi ön yüklemesi, eşzamanlı ve eşzamansız komut dosyaları, tam CSS ve meta veriler gibi diğer tüm varlıkların sırasının ne kadar farklı olduğu biraz şaşırtıcı oldu.
Kritik CSS'yi tüm asenkron komut dosyalarının ve yazı tipleri, resimler vb. gibi önceden yüklenmiş tüm varlıkların önüne yerleştirerek <head>
'in tamamını alt üst ettik. Önceden bağlanacağımız veya önceden yükleyeceğimiz varlıkları şablona göre ayırdık ve dosya türü, böylece kritik görüntüler, sözdizimi vurgulama ve video yerleştirmeleri yalnızca belirli türdeki makaleler ve sayfalar için erken talep edilecektir.
Genel olarak, <head>
içindeki düzeni dikkatli bir şekilde düzenledik, bant genişliği için rekabet eden önceden yüklenmiş varlıkların sayısını azalttık ve kritik CSS'yi doğru yapmaya odaklandık. <head>
sıralamasıyla ilgili bazı kritik hususlara daha derinden dalmak isterseniz, Harry bunları CSS ve Ağ Performansı makalesinde vurgulamaktadır. Bu değişiklik tek başına bize genel olarak 3-4 Lighthouse puanı kazandırdı.
Otomatik Kritik CSS'den Manuel Kritik CSS'ye Dönmek
<head>
etiketlerini hareket ettirmek hikayenin basit bir parçasıydı. Daha zor olanı, kritik CSS dosyalarının oluşturulması ve yönetimiydi. 2017'de, tüm ekran genişliklerinde ilk 1000 pikselin yüksekliğini oluşturmak için gereken tüm stilleri toplayarak her şablon için kritik CSS'yi elle oluşturduk. Bu elbette hantal ve biraz da ilham vermeyen bir görevdi, tüm bir kritik CSS dosyası ailesini ve tam bir CSS dosyasını evcilleştirmek için bakım sorunlarından bahsetmiyorum bile.
Bu nedenle, yapım rutininin bir parçası olarak bu işlemi otomatikleştirme seçeneklerini inceledik. Mevcut araç sıkıntısı gerçekten yoktu, bu yüzden birkaçını test ettik ve birkaç test yapmaya karar verdik. Bunları oldukça hızlı bir şekilde kurmayı ve çalıştırmayı başardık. Çıktı, otomatikleştirilmiş bir süreç için yeterince iyi görünüyordu, bu yüzden birkaç yapılandırma ayarından sonra fişi prize taktık ve üretime geçirdik. Bu, geçen yıl Temmuz-Ağustos civarında gerçekleşti ve yukarıdaki CrUX verilerindeki ani artış ve performans düşüşünde güzel bir şekilde görselleştirildi. Belirli stiller eklemek veya diğerlerini kaldırmak gibi basit şeylerle sık sık sorun yaşayarak, yapılandırma ile ileri geri gitmeye devam ettik. Örneğin, tanımlama bilgisi komut dosyası başlatılmadığı sürece bir sayfaya gerçekten dahil edilmeyen tanımlama bilgisi onay istemi stilleri.
Ekim ayında, sitede bazı önemli düzen değişiklikleri yaptık ve kritik CSS'yi incelerken, yine tamamen aynı sorunlarla karşılaştık - elde edilen sonuç oldukça ayrıntılıydı ve tam olarak istediğimiz gibi değildi. . Ekim ayının sonundaki bir deney olarak, kritik CSS yaklaşımımızı tekrar gözden geçirmek ve el yapımı kritik bir CSS'nin ne kadar küçük olabileceğini incelemek için hepimiz güçlü yönlerimizi bir araya getirdik. Derin bir nefes aldık ve önemli sayfalarda kod kapsamı aracının etrafında günler geçirdik. CSS kurallarını manuel olarak grupladık ve her iki yerde de (kritik CSS ve ana CSS) kopyaları ve eski kodu kaldırdık. Gerçekten de çok ihtiyaç duyulan bir temizlikti, çünkü 2017-2018 yıllarında yazılan birçok stil yıllar içinde modası geçmiş hale geldi.
Sonuç olarak, el yapımı üç kritik CSS dosyası ve şu anda devam etmekte olan üç dosya daha elde ettik:
- kritik-homepage-manual.css (8,2 KB, Brotlified)
- kritik-makale-manual.css (8 KB, Brotlified)
- kritik-makaleler-manual.css (6 KB, Brotlified)
- kritik-kitaplar-manual.css ( yapılacak işler )
- kritik-events-manual.css ( yapılacak işler )
- kritik-iş-board-manual.css ( yapılması gereken işler )
Dosyalar, her şablonun başlığında sıralanmıştır ve şu anda sitede kullanılan (veya artık gerçekten kullanılmayan) her şeyi içeren tek parça CSS paketinde çoğaltılırlar. Şu anda, tam CSS paketini birkaç CSS paketine ayırmayı düşünüyoruz, böylece derginin okuyucusu iş panosundan veya kitap sayfalarından stiller indirmez, ancak daha sonra bu sayfalara ulaştığında hızlı bir şekilde oluşturulur. kritik CSS ile ve o sayfa için CSS'nin geri kalanını eşzamansız olarak alın - yalnızca o sayfada.
Kuşkusuz, el yapımı kritik CSS dosyalarının boyutu çok daha küçük değildi: kritik CSS dosyalarının boyutunu yaklaşık %14 oranında küçülttük. Ancak, ihtiyaç duyduğumuz her şeyi, kopyalar ve geçersiz kılmalar olmadan baştan sona doğru sırayla dahil ettiler. Bu, doğru yönde atılmış bir adım gibi görünüyordu ve bize 3–4 puanlık bir Deniz Feneri desteği verdi. ilerleme kaydediyorduk.
Web Yazı Tipi Yüklemesini Değiştirme
font-display
parmaklarımızın ucundayken, yazı tipi yükleme geçmişte bir sorun gibi görünüyor. Ne yazık ki, bizim durumumuzda tam olarak doğru değil. Siz sevgili okuyucular, Smashing Magazine'deki bir dizi makaleyi ziyaret ediyor gibisiniz. Ayrıca, başka bir makaleyi okumak için sık sık siteye geri dönersiniz - belki birkaç saat veya gün sonra veya belki bir hafta sonra. Site genelinde kullanılan font-display
ilgili sorunlardan biri, makaleler arasında çok fazla hareket eden okuyucular için, yedek yazı tipi ile web yazı tipi arasında (normalde yazı tiplerinde olduğu gibi olmaması gereken) çok sayıda flaş fark etmemizdi. uygun şekilde önbelleğe alınmış).
Bu iyi bir kullanıcı deneyimi gibi gelmedi, bu yüzden seçenekleri inceledik. Smashing'de iki ana yazı tipi kullanıyoruz - başlıklar için Mija ve gövde kopyası için Elena. Mija iki ağırlıkta (Normal ve Kalın), Elena ise üç ağırlıkta (Normal, İtalik, Kalın) gelir. Elena'nın Kalın İtalik'ini yıllar önce yeniden tasarım sırasında çıkardık çünkü onu sadece birkaç sayfada kullandık. Kullanılmayan karakterleri ve Unicode aralıklarını kaldırarak diğer yazı tiplerini alt kümeye alıyoruz.
Makalelerimiz çoğunlukla metin şeklindedir, bu nedenle sitede çoğu zaman En Büyük İçerikli Boya'nın bir makaledeki metnin ilk paragrafı veya yazarın fotoğrafı olduğunu keşfettik. Bu, minimum yeniden akışla zarif bir şekilde web yazı tipine geçerken, ilk paragrafın bir yedek yazı tipinde hızlı bir şekilde görünmesini sağlamak için ekstra özen göstermemiz gerektiği anlamına gelir.
Ön sayfanın ilk yükleme deneyimine yakından bakın (üç kez yavaşlatıldı):
Bir çözüm bulurken dört ana hedefimiz vardı:
- İlk ziyarette, metni hemen bir yedek yazı tipiyle işleyin;
- Düzen kaymalarını en aza indirmek için yedek yazı tiplerinin ve web yazı tiplerinin yazı tipi metriklerini eşleştirin;
- Tüm web yazı tiplerini eşzamansız olarak yükleyin ve hepsini bir kerede uygulayın (en fazla 1 yeniden akış);
- Sonraki ziyaretlerde, tüm metni doğrudan web yazı tiplerinde (yanıp sönme veya yeniden akış olmadan) oluşturun.
Başlangıçta font-display: swap on font-face
kullanmayı denedik. Bu en basit seçenek gibi görünüyordu, ancak yukarıda bahsedildiği gibi, bazı okuyucular birkaç sayfayı ziyaret edecek, bu nedenle site genelinde oluşturduğumuz altı yazı tipinde çok fazla titremeyle karşılaştık. Ayrıca, yalnızca yazı tipi gösterimi ile istekleri veya yeniden boyamaları gruplayamadık.
Başka bir fikir, ilk ziyarette her şeyi yedek yazı tipinde işlemek, ardından tüm yazı tiplerini eşzamansız olarak istemek ve önbelleğe almak ve yalnızca sonraki ziyaretlerde web yazı tiplerini doğrudan önbellekten teslim etmekti. Bu yaklaşımla ilgili sorun, arama motorlarından çok sayıda okuyucunun gelmesi ve en azından bazılarının yalnızca bu sayfayı görmesiydi - ve bir makaleyi yalnızca sistem yazı tipinde oluşturmak istemedik.
Öyleyse ne var?
2017'den beri, web yazı tipi yükleme için temelde iki oluşturma aşamasını tanımlayan İki Aşamalı İşleme yaklaşımını kullanıyoruz: biri minimum web yazı tipi alt kümesiyle, diğeri ise eksiksiz bir yazı tipi ağırlıkları ailesiyle. Eskiden, sitede en sık kullanılan ağırlıklar olan Mija Bold ve Elena Regular'ın minimal alt kümelerini oluşturduk. Her iki alt küme de yalnızca Latin karakterleri, noktalama işaretleri, sayılar ve birkaç özel karakter içerir. Bu yazı tiplerinin ( ElenaInitial.woff2 ve MijaInitial.woff2 ) boyutu çok küçüktü - genellikle yaklaşık 10-15 KB boyutundaydı. Tüm sayfayı bu iki fontta görüntüleyerek font oluşturmanın ilk aşamasında onlara hizmet veriyoruz.

Bunu, hangi yazı tiplerinin başarıyla yüklendiği ve hangilerinin henüz yüklenmediği hakkında bize bilgi veren bir Yazı Tipi Yükleme API'si ile yapıyoruz. Perde arkasında, gövdeye bir .wf-loaded-stage1 sınıfı eklenerek ve stilleri bu yazı tiplerinde oluşturan stiller ekleyerek gerçekleşir:

.wf-loaded-stage1 article, .wf-loaded-stage1 promo-box, .wf-loaded-stage1 comments { font-family: ElenaInitial,sans-serif; } .wf-loaded-stage1 h1, .wf-loaded-stage1 h2, .wf-loaded-stage1 .btn { font-family: MijaInitial,sans-serif; }
Yazı tipi dosyaları oldukça küçük olduğundan, umarım ağdan oldukça hızlı geçerler. Ardından, okuyucu bir makaleyi gerçekten okumaya başlayabildiğinden, yazı tiplerinin tam ağırlıklarını eşzamansız olarak yükleriz ve gövdeye .wf-loaded-stage2'yi ekleriz :
.wf-loaded-stage2 article, .wf-loaded-stage2 promo-box, .wf-loaded-stage2 comments { font-family: Elena,sans-serif; } .wf-loaded-stage2 h1, .wf-loaded-stage2 h2, .wf-loaded-stage2 .btn { font-family: Mija,sans-serif; }
Bu nedenle, bir sayfa yüklerken, okuyucular önce hızlı bir şekilde küçük bir web yazı tipi alt kümesi alacaklar ve ardından tam yazı tipi ailesine geçeceğiz. Şimdi, varsayılan olarak, geri dönüş yazı tipleri ve web yazı tipleri arasındaki bu geçişler, ağ üzerinden ilk gelene bağlı olarak rastgele gerçekleşir. Bir makaleyi okumaya başladığınızda, bu oldukça rahatsız edici gelebilir. Bu nedenle, yazı tiplerinin ne zaman değiştirileceğine karar vermeyi tarayıcıya bırakmak yerine, yeniden boyamaları gruplandırarak yeniden akış etkisini minimuma indirdik.
/* Loading web fonts with Font Loading API to avoid multiple repaints. With help by Irina Lipovaya. */ /* Credit to initial work by Zach Leatherman: https://noti.st/zachleat/KNaZEg/the-five-whys-of-web-font-loading-performance#sWkN4u4 */ // If the Font Loading API is supported... // (If not, we stick to fallback fonts) if ("fonts" in document) { // Create new FontFace objects, one for each font let ElenaRegular = new FontFace( "Elena", "url(/fonts/ElenaWebRegular/ElenaWebRegular.woff2) format('woff2')" ); let ElenaBold = new FontFace( "Elena", "url(/fonts/ElenaWebBold/ElenaWebBold.woff2) format('woff2')", { weight: "700" } ); let ElenaItalic = new FontFace( "Elena", "url(/fonts/ElenaWebRegularItalic/ElenaWebRegularItalic.woff2) format('woff2')", { style: "italic" } ); let MijaBold = new FontFace( "Mija", "url(/fonts/MijaBold/Mija_Bold-webfont.woff2) format('woff2')", { weight: "700" } ); // Load all the fonts but render them at once // if they have successfully loaded let loadedFonts = Promise.all([ ElenaRegular.load(), ElenaBold.load(), ElenaItalic.load(), MijaBold.load() ]).then(result => { result.forEach(font => document.fonts.add(font)); document.documentElement.classList.add('wf-loaded-stage2'); // Used for repeat views sessionStorage.foutFontsStage2Loaded = true; }).catch(error => { throw new Error(`Error caught: ${error}`); }); }
Ancak, ilk küçük yazı tipi alt kümesi ağ üzerinden hızlı bir şekilde gelmiyorsa ne olur? Bunun istediğimizden daha sık gerçekleştiğini fark ettik. Bu durumda, 3 saniyelik bir zaman aşımı süresi dolduktan sonra, modern tarayıcılar bir sistem yazı tipine geri döner (yazı tipi yığınımızda bu Arial olacaktır), daha sonra sırasıyla tam Elena veya Mija'ya geçmek için ElenaInitial veya MijaInitial'e geçiş yapın. . Bu, tadımamızda biraz fazla yanıp sönmeye neden oldu. Başlangıçta yalnızca yavaş ağlar için ilk aşama oluşturmayı kaldırmayı düşünüyorduk (Ağ Bilgi API'si aracılığıyla), ancak daha sonra tamamen kaldırmaya karar verdik.
Böylece Ekim ayında, ara aşamayla birlikte alt kümeleri tamamen kaldırdık. Hem Elena hem de Mija yazı tiplerinin tüm ağırlıkları müşteri tarafından başarıyla indirildiğinde ve uygulanmaya hazır olduğunda, 2. aşamayı başlatır ve her şeyi bir kerede yeniden boyarız. Ve yeniden akışları daha az fark edilir kılmak için, yedek yazı tiplerini ve web yazı tiplerini eşleştirmek için biraz zaman harcadık. Bu, çoğunlukla, sayfanın ilk görünür bölümünde boyanmış öğeler için biraz farklı yazı tipi boyutları ve satır yükseklikleri uygulamak anlamına geliyordu.
Bunun için font-style-matcher
ve (ahem, ahem) birkaç sihirli sayı kullandık. Küresel yedek yazı tipleri olarak başlangıçta -apple-system ve Arial'i kullanmamızın nedeni de budur; San Francisco ( -apple-system aracılığıyla oluşturuldu) Arial'den biraz daha iyi görünüyordu, ancak mevcut değilse, çoğu işletim sistemine yaygın olarak yayıldığı için Arial'i kullanmayı seçtik.
CSS'de şöyle görünecektir:
.article__summary { font-family: -apple-system,Arial,BlinkMacSystemFont,Roboto Slab,Droid Serif,Segoe UI,Ubuntu,Cantarell,Georgia,sans-serif; font-style: italic; /* Warning: magic numbers ahead! */ /* San Francisco Italic and Arial Italic have larger x-height, compared to Elena */ font-size: 0.9213em; line-height: 1.487em; } .wf-loaded-stage2 .article__summary { font-family: Elena,sans-serif; font-size: 1em; /* Original font-size for Elena Italic */ line-height: 1.55em; /* Original line-height for Elena Italic */ }
Bu oldukça iyi çalıştı. Metni hemen görüntüleriz ve web yazı tipleri ekranda gruplandırılmış olarak gelir, ideal olarak ilk görünümde tam olarak bir yeniden akışa neden olur ve sonraki görünümlerde tamamen yeniden akış olmaz.
Yazı tipleri indirildikten sonra, onları bir hizmet çalışanının önbelleğinde saklarız. Sonraki ziyaretlerde önce yazı tiplerinin önbellekte olup olmadığını kontrol ederiz. Varsa, bunları hizmet çalışanının önbelleğinden alır ve hemen uygularız. Değilse, fallback-web-font-switcheroo ile en baştan başlarız.
Bu çözüm, yazı tiplerini kalıcı ve güvenilir bir şekilde önbellekte tutarken, nispeten hızlı bağlantılarda yeniden akış sayısını minimuma (bir) düşürdü. Gelecekte, sihirli sayıları f-modlarla değiştirmeyi içtenlikle umuyoruz. Belki Zach Leatherman gurur duyardı.
Monolitik JS'yi Tanımlama ve Parçalama
DevTools'un Performans panelindeki ana konuyu incelediğimizde, tam olarak ne yapmamız gerektiğini biliyorduk. 70ms ile 580ms arasında süren, arayüzü engelleyen ve yanıt vermemesine neden olan sekiz Uzun Görev vardı. Genel olarak, bunlar en yüksek maliyetli senaryolardı:
- uc.js , bir tanımlama bilgisi istemi komut dosyası oluşturma (70ms)
- gelen full.css dosyasının (176ms) neden olduğu stil yeniden hesaplamaları (kritik CSS, tüm görünüm alanlarında 1000 piksel yüksekliğin altındaki stiller içermez)
- panelleri, alışveriş sepetini vb. yönetmek için load olayında çalışan reklam komut dosyaları + stil yeniden hesaplamaları (276ms)
- web yazı tipi anahtarı, stil yeniden hesaplamaları (290ms)
- app.js değerlendirmesi (580ms)
İlk önce en zararlı olanlara, deyim yerindeyse en uzun Uzun Görevlere odaklandık.

İlki, yazı tiplerinin değişmesinden (yedek yazı tipinden web yazı tipine) kaynaklanan ve 290 ms'den fazla ekstra çalışmaya (hızlı bir dizüstü bilgisayar ve hızlı bağlantı) neden olan pahalı düzen yeniden hesaplamaları nedeniyle meydana geldi. Birinci aşamayı yalnızca yazı tipi yüklemesinden kaldırarak, yaklaşık 80ms geri kazanabildik. Yine de yeterince iyi değildi çünkü 50ms bütçesinin çok ötesindeydi. Böylece daha derine inmeye başladık.
Yeniden hesaplamaların olmasının ana nedeni, yalnızca yedek yazı tipleri ile web yazı tipleri arasındaki büyük farklılıklardı. Yedek yazı tipleri ve web yazı tipleri için satır yüksekliğini ve boyutlarını eşleştirerek, bir metin satırının yedek yazı tipinde yeni bir satıra kaydırılacağı, ancak daha sonra biraz küçülüp önceki satıra sığacağı birçok durumdan kaçınabildik. tüm sayfanın geometrisinde büyük değişikliğe ve sonuç olarak büyük düzen kaymalarına neden olur. letter-spacing
ve word-spacing
aralığı ile de oynadık, ancak iyi sonuçlar vermedi.
Bu değişikliklerle 50-80ms daha kesebildik, ancak içeriği bir geri dönüş yazı tipinde görüntülemeden 120ms'nin altına indiremedik ve daha sonra içeriği web yazı tipinde görüntüleyemedik. Açıkçası, yalnızca ilk kez gelen ziyaretçileri büyük ölçüde etkilemesi gerekir, çünkü sonuçta sayfa görünümleri, yazı tipi değişikliği nedeniyle maliyetli yeniden akışlar olmadan doğrudan hizmet çalışanının önbelleğinden alınan yazı tipleriyle oluşturulacaktır.
Bu arada, bizim durumumuzda, Uzun Görevlerin çoğunun büyük JavaScript'ten değil, Yerleşim Yeniden Hesaplamalarından ve CSS'nin ayrıştırılmasından kaynaklandığını fark ettik, bu da biraz CSS yapmamız gerektiği anlamına geliyordu. cleaning, especially watching out for situations when styles are overwritten. In some way, it was good news because we didn't have to deal with complex JavaScript issues that much. However, it turned out not to be straightforward as we are still cleaning up the CSS this very day. We were able to remove two Long Tasks for good, but we still have a few outstanding ones and quite a way to go. Fortunately, most of the time we aren't way above the magical 50ms threshold.
The much bigger issue was the JavaScript bundle we were serving, occupying the main thread for a whopping 580ms. Most of this time was spent in booting up app.js which contains React, Redux, Lodash, and a Webpack module loader. The only way to improve performance with this massive beast was to break it down into smaller pieces. So we looked into doing just that.
With Webpack, we've split up the monolithic bundle into smaller chunks with code-splitting , about 30Kb per chunk. We did some package.json cleansing and version upgrade for all production dependencies, adjusted the browserlistrc setup to address the two latest browser versions, upgraded to Webpack and Babel to the latest versions, moved to Terser for minification, and used ES2017 (+ browserlistrc) as a target for script compilation.
We also used BabelEsmPlugin to generate modern versions of existing dependencies. Finally, we've added prefetch links to the header for all necessary script chunks and refactored the service worker, migrating to Workbox with Webpack (workbox-webpack-plugin).

Remember when we switched to the new navigation back in mid-2020, just to see a huge performance penalty as a result? The reason for it was quite simple. While in the past the navigation was just static plain HTML and a bit of CSS, with the new navigation, we needed a bit of JavaScript to act on opening and closing of the menu on mobile and on desktop. That was causing rage clicks when you would click on the navigation menu and nothing would happen, and of course, had a penalty cost in Time-To-Interactive scores in Lighthouse.
We removed the script from the bundle and extracted it as a separate script . Additionally, we did the same thing for other standalone scripts that were used rarely — for syntax highlighting, tables, video embeds and code embeds — and removed them from the main bundle; instead, we granularly load them only when needed.

However, what we didn't notice for months was that although we removed the navigation script from the bundle, it was loading after the entire app.js bundle was evaluated, which wasn't really helping Time-To-Interactive (see image above). We fixed it by preloading nav.js and deferring it to execute in the order of appearance in the DOM, and managed to save another 100ms with that operation alone. By the end, with everything in place we were able to bring the task to around 220ms.

We managed to get some improvement in place, but still have quite a way to go, with further React and Webpack optimizations on our to-do list. At the moment we still have three major Long Tasks — font switch (120ms), app.js execution (220ms) and style recalculations due to the size of full CSS (140ms). For us, it means cleaning up and breaking up the monolithic CSS next.
It's worth mentioning that these results are really the best-scenario- results. On a given article page we might have a large number of code embeds and video embeds, along with other third-party scripts and customer's browser extensions that would require a separate conversation.
Dealing With 3rd-Parties
Fortunately, our third-party scripts footprint (and the impact of their friends' fourth-party-scripts) wasn't huge from the start. But when these third-party scripts accumulated, they would drive performance down significantly. This goes especially for video embedding scripts , but also syntax highlighting, advertising scripts, promo panels scripts and any external iframe embeds.
Obviously, we defer all of these scripts to start loading after the DOMContentLoaded event, but once they finally come on stage, they cause quite a bit of work on the main thread. This shows up especially on article pages, which are obviously the vast majority of content on the site.
The first thing we did was allocating proper space to all assets that are being injected into the DOM after the initial page render. It meant width
and height
for all advertising images and the styling of code snippets. We found out that because all the scripts were deferred, new styles were invalidating existing styles, causing massive layout shifts for every code snippet that was displayed. We fixed that by adding the necessary styles to the critical CSS on the article pages.
We've re-established a strategy for optimizing images (preferably AVIF or WebP — still work in progress though). All images below the 1000px height threshold are natively lazy-loaded (with <img loading=lazy>
), while the ones on the top are prioritized ( <img loading=eager>
). The same goes for all third-party embeds.
We replaced some dynamic parts with their static counterparts — eg while a note about an article saved for offline reading was appearing dynamically after the article was added to the service worker's cache, now it appears statically as we are, well, a bit optimistic and expect it to be happening in all modern browsers.
As of the moment of writing, we're preparing facades for code embeds and video embeds as well. Plus, all images that are offscreen will get decoding=async
attribute, so the browser has a free reign over when and how it loads images offscreen, asynchronously and in parallel.

To ensure that our images always include width and height attributes, we've also modified Harry Roberts' snippet and Tim Kadlec's diagnostics CSS to highlight whenever an image isn't served properly. It's used in development and editing but obviously not in production.
One technique that we used frequently to track what exactly is happening as the page is being loaded, was slow-motion loading .
First, we've added a simple line of code to the diagnostics CSS, which provides a noticeable outline for all elements on the page.
* { outline: 3px solid red }
* { outline: 3px solid red }

* { outline: 3px red }
and observing the boxes as the browser is rendering the page. (Büyük önizleme)Then we record a video of the page loaded on a slow and fast connection. Then we rewatch the video by slowing down the playback and moving back and forward to identify where massive layout shifts happen.
Here's the recording of a page being loaded on a fast connection:
And here's the recording of a recording being played to study what happens with the layout:
By auditing the layout shifts this way, we were able to quickly notice what's not quite right on the page, and where massive recalculation costs are happening. As you probably have noticed, adjusting the line-height
and font-size
on headings might go a long way to avoid large shifts.
With these simple changes alone, we were able to boost performance score by a whopping 25 Lighthouse points for the video-heaviest article, and gain a few points for code embeds.
Enhancing The Experience
We've tried to be quite strategic in pretty much everything from loading web fonts to serving critical CSS. However, we've done our best to use some of the new technologies that have become available last year.
We are planning on using AVIF by default to serve images on SmashingMag, but we aren't quite there yet, as many of our images are served from Cloudinary (which already has beta support for AVIF), but many are directly from our CDN yet we don't really have a logic in place just yet to generate AVIFs on the fly. That would need to be a manual process for now.
We're lazy rendering some of the offset components of the page with content-visibility: auto . For example, the footer, the comments section, as well as the panels way below the first 1000px height threshold, are all rendered later after the visible portion of each page has been rendered.
link rel="prefetch"
ve hatta link rel="prerender"
(NoPush prefetch) ile sayfanın daha fazla gezinme için kullanılması çok muhtemel olan bazı bölümleriyle biraz oynadık - örneğin, varlıkları ilk kez önceden getirmek için ön sayfadaki makaleler (hala tartışılıyor).
Ayrıca En Büyük İçerikli Boyamayı azaltmak için yazar resimlerini ve dans eden kedi resimleri (gezinme için) ve tüm yazar resimleri için kullanılan gölge gibi her sayfada kullanılan bazı önemli varlıkları önceden yüklüyoruz. Ancak, okuyucunun daha büyük bir ekranda (>800 piksel) olması durumunda tümü önceden yüklenir, ancak daha doğru olması için Ağ Bilgi API'sini kullanmayı düşünüyoruz.
Ayrıca eski kodu kaldırarak, birkaç bileşeni yeniden düzenleyerek ve metin-dekorasyon-atlama kombinasyonuyla mükemmel alt çizgiler elde etmek için kullandığımız metin gölgesi hilesini kaldırarak tam CSS'nin ve tüm kritik CSS dosyalarının boyutunu küçülttük. -mürekkep ve metin-dekorasyon-kalınlığı (nihayet!).
Yapılacak İş
Sitedeki tüm küçük ve büyük değişiklikler üzerinde çalışmak için oldukça fazla zaman harcadık. Masaüstünde oldukça önemli gelişmeler ve mobil cihazlarda oldukça belirgin bir artış fark ettik. Yazımız şu anda masaüstünde ortalama 90 ile 100 Lighthouse puanı arasında, mobilde ise 65-80 civarında puan alıyor.


Mobil cihazlarda düşük puanın nedeni, uygulamanın başlatılması ve tam CSS dosyasının boyutu nedeniyle açıkça Etkileşim Süresinin zayıf ve Toplam Engelleme süresinin zayıf olmasıdır. Yani orada hala yapılması gereken işler var.
Sonraki adımlara gelince, şu anda CSS'nin boyutunu daha da küçültmeyi ve JavaScript'e benzer şekilde özellikle modüllere ayırmayı, CSS'nin bazı bölümlerini (örn. ödeme veya iş panosu veya kitaplar/e-Kitaplar) yalnızca gerekli.
Ayrıca, şu anda önemsiz gibi görünse de, app.js'nin performans etkisini azaltmak için mobil cihazlarda daha fazla gruplama denemesi seçeneklerini araştırıyoruz. Son olarak, tanımlama bilgisi istemi çözümümüzün alternatiflerini araştıracağız, kaplarımızı CSS clamp()
ile yeniden oluşturacağız, dolgu-alt oran tekniğini aspect-ratio
değiştireceğiz ve AVIF'te mümkün olduğunca çok görüntü sunmaya bakacağız.
İşte bu, Millet!
Umarım bu küçük vaka çalışması sizin için faydalı olacaktır ve belki de projenize hemen uygulayabileceğiniz bir veya iki teknik vardır. Sonuç olarak, performans, toplandığında müşterinizin deneyimini oluşturan veya bozan tüm ince ayrıntıların toplamıdır.
Performansta daha iyi olmaya son derece kararlı olsak da, erişilebilirliği ve sitenin içeriğini iyileştirmeye de çalışıyoruz. Bu nedenle, tam olarak doğru olmayan bir şey veya Smashing Magazine'i daha da geliştirmek için yapabileceğimiz herhangi bir şey görürseniz, lütfen bu makalenin yorumlarında bize bildirin.
Son olarak, bunun gibi makalelerden haberdar olmak istiyorsanız, lütfen arkadaş canlısı web ipuçları, hediyeler, araçlar ve makaleler ve mevsimlik Smashing kedileri seçimi için e-posta bültenimize abone olun.