Ayrıntılı ögelemeyle, Next.js ve Gatsby sayfa yükleme performansı iyileştirildi

Next.js ve Gatsby'deki daha yeni bir web paketi parçalama stratejisi, sayfa yükleme performansını iyileştirmek için yinelenen kodları en aza indirir.

Chrome, JavaScript açık kaynak ekosistemindeki araçlar ve çerçevelerle ortak çalışmalar yürütmektedir. Next.js ve Gatsby'nin yükleme performansını iyileştirmek için kısa süre önce birkaç yeni optimizasyon eklendi. Bu makalede, artık her iki çerçevede de varsayılan olarak gönderilen iyileştirilmiş ayrıntılı bir öbekleme stratejisi ele alınmaktadır.

Giriş

Birçok web çerçevesi gibi Next.js ve Gatsby de temel paketleyici olarak webpack'i kullanır. Webpack v3, tek bir (veya birkaç) "ortak" parçada (ya da parçalarda) farklı giriş noktaları arasında paylaşılan çıkış modüllerini mümkün kılmak için CommonsChunkPlugin'i kullanıma sundu. Paylaşılan kod, ayrı olarak indirilip tarayıcı önbelleğinde erkenden depolanabilir. Bu da daha iyi bir yükleme performansı sağlayabilir.

Bu kalıp, tek sayfalık uygulama çerçevesinin bir giriş noktası ve paket yapılandırmasının benimsediği şu şekilde popüler hale geldi:

Ortak giriş noktası ve paket yapılandırması

Pratik olsa da tüm paylaşılan modül kodlarını tek bir parçada paketleme kavramının bazı sınırlamaları vardır. Her giriş noktasında paylaşılmayan modüller, onu kullanmayan rotalar için indirilebilir. Bu da gereğinden fazla kodun indirilmesine neden olur. Örneğin, page1, common parçasını yüklediğinde page1, moduleC kullanmasa bile moduleC kodunu yükler. Bu nedenle, birkaç diğer eklentiyle birlikte webpack v4, eklentiyi yeni bir eklenti için kaldırdı: SplitChunksPlugin.

Geliştirilmiş Yığın Oluşturma

SplitChunksPlugin uygulamasının varsayılan ayarları çoğu kullanıcı için uygundur. Birden fazla rotada yinelenen kodun getirilmesini önlemek için bir dizi conditions bağlı olarak birden fazla bölünmüş parça oluşturulur.

Bununla birlikte, bu eklentiyi kullanan birçok web çerçevesi, parça bölme için hâlâ "tek yaygın" yaklaşımını izler. Örneğin Next.js, sayfaların% 50'sinden fazlasında ve tüm çerçeve bağımlılıklarında (react, react-dom vb.) kullanılan herhangi bir modülü içeren bir commons paketi oluşturur.

const splitChunksConfigs = {
  …
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

Çerçeveye bağlı kodların paylaşılan bir parçaya dahil edilmesi, kodun herhangi bir giriş noktası için indirilip önbelleğe alınabileceği anlamına gelse de, sayfaların yarısından fazlasında kullanılan yaygın modüllerin dahil edilmesiyle ilgili kullanıma dayalı bulgular pek etkili değildir. Bu oranın değiştirilmesi iki sonuçtan yalnızca biriyle sonuçlanır:

  • Oranı azaltırsanız daha fazla gereksiz kod indirilir.
  • Oranı artırırsanız daha fazla kod birden çok rotada yinelenir.

Next.js, bu sorunu çözmek amacıylaSplitChunksPlugin için herhangi bir rotadaki gereksiz kodları azaltan farklı bir yapılandırma benimsemiştir.

  • Yeterli büyüklükte herhangi bir üçüncü taraf modülü (160 KB'tan büyük), kendi parçasına bölünür
  • Çerçeve bağımlılıkları (react, react-dom vb.) için ayrı bir frameworks parçası oluşturulur
  • Gerektiği kadar paylaşılan parça oluşturulur (en fazla 25)
  • Oluşturulacak minimum veri parçası boyutu 20 KB olarak değiştirildi

Bu ayrıntılı parçalama stratejisi aşağıdaki avantajları sağlar:

  • Sayfa yüklenme süreleri iyileştirildi. Tek bir yerine birden fazla paylaşılan parçanın yayınlanması, herhangi bir giriş noktası için gereksiz (veya yinelenen) kod miktarını en aza indirir.
  • Gezinme sırasında iyileştirilmiş önbelleğe alma. Büyük kitaplıkları ve çerçeve bağımlılıklarını ayrı parçalara bölmek, yükseltme yapılana kadar ikisinin de değişme olasılığı düşük olduğundan önbelleği geçersiz kılma olasılığını azaltır.

Next.js'nin benimsediği yapılandırmanın tamamını webpack-config.ts'te görebilirsiniz.

Daha fazla HTTP isteği

SplitChunksPlugin, ayrıntılı öbeklemenin temelini tanımlamıştı. Bu yaklaşımı Next.js gibi bir çerçeveye uygulamak ise tamamen yeni bir kavram değildi. Bununla birlikte birçok çerçeve, birkaç nedenden ötürü tek bir bulgusal ve "yaygın kullanılanlar" paket stratejisi kullanmaya devam etti. Buna, çok daha fazla HTTP isteğinin site performansını olumsuz etkileyebileceği endişesi de dahildir.

Tarayıcılar, tek bir kaynağa yalnızca sınırlı sayıda TCP bağlantısı (Chrome için 6) açabilir. Bu nedenle, bir paketleyici tarafından oluşturulan parça sayısının en aza indirilmesi, toplam istek sayısının bu eşiğin altında kalmasını sağlayabilir. Ancak bu yalnızca HTTP/1.1 için geçerlidir. HTTP/2'de Multiplex, birden fazla isteğin tek bir kaynak üzerinden tek bir bağlantı kullanılarak paralel olarak akışına izin verir. Diğer bir deyişle, genellikle paketleyicimizin yayınladığı parça sayısını sınırlama konusunda endişelenmemize gerek yoktur.

Başlıca tüm tarayıcılar HTTP/2'yi destekler. Chrome ve Next.js ekipleri, Next.js'nin tek "yaygın" paketini birden fazla paylaşılan parçaya bölerek istek sayısını artırmanın, yükleme performansını herhangi bir şekilde etkileyip etkilemeyeceğini görmek istedi. maxInitialRequests özelliğini kullanarak, tek bir sitenin performansını ölçerken maksimum paralel istek sayısını değiştirerek başladılar.

İstek sayısı arttıkça sayfa yükleme performansı

Tek bir web sayfasında birden çok denemenin ortalama üç çalışmasında, maksimum ilk istek sayısı (5'ten 15'e) değiştirilirken load, start-render ve First Contentful Paint zamanlarının hepsi neredeyse aynı kaldı. İşin ilginç tarafı, ancak yüzlerce isteği agresif bir şekilde böldükten sonra hafif bir performans ek yükü fark ettik.

Yüzlerce istekle sayfa yükleme performansı

Bu, güvenilir bir eşiğin (20~25 istek) altında kalmanın, yükleme performansı ile önbelleğe alma verimliliği arasında doğru dengeyi sağladığını gösterdi. Birkaç temel testten sonra maxInitialRequest sayısı olarak 25 seçildi.

Birbirine paralel olarak gerçekleşen maksimum istek sayısının değiştirilmesi, birden fazla paylaşılan paketle sonuçlandı ve bunları her giriş noktası için uygun şekilde ayırmak, aynı sayfa için gereksiz kod miktarını önemli ölçüde azalttı.

Artan öbekleme ile JavaScript yükü azalması

Bu denemenin tek amacı, sayfa yükleme performansı üzerinde herhangi bir olumsuz etki olup olmadığını görmek için istek sayısının değiştirilmesiydi. Sonuçlar, sayfayı yavaşlatmadan JavaScript yükünün boyutunu azalttığı için test sayfasında maxInitialRequests değerini 25 olarak ayarlamanın en uygun seçenek olduğunu göstermektedir. Sayfayı kullanılabilir hale getirmek için gereken toplam JavaScript miktarı hâlâ aynı kalmıştır. Bu durum, sayfa yükleme performansının neden az miktarda kod kullanılmasıyla birlikte iyileşmediğini açıklar.

webpack, parçanın oluşturulması için varsayılan minimum boyut olarak 30 KB kullanır. Ancak 25 değerindeki maxInitialRequests değerini 20 KB'lık minimum boyutla birleştirmek, önbelleğe alma işleminin daha iyi olmasını sağladı.

Ayrıntılı parçalarla boyutu küçültme

Next.js de dahil olmak üzere birçok çerçeve, her rota geçişi için yeni komut dosyası etiketleri eklemek üzere istemci tarafı yönlendirme (JavaScript tarafından işlenir) kullanır. Peki bu dinamik parçaları derleme sırasında nasıl önceden belirliyorlar?

Next.js, hangi çıktı parçalarının farklı giriş noktaları tarafından kullanıldığını belirlemek için sunucu tarafı derleme manifest dosyası kullanır. Bu bilgileri istemciye de sağlamak için her giriş noktasının tüm bağımlılıklarını eşlemek üzere kısaltılmış bir istemci tarafı derleme manifest dosyası oluşturuldu.

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Next.js uygulamasında paylaşılan birden fazla parçanın çıkışı.

Bu yeni ayrıntılı öbekleme stratejisi ilk olarak Next.js'de bir işaretin arkasında kullanıma sunuldu ve burada bir dizi ilk kullanıcı üzerinde test edildi. Birçok kişi, sitesinin tamamında kullanılan toplam JavaScript'te önemli ölçüde düşüş gördü:

Web sitesi Toplam JS Değişikliği % Farkı
https://www.barnebys.com/ -238 KB -%23
https://sumup.com/ -220 KB -%30
https://www.hashicorp.com/ -11 MB -%71
Tüm rotalarda JavaScript boyutu azaltmaları - tüm rotalarda (sıkıştırılmış)

Varsayılan olarak son sürüm, sürüm 9.2'de gönderilmiştir.

Gatsby

Gatsby, ortak modülleri tanımlamak için kullanıma dayalı buluşsal yaklaşımla aynı yaklaşımı izlerdi:

config.optimization = {
  …
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

Web paketi yapılandırmasını benzer ayrıntılı bir öbekleme stratejisi kullanacak şekilde optimize ederek, birçok büyük sitede JavaScript'te önemli ölçüde azalma olduğunu da fark ettiler:

Web sitesi Toplam JS Değişikliği % Farkı
https://www.gatsbyjs.org/ -680 KB -%22
https://www.thirdandgrove.com/ -390 KB -%25
https://ghost.org/ -1,1 MB -%35
https://reactjs.org/ -80 KB -%8
Tüm rotalarda JavaScript boyutu azaltmaları - tüm rotalarda (sıkıştırılmış)

Bu mantığı, varsayılan olarak 2.20.7 sürümünde gönderilen web paketi yapılandırmasına nasıl uyguladıklarını anlamak için PR bölümüne göz atın.

Sonuç

Ayrıntılı parçalar gönderme kavramı Next.js, Gatsby ve hatta webpack'e özgü değildir. Kullanılan çerçeve veya modül paketleyiciden bağımsız olarak, büyük bir "yaygın" paket yaklaşımı benimserse herkes, uygulamasının yığın oluşturma stratejisini iyileştirmeyi düşünmelidir.

  • Vanilla React uygulamasına aynı bölme optimizasyonlarının uygulandığını görmek istiyorsanız bu örnek React uygulamasına göz atın. Bu uygulama, ayrıntılı öbekleme stratejisinin basitleştirilmiş bir sürümünü kullanır ve sitenize aynı mantığı uygulamaya başlamanıza yardımcı olabilir.
  • Toplayıcı Parçalar'da, parçalar varsayılan olarak ayrıntılı şekilde oluşturulur. Davranışı manuel olarak yapılandırmak istiyorsanız manualChunks platformuna göz atın.