Kami telah lama mengabaikan topik browser, CSS, dan aksesibilitas, dan memutuskan untuk kembali ke topik itu dengan terjemahan materi ulasan hari ini (asli - Februari 2020). Saya sangat tertarik dengan pendapat Anda tentang teknologi rendering server yang disebutkan di sini, serta tentang seberapa mendesak kebutuhan akan buku lengkap tentang HTTP / 2 - namun, mari kita bicarakan semuanya secara berurutan.
Posting ini menjelaskan beberapa teknik untuk mempercepat pemuatan aplikasi front-end dan dengan demikian meningkatkan kegunaan.
Mari kita lihat arsitektur umum frontend. Bagaimana Anda memastikan bahwa sumber daya penting dimuat terlebih dahulu dan memaksimalkan kemungkinan sumber daya tersebut akan berakhir di cache?
Saya tidak akan membahas bagaimana backend harus mengirimkan resource, apakah Anda perlu membuat halaman dalam bentuk aplikasi klien sama sekali, atau cara mengoptimalkan waktu rendering aplikasi Anda.
Gambaran
Mari kita uraikan proses pengunduhan aplikasi menjadi tiga langkah terpisah:
- Rendering utama - berapa lama waktu yang dibutuhkan sebelum pengguna melihat sesuatu?
- Download aplikasi - berapa lama waktu yang dibutuhkan sebelum pengguna melihat aplikasi?
- – ?
Hingga tahap rendering utama (rendering), pengguna tidak bisa melihat apapun. Untuk merender halaman, Anda memerlukan setidaknya dokumen HTML, tetapi dalam banyak kasus, Anda juga perlu memuat sumber daya tambahan, seperti file CSS dan JavaScript. Jika tersedia, browser dapat mulai merender ke layar.
Dalam posting ini, saya akan menggunakan diagram air terjun WebPageTest . Rangkaian permintaan untuk situs Anda akan terlihat seperti ini.
Satu set file lain dimuat bersama dengan dokumen HTML, dan halaman dirender setelah semuanya ada di memori. Harap diperhatikan bahwa file CSS dimuat secara paralel, jadi setiap permintaan berikutnya tidak meningkatkan penundaan secara signifikan.
Mengurangi jumlah permintaan pemblokiran render
Stylesheet dan elemen skrip (secara default) tidak mengizinkan konten apa pun di bawahnya untuk ditampilkan.
Ada beberapa cara untuk memperbaikinya:
- Kami menempatkan tag skrip di bagian paling bawah tag
body - Muat skrip secara asinkron menggunakan
async - Tulis potongan kecil inline JS atau CSS jika Anda ingin memuatnya secara sinkron
Hindari merender rantai kueri yang memblokir
Bukan hanya jumlah permintaan pemblokiran render yang dapat memperlambat situs Anda. Yang penting adalah ukuran dari masing-masing sumber daya yang perlu diunduh, serta kapan tepatnya browser mendeteksi bahwa sumber daya perlu diunduh.
Jika browser menyadari kebutuhan untuk mendownload file hanya setelah permintaan lain selesai, Anda mungkin akan mendapatkan rangkaian permintaan sinkron. Ini dapat terjadi karena beberapa alasan:
- Memiliki aturan
@importdi CSS - Menggunakan font web yang dirujuk dalam file CSS
- Tautan injeksi JavaScript atau tag skrip
Pertimbangkan contoh ini:
Salah satu file CSS di situs ini berisi aturan
@importuntuk memuat font Google. Karenanya, browser harus menjalankan permintaan berikut satu per satu, dalam urutan ini:
- Dokumen HTML
- CSS Aplikasi
- CSS Font Google
- File Google Font Woff (tidak ditampilkan dalam kaskade)
Untuk memperbaikinya, pertama-tama kita memindahkan permintaan untuk Google Fonts CSS dari
@importke tag tautan di dokumen HTML. Ini akan mempersingkat rantai dengan satu tautan.
Untuk kecepatan yang lebih tinggi, sematkan file CSS Google Fonts langsung ke HTML atau file CSS Anda.
(Ingat bahwa respons CSS dari Google Fonts bergantung pada agen pengguna. Jika Anda membuat permintaan menggunakan IE8, CSS akan merujuk ke file EOT (disematkan oleh OpenType), IE11 akan menerima file woff, dan browser modern akan menerima woff2. Tetapi jika Anda menyukainya berfungsi seperti browser yang relatif lama menggunakan font sistem, Anda cukup menyalin dan menempelkan konten file CSS.)
Bahkan setelah halaman mulai ditampilkan, pengguna mungkin tidak dapat melakukan apa pun dengannya karena tidak ada teks yang akan ditampilkan hingga font dimuat sepenuhnya. Ini dapat dihindari dengan menggunakan properti font-display swap , yang sekarang menjadi default di Google Fonts.
Terkadang tidak mungkin untuk sepenuhnya menyingkirkan rantai permintaan. Jika demikian, coba gunakan tag
preloadatau preconnect. Misalnya, situs yang ditampilkan di atas mungkin terhubung fonts.googleapis.comsebelum permintaan CSS sebenarnya dibuat.
Menggunakan kembali koneksi server untuk mempercepat permintaan
Biasanya, membuat koneksi server baru membutuhkan 3 perjalanan bolak-balik antara browser dan server:
- Pencarian DNS
- Membuat koneksi TCP
- Membuat koneksi SSL
Setelah koneksi dibuat, setidaknya diperlukan 1 perjalanan pulang pergi: mengirim permintaan dan mengunduh tanggapan.
Seperti yang ditunjukkan dalam kaskade di bawah ini, sambungan dimulai ke empat server berbeda: hostgator.com, optimizely.com, googletagmanager.com, dan googelapis.com.
Namun , permintaan berikutnya ke server yang terpengaruh dapat menggunakan kembali koneksi yang ada. Oleh karena itu,
base.cssatau index1.cssdimuat dengan cepat, karena mereka juga berada di hostgator.com.
Mengurangi ukuran file dan menggunakan jaringan pengiriman konten (CDN)
Dua faktor lain yang Anda kontrol memengaruhi durasi permintaan, bersama dengan ukuran file: ukuran sumber daya dan lokasi server Anda.
Kirimi pengguna jumlah data minimum yang diperlukan, terlebih lagi, jaga kompresinya (misalnya, menggunakan brotli atau gzip).
Jaringan Pengiriman Konten (CDN) menyediakan server di berbagai lokasi, jadi ada kemungkinan besar salah satunya akan berlokasi di dekat pengguna Anda. Anda dapat menghubungkannya bukan ke server aplikasi pusat Anda, tetapi ke server terdekat di CDN. Dengan demikian, jalur data ke dan dari server akan berkurang secara signifikan. Ini sangat berguna saat bekerja dengan sumber daya statis seperti CSS, JavaScript, dan gambar karena mudah didistribusikan.
Memintas jaringan dengan pekerja layanan
Pekerja layanan memungkinkan Anda untuk mencegat permintaan sebelum mereka memasuki jaringan. Jadi, render pertama bisa terjadi hampir seketika !
Tentu saja, ini hanya berfungsi jika Anda ingin jaringan hanya mengirim tanggapan. Respons ini seharusnya sudah di-cache, sehingga membuat hidup lebih mudah bagi pengguna Anda ketika mereka mengunduh ulang aplikasi Anda.
Pekerja layanan yang ditunjukkan di bawah ini menyimpan HTML dan CSS yang diperlukan untuk merender halaman. Saat dimuat ulang, aplikasi mencoba mengeluarkan sumber daya yang disimpan dalam cache, dan jika tidak tersedia, aplikasi akan beralih ke jaringan sebagai cadangan.
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);
})
);
});
Untuk informasi lebih lanjut tentang pramuat dan caching sumber daya menggunakan service worker, lihat tutorial ini .
Download aplikasi
Oke, pengguna kami sudah melihat sesuatu. Apa lagi yang dia butuhkan untuk dapat menggunakan aplikasi kita?
- Memuat aplikasi (JS dan CSS)
- Memuat data terpenting untuk sebuah halaman
- Unduh data dan gambar tambahan
Harap dicatat bahwa tidak hanya memuat data melalui jaringan dapat memperlambat rendering. Saat kode dimuat, browser perlu mengurai, mengkompilasi, dan menjalankannya.
Memisahkan paket: muat hanya kode yang diperlukan dan maksimalkan klik cache.
Dengan membagi bundel, Anda hanya dapat mengunduh kode yang Anda perlukan hanya untuk halaman ini, dan tidak mengunduh seluruh aplikasi. Saat memisahkan bundel, itu dapat di-cache menjadi beberapa bagian, bahkan jika bagian lain dari kode telah berubah dan perlu dimuat ulang.
Biasanya, kode terdiri dari tiga jenis file yang berbeda:
- Kode khusus untuk halaman ini
- Kode aplikasi bersama
- Modul pihak ketiga yang jarang berubah (bagus untuk penyimpanan dalam cache!)
Webpack dapat secara otomatis membagi kode yang diurai untuk mengurangi bobot unduhan secara keseluruhan, ini dilakukan dengan optimization.splitChunks . Pastikan untuk mengaktifkan potongan waktu proses sehingga hash dari potongan tetap stabil dan cache jangka panjang dapat digunakan secara berguna. Ivan Akulov telah menulis panduan komprehensif tentang berbagi dan menyimpan kode Webpack.
Pemisahan kode khusus halaman tidak dapat dilakukan secara otomatis, jadi Anda harus mengidentifikasi cuplikan yang dapat dimuat secara terpisah. Ini sering kali merupakan rute atau kumpulan halaman tertentu. Gunakan impor dinamis untuk memuat kode tersebut secara lambat.
Memisahkan hasil bundel dalam lebih banyak permintaan yang dibuat untuk memuat aplikasi Anda sepenuhnya. Tapi, jika permintaannya diparalelkan, masalah ini tidak besar, terutama di situs yang menggunakan HTTP / 2. Perhatikan tiga kueri pertama dalam kaskade ini:
Namun, kaskade ini juga menunjukkan 2 kueri yang dijalankan secara berurutan. Fragmen ini diperlukan hanya untuk halaman ini, dan dimuat secara dinamis menggunakan panggilan
import().
Ini dapat diperbaiki dengan memasukkan tag
preload linkjika Anda tahu Anda pasti membutuhkan fragmen ini.
Namun, seperti yang Anda lihat, peningkatan kecepatan dalam kasus ini mungkin kecil dibandingkan dengan total waktu buka halaman.
Selain itu, menggunakan pramuat terkadang kontraproduktif dan dapat menyebabkan penundaan saat file lain yang lebih penting dimuat. Lihat posting Andy Davis tentang preloading font dan cara memblokir rendering utama dengan memuat font terlebih dahulu dan kemudian CSS yang mencegah rendering.
Memuat data halaman
Mungkin, aplikasi Anda dirancang untuk menampilkan beberapa jenis data. Berikut adalah beberapa tip tentang cara memuat data sebelumnya dan menghindari penundaan rendering.
Jangan menunggu paket, mulai muat data segera.
Mungkin ada kasus khusus permintaan berurutan yang berantai: Anda memuat bundel aplikasi, dan kode ini sudah meminta data halaman.
Ada dua cara untuk menghindari ini:
- Sematkan data halaman ke dalam dokumen HTML
- Mulai meminta data melalui skrip sebaris di dalam dokumen
Menyematkan data dalam HTML memastikan bahwa aplikasi Anda tidak perlu menunggu untuk memuatnya. Ini juga mengurangi keseluruhan kompleksitas aplikasi dengan tidak harus menangani status pemuatan.
Namun, ide ini tidak begitu baik jika pengambilan data mengakibatkan penundaan yang signifikan dalam respons dokumen Anda, karena akan memperlambat rendering awal juga.
Dalam kasus ini, atau saat menyajikan dokumen HTML yang disimpan dalam cache menggunakan pekerja layanan, Anda dapat menyematkan skrip sebaris di HTML yang akan memuat data ini. Itu bisa diberikan sebagai janji global, seperti ini:
window.userDataPromise = fetch("/me")
Lalu, jika datanya sudah siap, aplikasi Anda bisa langsung mulai rendering, atau tunggu hingga siap.
Saat menggunakan kedua metode ini, Anda perlu tahu persis data apa yang harus ditampilkan di halaman, dan bahkan sebelum aplikasi mulai merender. Ini biasanya mudah untuk menyediakan data khusus pengguna (nama, pemberitahuan ...) tetapi tidak mudah saat menangani konten khusus halaman. Cobalah untuk menyorot sendiri halaman yang paling penting dan tulis logika Anda sendiri untuk masing-masing halaman tersebut.
Jangan memblokir rendering sambil menunggu data yang tidak relevan
Terkadang membuat data berhalaman membutuhkan logika yang lambat dan kompleks yang diterapkan di backend. Dalam kasus tersebut, kemampuan untuk memuat versi data yang disederhanakan terlebih dahulu akan berguna, jika itu cukup untuk membuat aplikasi Anda berfungsi dan interaktif.
Misalnya, alat analisis mungkin memuat semua bagan terlebih dahulu dan kemudian menyertainya dengan data. Dengan demikian, pengguna akan segera dapat melihat diagram yang menarik baginya, dan Anda akan memiliki waktu untuk mendistribusikan permintaan backend ke server yang berbeda.
Hindari Rantai Kueri Data Berurutan
Saran ini mungkin tampak bertentangan dengan poin saya sebelumnya di mana saya berbicara tentang penundaan pemuatan data yang tidak penting ke permintaan kedua. Namun, hindari permintaan berantai berantai jika permintaan berikutnya dalam rantai tidak memberikan informasi baru apa pun kepada pengguna.
Alih-alih menanyakan terlebih dahulu pengguna apa yang masuk dan kemudian menanyakan daftar grup tempat pengguna berada, kembalikan daftar grup bersama dengan informasi tentang pengguna. Anda dapat menggunakan GraphQL untuk ini , tetapi titik akhir khusus juga
user?includeTeams=truebaik - baik saja.
Rendering sisi server
Dalam hal ini, yang kami maksud adalah rendering aplikasi di server, sehingga halaman HTML yang lengkap disajikan sebagai respons atas permintaan dari dokumen. Dengan demikian, klien dapat melihat seluruh halaman tanpa menunggu kode atau data tambahan dimuat!
Karena server hanya mengirim HTML statis ke klien, aplikasi Anda masih tanpa interaktivitas pada tahap ini. Aplikasi perlu dimuat, harus menjalankan ulang logika rendering, lalu melampirkan event listener yang diperlukan ke DOM.
Gunakan perenderan sisi server jika Anda merasa bahwa konten non-interaktif itu sendiri berharga. Selain itu, pendekatan ini membantu men-cache HTML yang ditampilkan di sana di server, dan kemudian mentransfernya ke semua pengguna tanpa penundaan saat dokumen pertama kali diminta. Misalnya, rendering sisi server bagus jika Anda merender blog menggunakan React.
Baca artikel ini oleh Michal Janaszek; ini menjelaskan dengan baik cara menggabungkan pekerja layanan dengan rendering sisi server.
Halaman selanjutnya
Pada titik tertentu, pengguna yang bekerja dengan aplikasi Anda harus membuka halaman berikutnya. Saat halaman pertama terbuka, Anda mengontrol semua yang terjadi di browser, sehingga Anda dapat mempersiapkan interaksi berikutnya.
Mengambil Sumber Daya Sebelumnya
Mengambil kode yang diperlukan untuk menampilkan halaman berikutnya dapat membantu menghindari penundaan dalam navigasi khusus. Gunakan tag
prefetch linkatau webpackPrefetchuntuk impor dinamis:
import(
/* webpackPrefetch: true, webpackChunkName: "todo-list" */ "./TodoList"
)
Pertimbangkan berapa banyak data pengguna yang Anda gunakan dan berapa bandwidth-nya, terutama untuk koneksi seluler. Dalam versi seluler situs Anda tidak dapat menggunakan pramuat, dan juga jika mode hemat data diaktifkan.
Pilih secara strategis data yang paling dibutuhkan pengguna Anda.
Gunakan kembali data yang sudah dimuat
Secara lokal menyimpan data Ajax di aplikasi Anda untuk menghindari permintaan yang tidak perlu nanti. Jika pengguna menavigasi ke daftar grup pada halaman Edit Grup, transisi dapat dilakukan secara instan dengan menggunakan kembali data yang telah dipilih sebelumnya.
Harap dicatat bahwa ini tidak akan berfungsi jika objek Anda sering diedit oleh pengguna lain dan data yang Anda unggah dapat dengan cepat menjadi usang. Jika demikian, coba tunjukkan data yang ada dalam mode hanya baca terlebih dahulu, dan sementara itu pilih data yang diperbarui.
Kesimpulan
Dalam artikel ini, kami melihat sejumlah faktor yang dapat memperlambat halaman di berbagai titik dalam proses pemuatan. Gunakan alat seperti Chrome DevTools , WebPageTest, dan Mercusuar untuk menentukan kiat relevan dengan aplikasi Anda.
Dalam praktiknya, pengoptimalan yang komprehensif jarang dapat dilakukan. Tentukan apa yang paling penting bagi pengguna Anda dan fokuslah pada itu.
Saat saya mengerjakan artikel ini, saya menyadari bahwa saya memiliki keyakinan yang tertanam kuat bahwa banyak kueri adalah masalah kinerja yang buruk. Ini adalah kasus sebelumnya, ketika setiap permintaan memerlukan koneksi terpisah dan browser hanya mengizinkan beberapa koneksi per domain. Tetapi masalah ini menghilang dengan munculnya HTTP / 2 dan browser modern.
Ada argumen yang kuat untuk memisahkan kueri. Dengan melakukan ini, Anda dapat memuat sumber daya yang benar-benar diperlukan dan memanfaatkan konten yang disimpan dalam cache dengan lebih baik, karena Anda hanya perlu memuat ulang file yang telah berubah.