27 June, 2020

#38: Tản mạn chuyện tối ưu hóa web

#38: Tản mạn chuyện tối ưu hóa web
Available in:
 Vietnamese
Reading time: 7 min.
Table of content

    Để tiếp nối bài viết #37: Điểm Performance web – hiểu sao cho đúng? thì ở bài viết này, mình sẽ chia sẻ câu chuyện về một trong những lần mình tối ưu hóa web.

    Trong phạm vi bài viết này, mình chỉ có thể chia sẻ một phần rất, rất nhỏ những gì có thể làm để tối ưu hóa một website. Nhưng thứ mình mong muốn truyền đạt, đó là các bước chi tiết mà mình tìm hiểu vấn đề và sửa nó.

    Website mà mình tối ưu hóa có địa chỉ tại: https://chuyennguyenhue.com . Đây là trang của câu lạc bộ viết báo CNH Spotlight, của trường THPT chuyên Nguyễn Huệ (là trường cấp 3 mà mình từng theo học).

    Website có khá nhiều hình ảnh, nên việc mất thời gian load là không thể tránh khỏi

    Website này lúc đầu không thuộc hệ thống của mình, và mới được gia nhập từ tháng 4 năm nay. Vì thế, nó được cài cùng các tối ưu hóa đã có sẵn trên WP Network của mình, các bạn có thể tham khảo #33: Mình đã tối ưu blog thế nào? để biết thêm về các tối ưu hóa này. Tuy nhiên, các tối ưu này đều vẫn chỉ ở mức cơ bản, và bản thân cái theme không được tối ưu tốt, nên mình vẫn cảm giác web chạy chậm.

    Và trong bài này, có thể các bạn sẽ thắc mắc vì sao mình không dùng số điểm PageSpeed Insight? Vì nó không phản ánh tốc độ thực sự mà người truy cập cảm nhận được. Tham khảo: #37: Điểm Performance web – hiểu sao cho đúng?

    Quan sát chung

    Khoan, đừng vội áp luôn các cách tối ưu hóa mà bạn đã tìm được trên Google! Bước đầu tiên bạn cần xác định xem web của bạn chậm thế nào, và chậm do cái gì đã.

    Như vậy vấn đề to đùng mà mình nhận thấy, đó là mấy ảnh recent post to đùng ở trên đầu trang tốn quá nhiều thời gian mới hiện ra – tận hơn 6 giây với điều kiện mạng cáp quang, còn nếu dùng 3G có khi còn chậm hơn. Trong khi đó, mấy ảnh trên đầu phải “đập” vào mắt người xem trước, tức là nó phải được load trước chứ!

    Phân tích vấn đề

    Tiếp theo, mình dùng webspeedtest.org để xem có những gì được load khi ở web lên. Nó sẽ hiển thị màn hình “waterfall”, để bạn có thể xem chính xác cái gì được load vào lúc nào: bấm vào đây để xem

    Dựa vào waterfall này, mình nhận ra 2 vấn đề:

    Vấn đề thứ nhất là các thumbnail này: vì phải đợi nó load xong thì mới đến document.onload (vạch xanh biển ở mốc 5s), sau đó mấy ảnh recent posts ở đầu trang mới hiện, nên cảm giác mình phải đợi khá lâu.

    Môt loạt ảnh thumbnail được load

    Vậy rõ ràng là hoặc mình phải cho mấy ảnh recent posts load trước, hoặc làm cách nào đẩy onload (vạch xanh biển) tới vị trí sớm hơn.

    Tuy nhiên còn vấn đề thứ 2: cái phần facebook page widget cũng tốn quá nhiều thời gian load, cụ thể là từ lúc thằng facebook sdk.js load đến lúc widget hiện xong là mất gần 4 giây:

    Còn mấy requests ở dưới nữa, các bạn tham khảo file sẽ thấy

    Lý do là vì sau khi facebook sdk.js load xong, nó sẽ quét xem có DOM nào là placeholder cho widget của fb không, rồi lắp iframe, div, … vào DOM đó, xong rồi mới load nội dung thực.

    Giải quyết vấn đề thứ nhất

    Cách hiệu quả nhất để giải quyết vấn đề này đó là làm ảnh của recent posts hiện lên luôn từ đầu, thay vì phải đợi onload. Sau khi xem qua code, mình nhận thấy đáng nhẽ ra nó phải được hiện từ document.ready, thay vì onload. Quái lạ nhỉ?

    Đoạn code để load ảnh này lên là code JS. Vậy cách giải quyết triệt để nhất là viết nó thành HTML luôn. Nhưng giải pháp này ở đây không khả thi, vì không nên sửa trực tiếp vào file theme của wp (và kể cả dùng child theme thì vẫn rất phức tạp)

    Quay lại với đoạn code JS. Hóa ra, vấn đề nằm ở tính năng rocket loader của cloudflare, mà mình bật ngay từ hồi cài domain. Tính năng này nôm na là nó sẽ đợi cho đến khi css / media load xong hết, tức là onload, rồi mới chạy script, mà script mới điều khiển cho recent posts hiện lên. (Và hóa ra đâu phải cứ áp một đống optimizer vào là web sẽ chạy nhanh. Đây là một trường hợp phản tác dụng đấy!)

    Tuy nhiên, nếu tắt rocket loader thì có những script mình không muốn load cùng media, thời gian onload còn bị đẩy ra xa nữa.

    Có 2 giải pháp cho việc này: hoặc là tắt rocket loader đi, rồi dùng attribute defer để bắt một vài script chạy sau khi onload. Hoặc cách mình làm thì ngược lại, vẫn bật rocket loader, nhưng chỉ định nó bỏ qua những script mình muốn chạy trước khi onload. Trong tài liệu chính thức của cloudflare có ghi cách làm:

    Áp dụng vào trong code:

    Sau khi áp dụng cách này, recent posts đã hiện ra vào lúc document.ready thay vì onload, đúng như mong đợi. Nhưng đổi lại, mình mất thêm một chút thời gian mới tới được onload:

    Vì thế mình thực hiện cả phương án 2: đẩy thời gian onload lên sớm hơn. Để làm việc này, hãy nhớ rằng chúng ra phải load 1 đống thumbnails rồi mới tới onload được. Vậy, hay là để đống ảnh thumbnails hiện sau khi onload? Vì đằng nào như vậy cũng không ảnh hưởng quá nhiều đến trải nghiệm người dùng
    => Lazyload là cách hiệu quả nhất

    Ở đây, mình chỉ lazyload cho cỡ ảnh thumbnails vì sợ ảnh hưởng đến SEO (dù về sau mình có tìm hiểu và được biết, lazyload KHÔNG ảnh hưởng đến SEO). Việc thay src của <img> bằng một cái placeholder là để tránh việc layout bị đẩy xuống khi ảnh load (trước khi ảnh load thì browser không biết cỡ của ảnh để mà cách ra).

    Kết quả: Sau khi áp dụng lazyload, thời gian onload giảm từ hơn 5s xuống còn 3.5s

    Giải quyết vấn đề thứ 2

    Bây giờ mình có thể dùng trực tiếp <iframe> luôn, thay vì phải đợi thằng fb sdk.js thêm iframe vào, tốn thêm 1 công đoạn (và nếu bạn chưa biết, thì fb sdk.js còn thêm 1 đống DOM râu ria và một vài thành phần “nghe lén” thông tin trên web bạn nữa). Cách này có thể áp dụng nếu bạn dùng fb like, share, comment, page plugin (xem tại đây), nhưng không thể áp dụng cho messenger plugin.

    Ở đây mình đang dùng facebook page plugin

    Code iframe cho page của mình như sau (tạo trên trang của facebook)

    <iframe
      src="https://www.facebook.com/plugins/page.php?href=https%3A%2F%2Fwww.facebook.com%2Fcnhspotlight&tabs&width=340&height=214&small_header=false&adapt_container_width=true&hide_cover=false&show_facepile=true&appId=127412397882783"
      width="340"
      height="214"
      style="border:none;overflow:hidden"
      scrolling="no"
      frameborder="0"
      allowtransparency="true"
      allow="encrypted-media"
    ></iframe>
    

    Tuy nhiên, sau khi reload web, mình lại gặp một vấn đề khác: Giờ thì nội dung trong iframe được load ngay cùng media, làm chậm quá trình này, và dẫn đến tăng thời gian onload.

    Giải pháp mà mình đưa ra, đó là sẽ thêm iframe này bằng js, sau khi onload. Đoạn code này sẽ được inline trong HTML luôn:

    <div id="nuifbpage"></div>
    <script>jQuery(window).load(function() {
      jQuery('#nuifbpage').html('<iframe ...... ></iframe>');
    });</script>
    

    Kết quả: chỉ mất chưa đầy 3 giây từ lúc onload để nó hiện page facebook:

    Lời kết

    Tối ưu hóa web dù cực nhọc, có khi làm cả ngày cũng chỉ giúp web chạy nhanh hơn chút, nhưng chính quá trình phân tích, tìm giải pháp mới là điều vui nhất. Bản thân mình, sau khi giải quyết 2 vấn đề trên, mình cũng đã cảm thấy bản thân học thêm được nhiều kiến thức mới.

    Cùng xem lại thành quả, website giờ đã load nhanh hơn khoảng 3 đến 5 giây (tức hơn khoảng 20-30%) so với trước:

    Want to receive latest articles from my blog?
    Follow on