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

Next.js ve Gatsby'deki daha yeni bir webpack parçalama stratejisi, sayfa yükleme performansını artırmak için yinelenen kodu en aza indirir.

Houssein Djirdeh
Houssein Djirdeh

Chrome, JavaScript açık kaynak ekosistemindeki araçlar ve çerçevelerle işbirliği yapıyor. Yakın zamanda Next.js ve Gatsby'nin yükleme performansını iyileştirmek için daha yeni optimizasyonlar eklendi. Bu makalede, artık her iki çerçevede de varsayılan olarak sunulan, iyileştirilmiş ayrıntılı parçalama 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, farklı giriş noktaları arasında paylaşılan modüllerin tek bir (veya birkaç) "ortak" parçada (veya parçalarda) çıkışını 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, birçok tek sayfalık uygulama çerçevesinin aşağıdaki gibi bir giriş noktası ve paket yapılandırması benimsemesiyle popüler hale geldi:

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

Tüm paylaşılan modül kodunu tek bir parçada birleştirme kavramı pratik olsa da bazı sınırlamaları vardır. Her giriş noktasında paylaşılmayan modüller, bu modülleri 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'yi kullanmasa bile moduleC'nin kodunu yükler. Bu ve diğer birkaç nedenden dolayı webpack v4, eklentiyi kaldırarak yerine yeni bir eklenti getirdi: SplitChunksPlugin.

Gelişmiş parçalama

SplitChunksPlugin için varsayılan ayarlar çoğu kullanıcı için uygundur. Birden fazla rotada yinelenen kodların getirilmesini önlemek için çeşitli koşullara bağlı olarak birden fazla bölünmüş parça oluşturulur.

Ancak bu eklentiyi kullanan birçok web çerçevesi, parçalara ayırma konusunda hâlâ "tek ortak" yaklaşımını izler. Örneğin Next.js, sayfaların% 50'sinden fazlasında kullanılan tüm modülleri ve tüm çerçeve bağımlılıklarını (react, react-dom vb.) 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ı kodun paylaşılan bir parçaya dahil edilmesi, 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 ortak modülleri dahil etmenin kullanıma dayalı sezgisel yöntemi pek etkili değildir. Bu oranı değiştirmek yalnızca iki sonuçtan birine yol açar:

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

Next.js bu sorunu çözmek için her rotadaki gereksiz kodları azaltan farklı bir yapılandırmaSplitChunksPlugin kullanır.

  • Yeterince büyük olan tüm üçüncü taraf modülleri (160 KB'tan büyük) kendi ayrı parçaları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 parçanın minimum 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ştirilir. Tek bir parça yerine birden fazla paylaşılan parça yayınlamak, 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 her ikisinin de değişme olasılığı düşük olduğundan önbellek geçersiz kılma olasılığını azaltır.

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

Daha fazla HTTP isteği

SplitChunksPlugin, ayrıntılı parçalama için temel oluşturdu ve bu yaklaşımı Next.js gibi bir çerçeveye uygulamak tamamen yeni bir kavram değildi. Ancak birçok çerçeve, birkaç nedenden dolayı tek bir sezgisel ve "ortak" paket stratejisi kullanmaya devam etti. Bu endişeler arasında, çok daha fazla HTTP isteğinin site performansını olumsuz etkileyebileceği de yer alır.

Tarayıcılar tek bir kaynağa yalnızca sınırlı sayıda TCP bağlantısı açabilir (Chrome için 6). Bu nedenle, paketleyici tarafından oluşturulan parça sayısını en aza indirmek, 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 multiplexing, tek bir kaynak üzerinden tek bir bağlantı kullanılarak birden fazla isteğin paralel olarak akışına olanak tanır. Başka bir deyişle, paketleyicimiz tarafından oluşturulan parçaların sayısını sınırlama konusunda endişelenmemize gerek yoktur.

Tüm büyük tarayıcılar HTTP/2'yi destekler. Chrome ve Next.js ekipleri, Next.js'nin tek "commons" paketini birden fazla paylaşılan parçaya bölerek istek sayısını artırmanın yükleme performansını etkileyip etkilemeyeceğini görmek istedi. İlk olarak, maxInitialRequests özelliğini kullanarak maksimum paralel istek sayısını değiştirirken tek bir sitenin performansını ölçtüler.

Artan istek sayısıyla sayfa yükleme performansı

Tek bir web sayfasında birden fazla denemenin ortalama üç kez çalıştırılmasıyla elde edilen sonuçlara göre, maksimum ilk istek sayısı değiştirildiğinde (5'ten 15'e) load, oluşturmaya başlama ve First Contentful Paint sürelerinin tümü yaklaşık olarak aynı kaldı. İlginç bir şekilde, yalnızca agresif bir şekilde yüzlerce isteğe bölündükten sonra hafif bir performans ek yükü fark ettik.

Yüzlerce istek içeren 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. Bazı temel testlerden sonra, maxInitialRequest sayısı olarak 25 seçildi.

Paralel olarak gerçekleşen maksimum istek sayısını değiştirmek, birden fazla paylaşılan pakete neden oldu ve bunları her giriş noktası için uygun şekilde ayırmak, aynı sayfa için gereksiz kod miktarını önemli ölçüde azalttı.

Daha fazla parçalama ile JavaScript yükü azaltma

Bu deneme, yalnızca sayfa yükleme performansı üzerinde olumsuz bir etki olup olmayacağını görmek için istek sayısını değiştirmeyle ilgiliydi. Sonuçlar, test sayfasında maxInitialRequests değerini 25 olarak ayarlamanın en iyi seçenek olduğunu gösteriyor. Bu sayede, JavaScript yük boyutu sayfayı yavaşlatmadan azaltıldı. Sayfayı canlandırmak için gereken toplam JavaScript miktarı yaklaşık olarak aynı kaldı. Bu da sayfa yükleme performansının, azaltılan kod miktarıyla neden iyileşmediğini açıklıyor.

webpack, oluşturulacak bir parçanın varsayılan minimum boyutu olarak 30 KB kullanır. Ancak 25 değerindeki bir maxInitialRequests değerini 20 KB minimum boyutla birleştirmek daha iyi önbelleğe alma sağladı.

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

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

Next.js, farklı giriş noktaları tarafından hangi çıkış parçalarının kullanıldığını belirlemek için sunucu tarafı derleme manifest dosyasını kullanır. Bu bilgileri istemciye de sağlamak için her giriş noktasının tüm bağımlılıklarını eşleyen 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 birden fazla paylaşılan parçanın çıkışı.

Bu yeni ayrıntılı parçalama 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 site, tüm sitelerinde kullanılan toplam JavaScript miktarında önemli düşüşler 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
JavaScript boyutunda küçülme - tüm rotalarda (sıkıştırılmış)

Son sürüm, varsayılan olarak 9.2 sürümünde kullanıma sunuldu.

Gatsby

Gatsby, ortak modülleri tanımlamak için kullanıma dayalı bir sezgisel yöntem kullanma yaklaşımını izliyordu:

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)[\\/]/,
      },

Webpack yapılandırmalarını benzer bir ayrıntılı parçalama stratejisi benimseyecek şekilde optimize ederek birçok büyük sitede JavaScript boyutunda önemli azalmalar olduğunu 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
JavaScript boyutunda küçülme - tüm rotalarda (sıkıştırılmış)

Bu mantığı, varsayılan olarak v2.20.7'de sunulan webpack yapılandırmalarına nasıl uyguladıklarını anlamak için PR'ye 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 "ortak" paket yaklaşımı izliyorsa herkes uygulamasının parçalama stratejisini iyileştirmeyi düşünmelidir.

  • Aynı parçalama optimizasyonlarının standart bir React uygulamasına nasıl uygulandığını görmek isterseniz bu örnek React uygulamasına göz atın. Bu uygulama, ayrıntılı parçalama stratejisinin basitleştirilmiş bir sürümünü kullanır ve aynı mantığı kendi sitenize uygulamaya başlamanıza yardımcı olabilir.
  • Toplama için parçalar varsayılan olarak ayrıntılı şekilde oluşturulur. Davranışı manuel olarak yapılandırmak istiyorsanız manualChunks bölümüne göz atın.