Mempercepat frontend. Ketika banyak permintaan server bagus

Artikel ini menjelaskan beberapa metode untuk mempercepat pemuatan aplikasi front-end untuk mengimplementasikan antarmuka pengguna yang responsif dan cepat.

Kami akan membahas arsitektur umum dari frontend, cara memuatkan sumber daya yang diperlukan dan meningkatkan kemungkinan bahwa mereka ada di cache. Kami akan membahas sedikit cara memberikan sumber daya dari backend dan kapan mungkin membatasi diri pada halaman statis alih-alih aplikasi klien interaktif.

Proses pengunduhan dibagi menjadi tiga tahap. Untuk setiap tahap, kami merumuskan strategi umum untuk meningkatkan produktivitas:

  1. Render awal : berapa lama bagi pengguna untuk melihat setidaknya sesuatu
    • Kurangi rendering permintaan pemblokiran
    • Hindari rantai berurutan
    • Gunakan kembali koneksi server
    • Pekerja Layanan untuk Rendering Instan
  2. : ,
    • . .
    • ,


  3. :
    • ,


Sampai rendering awal, pengguna tidak melihat apa pun di layar. Apa yang kami butuhkan untuk rendering ini? Minimal, unggah dokumen HTML, dan dalam banyak kasus sumber daya tambahan, seperti file CSS dan JavaScript. Setelah tersedia, browser dapat memulai semacam rendering.

Bagan WebPageTest disediakan di seluruh artikel ini . Urutan kueri untuk situs Anda mungkin akan terlihat seperti ini.



Dokumen HTML memuat banyak file tambahan, dan halaman tersebut diberikan setelah diunduh. Harap perhatikan bahwa file CSS dimuat secara paralel satu sama lain, sehingga setiap permintaan tambahan tidak menambah penundaan yang signifikan.

(Catatan: di tangkapan layar, gov.uk adalah contoh di mana HTTP / 2 sekarang diaktifkansehingga domain sumber daya dapat menggunakan kembali koneksi yang ada. Lihat di bawah untuk koneksi server.)

Kurangi rendering permintaan pemblokiran


Lembar gaya dan skrip (secara default) memblokir perenderan konten apa pun di bawahnya.

Ada beberapa opsi untuk memperbaikinya:

  • Pindahkan tag skrip ke bagian bawah tubuh
  • Unduh skrip dalam mode asinkron menggunakan async
  • Jika JS atau CSS harus dimuat secara berurutan, lebih baik untuk menyematkannya dengan potongan kecil

Hindari percakapan dengan permintaan berurutan yang memblokir perenderan


Penundaan rendering situs tidak selalu dikaitkan dengan sejumlah besar permintaan yang memblokir rendering. Yang lebih penting adalah ukuran masing-masing sumber daya, serta waktu mulai pengunduhannya. Yaitu, saat ketika browser tiba-tiba menyadari bahwa sumber ini perlu diunduh.

Jika browser mendeteksi perlunya mengunduh file hanya setelah menyelesaikan permintaan lain, maka ada rantai permintaan. Itu dapat terbentuk karena berbagai alasan:

  • Aturan @importCSS
  • Font web direferensikan oleh file CSS
  • JavaScript atau tag skrip yang dapat diunduh

Lihatlah contoh ini:



Salah satu file CSS di situs ini memuat font Google melalui aturan @import. Ini berarti bahwa browser harus bergantian menjalankan permintaan berikut:

  1. Dokumen HTML
  2. Aplikasi CSS
  3. CSS untuk Google Font
  4. File Font Google Woff (tidak ditampilkan dalam diagram)

Untuk memperbaikinya, pertama-tama pindahkan permintaan Google Font CSS dari tag @importke tautan dalam dokumen HTML. Jadi kami mempersingkat rantai dengan satu tautan.

Untuk mempercepat lebih jauh, embed Google Fonts CSS langsung ke file HTML atau CSS Anda.

(Perlu diingat bahwa respons CSS dari server Google Font bergantung pada baris agen pengguna. Jika Anda membuat permintaan menggunakan IE8, CSS akan merujuk ke file EOT, browser IE11 akan menerima file woff, dan browser modern akan menerima file woff2. Jika Anda setuju bahwa browser lama akan terbatas pada font sistem, Anda cukup menyalin dan menempelkan isi file CSS ke diri Anda sendiri).

Bahkan setelah dimulainya rendering, pengguna tidak mungkin dapat berinteraksi dengan halaman, karena font perlu dimuat untuk menampilkan teks. Ini adalah penundaan jaringan tambahan yang ingin saya hindari. Parameter swap berguna di sini , memungkinkan Anda untuk menggunakannya font-displaydengan Google Font, dan menyimpan font secara lokal.

Terkadang rantai kueri tidak dapat diselesaikan. Dalam kasus seperti itu, Anda mungkin ingin mempertimbangkan tag preload atau preconnect . Misalnya, situs web dalam contoh di atas dapat terhubung fonts.googleapis.comsebelum permintaan CSS yang sebenarnya tiba.

Menggunakan kembali koneksi server untuk mempercepat permintaan


Untuk membuat koneksi baru ke server, biasanya memerlukan tiga paket pertukaran antara browser dan server:

  1. Pencarian DNS
  2. Membangun Koneksi TCP
  3. Buat Koneksi SSL

Setelah koneksi dibuat, setidaknya satu pertukaran paket lagi diperlukan untuk mengirim permintaan dan menerima tanggapan.

Grafik di bawah ini menunjukkan bahwa kita memulai koneksi dengan empat server yang berbeda: hostgator.com, optimizely.com, googletagmanager.com, dan googelapis.com.

Namun, permintaan server berikutnya dapat menggunakan kembali koneksi yang ada . Pengunduhan base.cssjuga index1.cssterjadi lebih cepat karena mereka berada di server yang sama hostgator.comdengan koneksi yang telah dibuat.



Kurangi ukuran file dan gunakan CDN


Anda mengontrol dua faktor yang memengaruhi waktu eksekusi kueri: ukuran file sumber daya dan lokasi server.

Kirim sesedikit mungkin data kepada pengguna dan pastikan mereka dikompresi (misalnya, menggunakan brotli atau gzip).

Content Delivery Networks (CDNs) memiliki server di seluruh dunia. Alih-alih terhubung ke server pusat, pengguna dapat terhubung ke server CDN yang lebih dekat. Dengan demikian, pertukaran paket akan jauh lebih cepat. Ini sangat cocok untuk sumber daya statis seperti CSS, JavaScript, dan gambar, karena mudah didistribusikan melalui CDN.

Hilangkan latensi jaringan dengan pekerja layanan


Pekerja Layanan memungkinkan Anda untuk mencegat permintaan sebelum mengirimnya ke jaringan. Ini berarti bahwa jawabannya datang hampir secara instan !



Tentu saja, ini hanya berfungsi jika Anda benar-benar tidak perlu menerima data dari jaringan. Jawabannya harus sudah di-cache, sehingga manfaat hanya akan muncul dari unduhan aplikasi kedua.

Pekerja layanan di bawah ini cache HTML dan CSS yang diperlukan untuk membuat halaman. Ketika aplikasi dimuat lagi, ia mencoba mengeluarkan sumber daya yang di-cache itu sendiri - dan mengakses jaringan hanya jika tidak tersedia.

self.addEventListener("install", async e => {
 caches.open("v1").then(function (cache) {
   return cache.addAll(["/app", "/app.css"]);
 });
});

self.addEventListener("fetch", event => {
 event.respondWith(
   caches.match(event.request).then(cachedResponse => {
     return cachedResponse || fetch(event.request);
   })
 );
});

Dalam panduan ini, dijelaskan secara rinci tentang penggunaan pekerja layanan untuk memuat dan menyimpan sumber daya.

Unduh aplikasi


Jadi, pengguna melihat sesuatu di layar. Apa langkah lebih lanjut yang diperlukan baginya untuk menggunakan aplikasi?

  1. Unduh kode aplikasi (JS dan CSS)
  2. Unduh data yang diperlukan untuk halaman tersebut
  3. Unduh data dan gambar tambahan



Harap dicatat bahwa tidak hanya mengunduh data dari jaringan dapat menunda penguraian. Setelah kode Anda dimuat, peramban harus menganalisis, menyusun, dan menjalankannya.

Unduh hanya kode yang diperlukan dan maksimalkan jumlah klik dalam cache


β€œBreak a package” berarti mengunduh hanya kode yang diperlukan untuk halaman saat ini, bukan keseluruhan aplikasi. Ini juga berarti bahwa bagian-bagian dari paket dapat di-cache, bahkan jika bagian-bagian lain telah berubah dan perlu dimuat ulang.

Sebagai aturan, kode dibagi menjadi bagian-bagian berikut:

  • Kode untuk halaman tertentu (khusus halaman)
  • Kode aplikasi umum
  • Modul pihak ketiga yang jarang berubah (bagus untuk caching!)

Webpack dapat secara otomatis melakukan optimasi ini, memecahkan kode, dan mengurangi bobot beban keseluruhan. Kode dipecah menjadi beberapa bagian menggunakan objek optimisation.splitChunks . Pisahkan runtime (runtime) menjadi file terpisah: dengan cara ini Anda bisa mendapatkan keuntungan dari caching jangka panjang. Ivan Akulov menulis panduan terperinci tentang memecah paket menjadi file-file terpisah dan caching di Webpack .

Tidak mungkin untuk secara otomatis mengalokasikan kode untuk halaman tertentu. Anda harus secara manual mengidentifikasi bagian-bagian yang dapat diunduh secara terpisah. Seringkali ini adalah jalur atau set halaman tertentu. Gunakan impor dinamis untuk dengan malas memuat kode ini.

Membagi paket keseluruhan menjadi beberapa bagian akan meningkatkan jumlah permintaan untuk mengunduh aplikasi Anda. Tapi ini bukan masalah besar jika permintaan dieksekusi secara paralel, terutama jika situs dimuat menggunakan protokol HTTP / 2. Anda bisa melihat ini untuk tiga kueri pertama dalam diagram berikut:



Namun, dua kueri berturut-turut juga terlihat dalam diagram. Fragmen-fragmen ini diperlukan hanya untuk halaman khusus ini dan mereka dimuat secara dinamis import().

Anda dapat mencoba memperbaiki masalah dengan memasukkan tag preload preload .



Tetapi kami melihat bahwa total waktu buka halaman telah meningkat.

Preloading sumber daya terkadang kontraproduktif karena menunda pemuatan file yang lebih penting. BacaArtikel Andy Davis tentang font preloading dan bagaimana prosedur ini memblokir permulaan rendering halaman.

Memuat data untuk satu halaman


Aplikasi Anda mungkin harus menampilkan beberapa data. Berikut adalah beberapa kiat yang dapat Anda gunakan untuk mengunduh data ini lebih awal tanpa penundaan render yang tidak perlu.

Jangan menunggu unduhan penuh paket sebelum Anda mulai mengunduh data


Berikut ini adalah kasus khusus dari rangkaian permintaan berurutan: Anda mengunduh seluruh paket aplikasi, dan kemudian kode ini meminta data yang diperlukan untuk halaman tersebut.

Ada dua cara untuk menghindari ini:

  1. Sematkan data dalam dokumen HTML
  2. Jalankan permintaan data menggunakan skrip bawaan di dalam dokumen

Menanamkan data dalam HTML memastikan bahwa aplikasi tidak menunggu untuk memuat. Ini juga mengurangi kompleksitas sistem, karena Anda tidak perlu menangani status boot.

Namun, ini bukan ide yang baik jika teknik seperti itu menunda rendering awal.

Dalam hal ini, dan juga jika Anda mengirimkan dokumen HTML yang di-cache melalui pekerja layanan, Anda dapat menggunakan skrip bawaan untuk mengunduh data ini sebagai alternatif. Anda dapat menjadikannya tersedia sebagai objek global, inilah janji:

window.userDataPromise = fetch("/me")

Jika data sudah siap, dan dalam situasi seperti itu, aplikasi dapat segera mulai render atau menunggu hingga siap.

Saat menggunakan kedua metode, Anda harus tahu terlebih dahulu data apa yang akan dimuat halaman sebelum aplikasi mulai rendering. Ini biasanya jelas untuk data terkait pengguna (nama pengguna, pemberitahuan, dll.), Tetapi lebih sulit dengan konten yang khusus untuk halaman tertentu. Mungkin masuk akal untuk menyorot halaman yang paling penting dan menulis logika Anda sendiri untuk mereka.

Jangan blokir rendering sambil menunggu data yang tidak relevan


Terkadang, untuk menghasilkan data, Anda perlu menjalankan logika kompleks yang lambat di backend. Dalam kasus seperti itu, Anda dapat mencoba mengunduh versi data yang lebih sederhana terlebih dahulu, jika ini cukup untuk membuat aplikasi berfungsi dan interaktif.

Misalnya, alat analitik pertama-tama dapat mengunduh daftar semua bagan sebelum memuat data. Ini memungkinkan pengguna untuk segera mencari diagram yang menarik baginya, dan juga membantu mendistribusikan permintaan backend ke server yang berbeda.



Hindari Permintaan Data Berturut-turut


Ini mungkin bertentangan dengan paragraf sebelumnya bahwa lebih baik mengeluarkan data yang tidak penting dalam permintaan terpisah. Oleh karena itu, harus diklarifikasi: hindari rantai dengan permintaan data berurutan, jika setiap permintaan yang diselesaikan tidak mengarah pada fakta bahwa pengguna diperlihatkan lebih banyak informasi .

Alih-alih menanyakan terlebih dahulu pengguna mana yang masuk dan kemudian meminta daftar grupnya, segera kembalikan daftar grup bersama dengan informasi pengguna dalam permintaan pertama. Anda dapat menggunakan GraphQL untuk ini , tetapi titik akhir user?includeTeams=truejuga berfungsi dengan baik.

Render sisi server


Rendering sisi server berarti pra-rendering aplikasi, sehingga HTML satu halaman penuh dikembalikan pada permintaan klien. Klien melihat halaman yang sepenuhnya dirender, tanpa menunggu kode atau data tambahan dimuat!

Karena server hanya mengirim HTML statis kepada klien, aplikasi tidak interaktif pada saat ini. Anda perlu mengunduh aplikasi itu sendiri, memulai logika render, kemudian menghubungkan pendengar acara yang diperlukan ke DOM.

Gunakan rendering sisi server jika melihat konten non-interaktif itu berharga dalam dirinya sendiri. Senang juga untuk me-cache HTML yang diberikan di server dan segera mengembalikannya ke semua pengguna tanpa penundaan. Misalnya, rendering sisi server sangat bagus ketika menggunakan Bereaksi untuk menampilkan posting blog.

DIArtikel ini oleh Mikhail Yanashek menjelaskan cara menggabungkan pekerja layanan dan rendering sisi server.

Halaman selanjutnya


Pada titik tertentu, pengguna akan menekan tombol dan pergi ke halaman berikutnya. Dari saat Anda membuka halaman mulai, Anda mengontrol apa yang terjadi di browser, sehingga Anda dapat mempersiapkan interaksi selanjutnya.

Preloading sumber daya


Jika Anda memuat kode yang diperlukan untuk halaman berikutnya, penundaan itu menghilang ketika pengguna memulai navigasi. Gunakan tag prefetch atau webpackPrefetchuntuk impor dinamis:

import(
    /* webpackPrefetch: true, webpackChunkName: "todo-list" */ "./TodoList"
)

Pertimbangkan jenis beban apa yang Anda tempatkan pada pengguna dalam hal lalu lintas dan bandwidth, terutama jika ia terhubung melalui koneksi seluler. Jika seseorang mengunduh versi seluler situs dan mode penyimpanan data aktif, maka masuk akal untuk melakukan preload kurang agresif.

Pertimbangkan secara strategis bagian mana dari aplikasi yang akan dibutuhkan oleh pengguna sebelumnya.

Penggunaan kembali data yang sudah diunduh


Tembolok data secara lokal di aplikasi Anda dan menggunakannya untuk menghindari permintaan di masa mendatang. Jika pengguna beralih dari daftar grupnya ke halaman "Edit grup", Anda dapat melakukan transisi secara instan, menggunakan kembali data yang diunduh sebelumnya tentang grup.

Harap perhatikan bahwa ini tidak akan berfungsi jika objek sering diedit oleh pengguna lain dan data yang diunduh mungkin menjadi usang. Dalam kasus ini, ada opsi untuk terlebih dahulu menampilkan data hanya baca yang ada sambil secara bersamaan menjalankan permintaan untuk data yang diperbarui.

Kesimpulan


Artikel ini mencantumkan sejumlah faktor yang dapat memperlambat halaman Anda pada berbagai tahap proses pemuatan. Alat seperti Chrome DevTools , WebPageTest dan Mercusuar akan membantu Anda mengetahui mana dari faktor-faktor ini mempengaruhi aplikasi Anda.

Dalam praktiknya, jarang optimisasi langsung terjadi ke segala arah. Kita perlu mencari tahu apa yang memiliki dampak terbesar pada pengguna, dan fokus padanya.

Ketika saya sedang menulis artikel, saya menyadari satu hal penting: Saya memiliki keyakinan yang mengakar bahwa banyak permintaan server individu buruk untuk kinerja. Ini adalah kasus di masa lalu, ketika setiap permintaan memerlukan koneksi terpisah, dan browser hanya mengizinkan beberapa koneksi per domain. Tetapi dengan HTTP / 2 dan browser modern ini tidak lagi terjadi.

Ada argumen bagus yang mendukung pemisahan aplikasi menjadi beberapa bagian (dengan mengalikan kueri). Ini memungkinkan Anda untuk mengunduh hanya sumber daya yang diperlukan dan lebih baik menggunakan cache, karena hanya file yang diubah yang harus dimuat ulang.

All Articles