Trải nghiệm thực tế với link rel=preconnect, sử dụng Script tùy chỉnh bơm vào WebPageTest

Vài lời của người dịch: Bài viết này minh họa cho một thực tế là không phải bạn cứ preconnect đến tất cả các tài nguyên của bên thứ ba là sẽ giúp trang của bạn tải nhanh hơn hoặc/và hiển thị nhanh hơn cho người dùng. Điều này là một lời cảnh tỉnh cho chúng ta, và preconnect cũng giống như mọi thứ khác, lạm dụng nó không hề tốt (ưu tiên mọi thứ, nghĩa là không có ưu tiên nào hết). Cách phòng tránh điều này là trước hết bạn phải xác định rõ những gì quan trọng với website, kết nối trước đến các tài nguyên quảng cáo chẳng hạn, chỉ là điều nên làm nếu bạn cho là nên như thế, còn nếu không, chắc chắn nó sẽ làm chậm trang ít nhiều. Một lời nhắc nhở khác cũng không kém phần quan trọng, ngay cả khi khá chắc mẩm rồi, thì sự cải tiến hiệu suất, tốc độ phải được chứng minh trên thực tế thông qua các bài kiểm tra chứ không chỉ khẳng định trên lý thuyết thuần túy (giống slogan của trang Kiến càng ghê!). Nói cách khác chúng ta sẽ phải học hỏi thêm về cách kiểm tra, đo đạc tốc độ sao cho đúng (chuyện này cũng chẳng phải dễ như pha mỳ tôm đâu). OK, giờ chúng ta bắt đầu nhé.

Sử dụng kiểu gợi ý tài nguyên preconnect là cách hay để tăng tốc nội dung có nguồn gốc từ bên thứ ba (third-party origins) – nó có chi phí ngầm tương đối thấp (low overhead), dù vậy nó không hoàn toàn miễn phí đâu, và ưu điểm là thường được triển khai được một cách dễ dàng.

Đôi khi nó tạo ra được cải tiến nhỏ, và đôi khi là những cái tiến thực sự ấn tượng!

Các trình duyệt thường chỉ tạo kết nối tới máy chủ gốc ngay trước khi chúng yêu cầu một tài nguyên từ đó, vì thế với các tài nguyên được phát hiện sau này, chẳng hạn như ảnh nền trong CSS, fonts, hoặc các đoạn mã JS bơm vào, hoặc các tài nguyên được xem là có mức độ ưu tiên thấp, như cách Chrome đối xử với ảnh, sự trì hoãn trong việc tạo kết nối trở thành một phần của tuyến hiển thị quan trọng (critical path) cho nguồn tài nguyên.

Preconnect cho phép chúng ta tạo ra kết nối sớm – loại nó khỏi tuyến hiển thị quan trọng cho tài nguyên, cho phép tài nguyên tải về sớm hơn (loaded sooner) và qua đó hy vọng là sẽ có được sự cải thiện tổng thể về khía cạnh hiệu suất, tốc độ của trang.

Triển khai preconnect thường là cải tiến đầu tiên tôi khuyên khách hàng thực hiện, nhưng tôi chưa bao giờ hoàn toàn hài lòng với quá trình xử lý của mình…

Lighthouse cung cấp một số khuyến nghị về các máy chủ gốc cần preconnect, nhưng tôi có xu hướng kết hợp cả WebPageTest và DevTools để xác định ứng cử viên tiềm năng.

Khi tôi đưa ra khuyến nghị cho khách hàng của mình, đội ngũ lập trình viên của họ sẽ triển khai các hướng dẫn preconnect và chúng tôi thường kiểm tra ảnh hưởng trên môi trường tiền sản phầm (pre-production) và thực hiện điều chỉnh thêm nếu thấy cần thiết.

Nếu tôi ngồi ngay bên đội phát triển của họ, quy trình lặp lại này có thể diễn ra nhanh chóng, nhưng nếu khách hàng sử dụng đội ngũ phát triển bên ngoài, hoặc các đội ngũ kiểu offshore (làm việc ở các thiên đường thuế), quy trình này có thể mất nhiều thời gian hơn.

Điều tôi muốn làm là biện pháp nào đấy mà tôi có thể thử nghiệm, đánh giá các tùy chọn và chứng minh được lợi ích trước khi khách hàng cam kết biến các lời khuyên của tôi thành mã trên trang của họ.

Sau đó tôi nhớ ra rằng WebPageTest có khả năng bơm mã tùy biến vào trong trang cần kiểm tra… Tôi có thể tạo một script (JS) để thêm các chỉ thị preconnect và xem ảnh hưởng của các tùy chọn khác nhau lên tốc độ trang.

Bơm mã vào trang

Tại phần cuối của tab Advanced (nâng cao), có một text box (hộp văn bản) được gán nhãn là Inject Script, bất kỳ script nào được đặt ở đây sẽ được bơm vào trang ngay sau khi nó bắt đầu tải.

cài đặt nâng cao

Đoạn mã tôi sử dụng để tạo các phần tử <link rel=precoonect lặp xung quanh một mảng của chuỗi máy chủ gốc, thêm từng phần tử link vào một phần tài liệu, và rồi thêm phần tài liệu đó vào DOM.

(function () {
   var entries = [
       {'href': 'https://res.cloudinary.com'}
   ];

   var fragment = document.createDocumentFragment();
   for(entry of entries) {
       var link = document.createElement('link');
       link.rel = 'preconnect';
       link.href = entry.href;
       if(entry.hasOwnProperty('crossOrigin')) {
               link.crossOrigin = entry.crossOrigin;
       }
       fragment.appendChild(link);
   }
   document.head.appendChild(fragment);
   performance.mark('wpt.injectEnd'); // Not essential 
})();

P/S: đừng lăn tăn nếu bạn không hiểu rõ ràng từng dòng mã, các từ khóa bên trên, tôi cũng không hiểu lắm đâu! Quan trọng bạn biết ý nghĩa của nó dùng để làm gì là được.

Nhiều máy chủ gốc hơn có thể được thêm vào mảng array nếu cần thiết, và nếu một máy chủ gốc phục vụ font thì chúng ta cũng nên thêm thuộc tính crossOrigin: ‘anonymous’ vào.

Lấy ví dụ, nếu cả ảnh và font của bạn được host trên máy chủ của Cloudinary, mục nhập đầu tiên sẽ tạo ra một kết nối tới ảnh, và mục nhập thứ hai tạo ra một kết nối tới font.

 var entries = [
       {'href': 'https://res.cloudinary.com'},
       {'href': 'https://res.cloudinary.com', 'crossOrigin': 'anonymous'}
   ];

Tôi đã đóng gói đoạn script vào trong IIFE để giới hạn phạm vi các biến của nó, và tránh xung đột với bất kỳ cái nào đã tồn tại trên trang.

Khi Pat giới thiệu tính năng script injection, anh đã mô tả nó là ‘a bit racey / một chút chủng tộc’. Bạn không thể đảm bảo chắc chắn khi nào nó được thực thi, vì thế tôi bao gồm thêm đánh dấu hiệu suất để ghi lại thời điểm đoạn mã thực thi xong.

Cũng vậy, khi không có bằng chứng về việc preconnect luôn luôn giúp cải thiện hiệu suất, phần mã đánh dấu là một bảo đảm tốt rằng tôi thực sự đã đưa mã tùy chỉnh preconnect vào trang!

Đưa nó vào hành động

Vậy khác biệt nào preconnect có thể tạo ra?

Tôi sử dụng HTTP Archive để tìm một vài trang sử dụng Cloudinary (dịch vụ CDN chất lượng cao chuyên về media) cho ảnh của họ và kiểm tra khi họ KHÔNG có preconnect và khi preconnect bằng cách bơm đoạn mã JavaScript vào.

Mỗi kiểm tra bao gồm chín lần chạy, sử dụng Chrome để mô phỏng thiết bị di động, và profile của mạng kết nối.

Có một sự cải thiện đáng chú ý trong trang đầu tiên (https://www.digitaladventures.com/), với ảnh nền chỉnh được tải sơm hơn nửa giây (chuỗi hình ở trên) so với trang không có preconnect (chuỗi hình ở dưới).

trang có và không có preconnect
Hình ở trên có precoonect, hình ở dưới không có preconnect

So sánh các bảng waterfall: khoảng cách giữa phần kết thúc trong việc tạo kết nối mạng và phần bắt đầu trong việc yêu cầu ảnh cho thấy preconnect đã khuyến khích Chrome kết nối sớm hơn so với để nó thực hiện theo mặc định.

Và bằng cách loại bỏ thiết lập kết nối mạng từ tuyến hiển thị quan trọng, Chrome có thể bắt đầu tìm nạp các ảnh sớm hơn.

preconnect với cloudinary
Có preconnect với Cloudinary
Không preconnect với Cloudinary
Không preconnect với Cloudinary

Bạn cũng có thể lưu ý đến việc thiết lập kết nối diễn ra sớm hơn trong kiểm tra với preconnect, đây là điều tôi thường thấy và nghi ngờ nó là do tranh chấp mạng thấp hơn vào thời điểm trang bắt đầu tải.

Nếu bạn muốn tự mình kiểm tra kết quả, dưới đây là liên kết đến chế độ xem so sánh, click vào nhãn ở bên trái của filmstrip sẽ đưa bạn đến lần chạy trung bình cho mỗi kiểm tra:

https://www.webpagetest.org/video/compare.php?tests=190807

Với trang thứ hai (https://hydeparkpicturehouse.co.uk/), có rất ít sự khác biệt giữa hai lần kiểm tra – có một sự khác biệt nhỏ gần lúc bắt đầu của filmtrip do phản hồi máy chủ nhanh hơn trong kiểm tra có preconnect (thanh menu ở phía cuối trang xuất hiện nhanh hơn).

kiểm tra preconnect với trang thứ hai
Trang đầu có preconnect, trong khi trang bên dưới thì không

Nhưng về tổng thể preconnect không có bất kỳ ảnh hưởng nào một khi Chrome khám phá ra ảnh, nó ngay lập tức ưu tiên yêu cầu tìm nạp cho nó.

Trong bảng waterfall có preconnect, bạn sẽ thấy Chrome bắt đầu tạo kết nối thậm chí là trước khi đoạn mã bơm vào để thực hiện nhiệm vụ preconnect được thực thi – thanh dọc màu tím cho mục đích đánh dấu thời gian thực tế diễn ra sau tìm kiếm DNS cho Cloudinary.

Có preconnect tới Cloudinary
Có preconnect tới Cloudinary
Không có preconnect tới Cloudinary
Không có preconnect tới Cloudinary

Một lần nữa, đường link bên dưới đây so sánh giữa hai kiểm tra nếu bạn muốn tự khám phá thêm:

https://www.webpagetest.org/video/compare.php?tests=190807_CP

Có nhiều tùy chọn khác để cải thiện hiệu suất cho cả hai trang vừa test và nếu chúng được triển khai, preconnect có thể trở thành quan trọng hơn hoặc bớt quan trọng đi – để phát hiện ra điều đó thì kiểm tra chính là yếu tố chìa khóa.

Sử dụng preconnect một cách có chọn lọc

Đem đến cải thiện đến 0,5 giây trong kiểm tra đầu tiên, bạn có thể bị cám dỗ bởi tuyên bố: “preconnect tất cả mọi thứ, nó quá tốt!”, nhưng tôi phải can bạn lại – ít nhất là cho đến khi thực hiện các kiểm tra kỹ lưỡng.

Các trình duyệt có các giới hạn về số lượng yêu cầu DNS đồng thời mà chúng có thể tạo, và việc tạo một kết nối HTTP mới có thể yêu cầu xác thực để tìm nạp, và các xác thực này có thể cạnh tranh với các tài nguyên khác quan trọng hơn trong việc kết nối mạng.

Thậm chí ngay cả khi không phải chịu yêu cầu TLS ngầm ẩn, việc bổ sung thêm preconnect có thể thực sự làm mọi thứ chậm đi cũng như nó có thể thay đổi thứ tự các nguồn tài nguyên được lấy, cái có thể làm gia tăng cạnh tranh cho mạng hoặc luồng chính (mean thread) của trình duyệt.

Tôi thử chạy lại kiểm tra cho trang https://www.digitaladventures.com/ với việc bổ sung thêm nhiều preconnect hơn:

  var entries = [
       {'href': 'https://res.cloudinary.com'},
       {'href': 'https://connect.facebook.net'},
       {'href': 'https://www.google-analytics.com'},
       {'href': 'https://cdnjs.cloudflare.com'},
       {'href': 'https://client.crisp.chat'},
       {'href': 'https://www.googleadservices.com'},
       {'href': 'https://www.gstatic.com'}
   ];

Và kết quả kiểm tra cho thấy, với nhiều preconnect như vậy (cái ở trên) làm trang chậm đi một chút so với chỉ có một preconnect (hình ở dưới) – trong trường hợp này, qua nhiều thử nghiệm, tôi thấy ảnh nền render chậm hơn 100ms, nhưng tôi đã thấy nhiều ví dụ thậm chí còn tệ hại hơn.

quá nhiều preconnect
Hình ở trên có nhiều preconnect, hình bên dưới chỉ có một preconnect

Một lần nữa, đây là so sánh giữa hai kiểm tra nếu bạn muốn khám phá thêm: https://www.webpagetest.org/video/compare.php?tests=190807_2B

Chúng ta có thể tinh chỉnh danh sách preconnect để phát hiện ra cái nào thực sự giúp trang tải nhanh hơn, còn cái nào sẽ làm nó chậm đi, nhưng tôi sẽ để nó lại như một dạng bài tập nếu bạn muốn thử.

Vài suy nghĩ cuối

Trong bài viết này tôi tập trung vào hai trang sử dụng Cloudinary, nhưng tôi mong chờ thấy kết quả tương tự với bất kỳ trang nào host ảnh của họ thông qua bên thứ ba, chẳng hạn như Imgix, Kraken.io, Cloudfront, vân vân.

Và từ những gì tôi đã thấy từ trước đến nay, tôi nghĩ có nhiều trang host ảnh của họ thông qua dịch vụ của bên thứ ba sẽ thấy hiệu suất được cải thiện nếu trình duyệt tự động thực hiện kết nối sớm hơn. Tôi có thể nói vậy vì tôi vừa hoàn thành phân tích dữ liệu từ 2000 bài kiểm tra tôi thực hiện vào cuối tuần vừa rồi.

Trường hợp tự động thực hiện preconnect cho các nguồn tài nguyên không phải là ảnh có thể có ít nhiều lẫn lỗn hơn – kết xuất các nguồn tài nguyên chặn hiển thị có xu hướng yêu cầu ưu tiên ở mức độ cao do vậy không bị trì hoãn trong việc tạo kết nối, và thậm chí đối với các tài nguyên có mức độ ưu tiên thấp hơn chẳng hạn như các mã JS không đồng bộ (async), tải chúng sớm hơn đồng nghĩa với việc trình duyệt sẽ cố gắng thực thi chúng sớm hơn, mà điều này có thể là lựa chọn đúng hoặc sai.

Preconnect là tính năng hay nhưng do sự phức tạp trong cách trình duyệt ưu tiên tài nguyên, và do thói quen của chúng ta xây dựng trang dựa trên các nguồn tài nguyên được phân tán qua nhiều máy chủ gốc, cho nên không dễ dàng gì để luôn thực hiện chính xác nhiệm vụ này.

Sử dụng tính năng bơm mã trong WebPageTest cho phép tôi thử nghiệm, khám phá sự phức tạp trong việc nên kết nối với máy chủ gốc nào, còn máy chủ gốc nào thì không nên kết nối, và đánh giá được các kết quả trước khi tôi khuyến nghị khách hàng thay đổi mã của họ.

(Dịch từ bài viết: Experimenting With Link Rel=preconnect Using Custom Script Injection in WebPageTest, của tác giả: Andy Davies, trang: andydavies[.]me)

Leave a Comment