Không chống lại trình duyệt tải trước trình quét

Tìm hiểu về trình quét tải trước của trình duyệt, cách trình quét này giúp cải thiện hiệu suất và cách bạn có thể tránh bị ảnh hưởng bởi trình quét này.

Một khía cạnh bị bỏ qua trong việc tối ưu hoá tốc độ trang là tìm hiểu một chút về nội bộ trình duyệt. Trình duyệt thực hiện một số hoạt động tối ưu hoá nhất định để cải thiện hiệu suất theo những cách mà chúng ta không thể thực hiện được với tư cách là nhà phát triển – nhưng chỉ khi những hoạt động tối ưu hoá đó không bị cản trở một cách vô tình.

Một điểm tối ưu hoá trình duyệt nội bộ mà bạn cần hiểu là trình quét tải trước của trình duyệt. Bài đăng này sẽ đề cập đến cách hoạt động của trình quét tải trước – và quan trọng hơn là cách bạn có thể tránh bị ảnh hưởng bởi trình quét này.

Trình quét tải trước là gì?

Mọi trình duyệt đều có một trình phân tích cú pháp HTML chính mã hoá mã đánh dấu thô và xử lý mã này thành một mô hình đối tượng. Quá trình này diễn ra cho đến khi trình phân tích cú pháp tạm dừng khi tìm thấy một tài nguyên chặn, chẳng hạn như biểu định kiểu được tải bằng phần tử <link> hoặc tập lệnh được tải bằng phần tử <script> mà không có thuộc tính async hoặc defer.

Sơ đồ trình phân tích cú pháp HTML.
Hình 1: Sơ đồ về cách trình phân tích cú pháp HTML chính của trình duyệt có thể bị chặn. Trong trường hợp này, trình phân tích cú pháp gặp phải một phần tử <link> cho một tệp CSS bên ngoài, ngăn trình duyệt phân tích phần còn lại của tài liệu – hoặc thậm chí hiển thị bất kỳ phần nào của tài liệu – cho đến khi CSS được tải xuống và phân tích cú pháp.

Trong trường hợp tệp CSS, quá trình hiển thị sẽ bị chặn để ngăn chặn hiện tượng nội dung chưa được tạo kiểu xuất hiện chớp nhoáng (FOUC). Đây là hiện tượng phiên bản chưa được tạo kiểu của một trang có thể xuất hiện trong thời gian ngắn trước khi các kiểu được áp dụng cho trang đó.

Trang chủ web.dev ở trạng thái chưa được tạo kiểu (bên trái) và trạng thái đã được tạo kiểu (bên phải).
Hình 2: Ví dụ mô phỏng về FOUC. Ở bên trái là trang chủ của web.dev không có kiểu. Ở bên phải là trang tương tự nhưng đã áp dụng kiểu. Trạng thái chưa được tạo kiểu có thể xuất hiện trong chớp nhoáng nếu trình duyệt không chặn quá trình kết xuất trong khi một biểu định kiểu đang được tải xuống và xử lý.

Trình duyệt cũng chặn việc phân tích cú pháp và kết xuất trang khi gặp các phần tử <script> mà không có thuộc tính defer hoặc async.

Lý do là vì trình duyệt không thể biết chắc chắn liệu một tập lệnh nhất định có sửa đổi DOM hay không trong khi trình phân tích cú pháp HTML chính vẫn đang thực hiện công việc của mình. Đó là lý do tại sao việc tải JavaScript ở cuối tài liệu là một phương pháp phổ biến để giảm thiểu ảnh hưởng của việc phân tích cú pháp và hiển thị bị chặn.

Đây là những lý do chính đáng để trình duyệt nên chặn cả việc phân tích cú pháp và hiển thị. Tuy nhiên, việc chặn một trong hai bước quan trọng này là không nên, vì chúng có thể làm chậm quá trình phát hiện các tài nguyên quan trọng khác, khiến chương trình bị trì hoãn. Rất may là các trình duyệt đã cố gắng hết sức để giảm thiểu những vấn đề này bằng cách sử dụng một trình phân tích cú pháp HTML phụ có tên là trình quét tải trước.

Sơ đồ của cả trình phân tích cú pháp HTML chính (bên trái) và trình quét tải trước (bên phải), đây là trình phân tích cú pháp HTML phụ.
Hình 3: Sơ đồ mô tả cách trình quét tải trước hoạt động song song với trình phân tích cú pháp HTML chính để tải tài sản một cách suy đoán. Ở đây, trình phân tích cú pháp HTML chính bị chặn khi tải và xử lý CSS trước khi có thể bắt đầu xử lý mã đánh dấu hình ảnh trong phần tử <body>, nhưng trình quét tải trước có thể xem trước trong mã đánh dấu thô để tìm tài nguyên hình ảnh đó và bắt đầu tải tài nguyên đó trước khi trình phân tích cú pháp HTML chính được bỏ chặn.

Vai trò của trình quét tải trước là mang tính suy đoán, tức là trình quét này sẽ kiểm tra mã đánh dấu thô để tìm các tài nguyên cần tìm nạp một cách có cơ hội trước khi trình phân tích cú pháp HTML chính phát hiện ra chúng.

Cách nhận biết khi trình quét tải trước đang hoạt động

Trình quét tải trước tồn tại quá trình kết xuất và phân tích cú pháp bị chặn. Nếu không có hai vấn đề về hiệu suất này, thì trình quét tải trước sẽ không hữu ích lắm. Chìa khoá để xác định xem một trang web có được hưởng lợi từ trình quét tải trước hay không phụ thuộc vào những hiện tượng chặn này. Để làm việc đó, bạn có thể tạo một độ trễ giả tạo cho các yêu cầu để tìm hiểu vị trí hoạt động của trình quét tải trước.

Hãy lấy ví dụ về trang này gồm văn bản và hình ảnh cơ bản có biểu định kiểu. Vì các tệp CSS chặn cả quá trình kết xuất và phân tích cú pháp, nên bạn sẽ tạo ra một độ trễ giả tạo là 2 giây cho biểu định kiểu thông qua một dịch vụ proxy. Độ trễ này giúp bạn dễ dàng thấy được vị trí hoạt động của trình quét tải trước trong thác nước mạng.

Biểu đồ thác nước mạng của WebPageTest minh hoạ độ trễ nhân tạo là 2 giây được áp dụng cho biểu định kiểu.
Hình 4: WebPageTest WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. Mặc dù biểu định kiểu bị trì hoãn một cách giả tạo thông qua một proxy trong 2 giây trước khi bắt đầu tải, nhưng trình quét tải trước sẽ phát hiện ra hình ảnh nằm ở phần sau trong tải trọng đánh dấu.

Như bạn có thể thấy trong biểu đồ thác nước, trình quét tải trước sẽ phát hiện ra phần tử <img> ngay cả khi quá trình hiển thị và phân tích cú pháp tài liệu bị chặn. Nếu không có hoạt động tối ưu hoá này, trình duyệt sẽ không thể tìm nạp mọi thứ một cách tuỳ ý trong khoảng thời gian chặn và nhiều yêu cầu về tài nguyên sẽ liên tiếp thay vì đồng thời.

Sau khi xem xét ví dụ đơn giản đó, hãy cùng xem xét một số mẫu hình thực tế mà trình quét tải trước có thể bị đánh bại và những việc có thể làm để khắc phục các mẫu hình đó.

Tập lệnh async được chèn

Giả sử bạn có HTML trong <head> bao gồm một số JavaScript nội tuyến như sau:

<script>
  const scriptEl = document.createElement('script');
  scriptEl.src = '/yall.min.js';

  document.head.appendChild(scriptEl);
</script>

Theo mặc định, các tập lệnh được chèn sẽ là async. Vì vậy, khi tập lệnh này được chèn, nó sẽ hoạt động như thể thuộc tính async đã được áp dụng cho tập lệnh đó. Điều đó có nghĩa là nó sẽ chạy sớm nhất có thể và không chặn quá trình hiển thị. Nghe có vẻ tối ưu phải không? Tuy nhiên, nếu giả định rằng <script> nội tuyến này xuất hiện sau một phần tử <link> tải một tệp CSS bên ngoài, bạn sẽ nhận được kết quả không tối ưu:

Biểu đồ WebPageTest này cho thấy quá trình quét tải trước bị thất bại khi một tập lệnh được chèn.
Hình 5: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. Trang này chứa một biểu định kiểu duy nhất và một tập lệnh async được chèn. Trình quét tải trước không thể phát hiện tập lệnh trong giai đoạn chặn hiển thị vì tập lệnh được chèn vào máy khách.

Hãy cùng phân tích những gì đã xảy ra ở đây:

  1. Ở giây thứ 0, tài liệu chính được yêu cầu.
  2. Ở 1,4 giây, byte đầu tiên của yêu cầu điều hướng sẽ đến.
  3. Ở giây thứ 2, CSS và hình ảnh được yêu cầu.
  4. Vì trình phân tích cú pháp bị chặn tải biểu định kiểu và JavaScript nội tuyến chèn tập lệnh async xuất hiện sau biểu định kiểu đó ở 2,6 giây, nên chức năng mà tập lệnh cung cấp không có sẵn ngay khi có thể.

Điều này là không tối ưu vì yêu cầu về tập lệnh chỉ xảy ra sau khi biểu định kiểu hoàn tất quá trình tải xuống. Thao tác này sẽ trì hoãn việc chạy tập lệnh sớm nhất có thể. Ngược lại, vì phần tử <img> có thể phát hiện trong mã đánh dấu do máy chủ cung cấp, nên trình quét tải trước sẽ phát hiện phần tử này.

Vậy điều gì sẽ xảy ra nếu bạn sử dụng thẻ <script> thông thường có thuộc tính async thay vì chèn tập lệnh vào DOM?

<script src="/yall.min.js" async></script>

Đây là kết quả:

Thác mạng WebPageTest mô tả cách trình quét tải trước của trình duyệt vẫn có thể phát hiện ra một tập lệnh không đồng bộ được tải bằng cách sử dụng phần tử tập lệnh HTML, mặc dù trình phân tích cú pháp HTML chính của trình duyệt bị chặn trong khi tải xuống và xử lý một biểu định kiểu.
Hình 6: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. Trang này chứa một biểu định kiểu và một phần tử async <script>. Trình quét tải trước sẽ phát hiện tập lệnh trong giai đoạn chặn hiển thị và tải tập lệnh đó đồng thời với CSS.

Có thể bạn sẽ muốn đề xuất rằng những vấn đề này có thể được khắc phục bằng cách sử dụng rel=preload. Cách này chắc chắn sẽ hiệu quả, nhưng có thể gây ra một số tác dụng phụ. Sau cùng, tại sao lại dùng rel=preload để khắc phục một vấn đề có thể tránh được bằng cách không chèn phần tử <script> vào DOM?

Một thác nước WebPageTest cho thấy cách sử dụng gợi ý tài nguyên rel=preload để thúc đẩy việc khám phá một tập lệnh được chèn không đồng bộ – mặc dù theo cách có thể gây ra các tác dụng phụ không mong muốn.
Hình 7: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. Trang này chứa một biểu định kiểu duy nhất và một tập lệnh async được chèn, nhưng tập lệnh async được tải trước để đảm bảo tập lệnh này được phát hiện sớm hơn.

Việc tải trước sẽ "khắc phục" vấn đề ở đây, nhưng lại gây ra một vấn đề mới: tập lệnh async trong hai bản minh hoạ đầu tiên (mặc dù được tải trong <head>) được tải ở mức độ ưu tiên "Thấp", trong khi biểu định kiểu được tải ở mức độ ưu tiên "Cao nhất". Trong bản minh hoạ cuối cùng, khi tập lệnh async được tải trước, biểu định kiểu vẫn được tải ở mức độ ưu tiên "Cao nhất", nhưng mức độ ưu tiên của tập lệnh đã được nâng lên "Cao".

Khi mức độ ưu tiên của một tài nguyên tăng lên, trình duyệt sẽ phân bổ nhiều băng thông hơn cho tài nguyên đó. Điều này có nghĩa là mặc dù biểu định kiểu có mức độ ưu tiên cao nhất, nhưng mức độ ưu tiên được nâng cao của tập lệnh có thể gây ra tình trạng tranh chấp băng thông. Đó có thể là một yếu tố ảnh hưởng đến các kết nối chậm hoặc trong trường hợp tài nguyên có kích thước khá lớn.

Câu trả lời ở đây rất đơn giản: nếu cần một tập lệnh trong quá trình khởi động, đừng đánh bại trình quét tải trước bằng cách chèn tập lệnh đó vào DOM. Thử nghiệm khi cần với vị trí đặt phần tử <script>, cũng như với các thuộc tính như deferasync.

Tải từng phần bằng JavaScript

Tải từng phần là một phương pháp tuyệt vời để tiết kiệm dữ liệu, thường được áp dụng cho hình ảnh. Tuy nhiên, đôi khi tính năng tải từng phần được áp dụng không chính xác cho những hình ảnh "trong màn hình đầu tiên", có thể nói như vậy.

Điều này gây ra các vấn đề tiềm ẩn về khả năng phát hiện tài nguyên khi liên quan đến trình quét tải trước và có thể trì hoãn không cần thiết thời gian cần thiết để phát hiện một tham chiếu đến hình ảnh, tải hình ảnh xuống, giải mã và trình bày hình ảnh đó. Hãy xem xét ví dụ về mã đánh dấu hình ảnh này:

<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">

Việc sử dụng tiền tố data- là một mẫu hình phổ biến trong các trình tải lười được hỗ trợ bằng JavaScript. Khi hình ảnh được cuộn vào khung hiển thị, trình tải lười sẽ loại bỏ tiền tố data-, tức là trong ví dụ trước, data-src sẽ trở thành src. Bản cập nhật này nhắc trình duyệt tìm nạp tài nguyên.

Mẫu này không gây ra vấn đề cho đến khi được áp dụng cho những hình ảnh nằm trong khung hiển thị trong quá trình khởi động. Vì trình quét tải trước không đọc thuộc tính data-src theo cách tương tự như thuộc tính src (hoặc srcset), nên tham chiếu hình ảnh không được phát hiện sớm. Tệ hơn nữa, hình ảnh sẽ bị trì hoãn tải cho đến sau khi JavaScript của trình tải từng phần tải xuống, biên dịch và thực thi.

Biểu đồ thác nước mạng WebPageTest cho thấy hình ảnh được tải từng phần nằm trong khung hiển thị trong quá trình khởi động chắc chắn sẽ bị trì hoãn vì trình quét tải trước của trình duyệt không tìm thấy tài nguyên hình ảnh và chỉ tải khi JavaScript cần thiết để tải từng phần hoạt động. Hình ảnh được phát hiện muộn hơn nhiều so với thời điểm đáng lẽ phải được phát hiện.
Hình 8: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. Tài nguyên hình ảnh được tải chậm một cách không cần thiết, mặc dù tài nguyên này xuất hiện trong khung nhìn trong quá trình khởi động. Điều này sẽ vô hiệu hoá trình quét tải trước và gây ra sự chậm trễ không cần thiết.

Tuỳ thuộc vào kích thước của hình ảnh (có thể phụ thuộc vào kích thước của khung nhìn), hình ảnh đó có thể là một phần tử đề xuất cho Nội dung lớn nhất hiển thị (LCP). Khi trình quét tải trước không thể tìm nạp tài nguyên hình ảnh một cách suy đoán trước thời hạn (có thể là trong thời điểm (các) biểu định kiểu của trang chặn quá trình kết xuất), LCP sẽ bị ảnh hưởng.

Giải pháp là thay đổi mã đánh dấu hình ảnh:

<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">

Đây là mẫu tối ưu cho những hình ảnh nằm trong khung hiển thị trong quá trình khởi động, vì trình quét tải trước sẽ phát hiện và tìm nạp tài nguyên hình ảnh nhanh hơn.

Biểu đồ thác nước mạng WebPageTest mô tả một tình huống tải cho hình ảnh trong khung hiển thị trong quá trình khởi động. Hình ảnh không được tải từng phần, tức là hình ảnh không phụ thuộc vào tập lệnh để tải, nghĩa là trình quét tải trước có thể phát hiện hình ảnh đó sớm hơn.
Hình 9: Biểu đồ thác nước mạng của WebPageTest về một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. Trình quét tải trước sẽ phát hiện tài nguyên hình ảnh trước khi CSS và JavaScript bắt đầu tải, giúp trình duyệt có lợi thế ban đầu khi tải tài nguyên đó.

Kết quả trong ví dụ đơn giản này là LCP được cải thiện 100 mili giây trên một kết nối chậm. Có thể bạn thấy đây không phải là một điểm cải tiến lớn, nhưng thực tế là vậy khi bạn xem xét rằng giải pháp này là một bản sửa lỗi đánh dấu nhanh và hầu hết các trang web đều phức tạp hơn bộ ví dụ này. Điều đó có nghĩa là các thành phần LCP có thể phải cạnh tranh băng thông với nhiều tài nguyên khác, vì vậy, những hoạt động tối ưu hoá như thế này ngày càng trở nên quan trọng.

Hình nền CSS

Hãy nhớ rằng trình quét tải trước của trình duyệt sẽ quét phần đánh dấu. Công cụ này không quét các loại tài nguyên khác, chẳng hạn như CSS. CSS có thể liên quan đến việc tìm nạp hình ảnh mà thuộc tính background-image tham chiếu.

Giống như HTML, các trình duyệt xử lý CSS thành mô hình đối tượng riêng, được gọi là CSSOM. Nếu các tài nguyên bên ngoài được phát hiện khi CSSOM được tạo, thì những tài nguyên đó sẽ được yêu cầu tại thời điểm phát hiện chứ không phải do trình quét tải trước.

Giả sử LCP candidate (đề xuất LCP) của trang là một phần tử có thuộc tính background-image CSS. Sau đây là những gì xảy ra khi tài nguyên tải:

Biểu đồ thác nước mạng của WebPageTest mô tả một trang có ứng cử viên LCP được tải từ CSS bằng cách sử dụng thuộc tính background-image. Vì hình ảnh đề xuất LCP thuộc một loại tài nguyên mà trình quét tải trước của trình duyệt không thể kiểm tra, nên tài nguyên này bị trì hoãn tải cho đến khi CSS được tải xuống và xử lý, làm chậm thời gian hiển thị của đề xuất LCP.
Hình 10: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. LCP đề xuất của trang là một phần tử có thuộc tính background-image CSS (hàng 3). Hình ảnh mà thành phần này yêu cầu sẽ không bắt đầu tìm nạp cho đến khi trình phân tích cú pháp CSS tìm thấy hình ảnh đó.

Trong trường hợp này, trình quét tải trước không bị đánh bại mà chỉ không liên quan. Mặc dù vậy, nếu một ứng cử viên LCP trên trang đến từ thuộc tính CSS background-image, bạn sẽ muốn tải trước hình ảnh đó:

<!-- Make sure this is in the <head> below any
     stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">

Gợi ý rel=preload có kích thước nhỏ, nhưng giúp trình duyệt phát hiện hình ảnh sớm hơn:

Biểu đồ thác nước mạng của WebPageTest cho thấy hình nền CSS (là ứng cử viên LCP) tải nhanh hơn nhiều do sử dụng gợi ý rel=preload. Thời gian hiển thị LCP được cải thiện khoảng 250 mili giây.
Hình 11: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. LCP đề xuất của trang là một phần tử có thuộc tính background-image CSS (hàng 3). Gợi ý rel=preload giúp trình duyệt phát hiện hình ảnh sớm hơn khoảng 250 mili giây so với khi không có gợi ý này.

Với gợi ý rel=preload, ứng cử viên LCP sẽ được phát hiện sớm hơn, giúp giảm thời gian LCP. Mặc dù gợi ý đó giúp khắc phục vấn đề này, nhưng lựa chọn tốt hơn có thể là đánh giá xem ứng cử viên LCP hình ảnh của bạn có phải được tải từ CSS hay không. Với thẻ <img>, bạn sẽ có nhiều quyền kiểm soát hơn đối với việc tải một hình ảnh phù hợp với khung hiển thị trong khi vẫn cho phép trình quét tải trước phát hiện hình ảnh đó.

Nội tuyến quá nhiều tài nguyên

Nội tuyến là một phương pháp đặt tài nguyên bên trong HTML. Bạn có thể chèn biểu định kiểu cùng dòng trong các phần tử <style>, tập lệnh trong các phần tử <script> và hầu như mọi tài nguyên khác bằng cách sử dụng phương thức mã hoá base64.

Việc nội tuyến tài nguyên có thể nhanh hơn so với việc tải tài nguyên xuống vì không có yêu cầu riêng nào được đưa ra cho tài nguyên. Nội dung này nằm ngay trong tài liệu và tải ngay lập tức. Tuy nhiên, có những hạn chế đáng kể:

  • Nếu bạn không lưu vào bộ nhớ đệm HTML (và bạn không thể lưu vào bộ nhớ đệm nếu phản hồi HTML là động), thì các tài nguyên nội tuyến sẽ không bao giờ được lưu vào bộ nhớ đệm. Điều này ảnh hưởng đến hiệu suất vì các tài nguyên nội tuyến không thể dùng lại.
  • Ngay cả khi bạn có thể lưu HTML vào bộ nhớ đệm, các tài nguyên nội tuyến sẽ không được chia sẻ giữa các tài liệu. Điều này làm giảm hiệu quả lưu vào bộ nhớ đệm so với các tệp bên ngoài có thể được lưu vào bộ nhớ đệm và dùng lại trên toàn bộ nguồn gốc.
  • Nếu bạn nội tuyến quá nhiều, bạn sẽ trì hoãn việc trình quét tải trước phát hiện các tài nguyên sau này trong tài liệu, vì việc tải nội dung bổ sung, nội tuyến đó xuống sẽ mất nhiều thời gian hơn.

Hãy lấy trang này làm ví dụ. Trong một số điều kiện, LCP đề xuất là hình ảnh ở đầu trang và CSS nằm trong một tệp riêng biệt do phần tử <link> tải. Trang này cũng sử dụng 4 phông chữ trên web được yêu cầu dưới dạng các tệp riêng biệt từ tài nguyên CSS.

Biểu đồ thác nước mạng WebPageTest của trang có một tệp CSS bên ngoài có 4 phông chữ được tham chiếu trong đó. Trình quét tải trước sẽ phát hiện hình ảnh LCP đề xuất trong thời gian thích hợp.
Hình 12: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. LCP đề xuất của trang là một hình ảnh được tải từ phần tử <img>, nhưng được trình quét tải trước phát hiện vì CSS và phông chữ cần thiết để tải trang trong các tài nguyên riêng biệt, điều này không làm chậm quá trình hoạt động của trình quét tải trước.

Điều gì sẽ xảy ra nếu CSS tất cả các phông chữ đều được nội tuyến dưới dạng tài nguyên base64?

Biểu đồ thác nước mạng WebPageTest của trang có một tệp CSS bên ngoài có 4 phông chữ được tham chiếu trong đó. Trình quét tải trước bị trì hoãn đáng kể trong việc phát hiện hình ảnh LCP .
Hình 13: Biểu đồ thác nước mạng WebPageTest của một trang web chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. LCP của trang là một hình ảnh được tải từ phần tử <img>, nhưng việc nội tuyến CSS và 4 tài nguyên phông chữ của CSS trong "" khiến trình quét tải trước không phát hiện được hình ảnh cho đến khi các tài nguyên đó được tải xuống hoàn toàn.

Việc nội tuyến mang lại hậu quả tiêu cực cho LCP trong ví dụ này và cho hiệu suất nói chung. Phiên bản của trang không nội tuyến bất kỳ nội dung nào sẽ hiển thị hình ảnh LCP trong khoảng 3,5 giây. Trang nội tuyến mọi thứ không hiển thị hình ảnh LCP cho đến khi hơn 7 giây.

Ở đây, không chỉ có trình quét tải trước. Việc chèn phông chữ không phải là một chiến lược hay vì base64 là một định dạng không hiệu quả đối với các tài nguyên nhị phân. Một yếu tố khác cần xem xét là các tài nguyên phông chữ bên ngoài sẽ không được tải xuống trừ phi CSSOM xác định rằng chúng là cần thiết. Khi các phông chữ đó được nội tuyến dưới dạng base64, chúng sẽ được tải xuống cho dù có cần thiết cho trang hiện tại hay không.

Liệu việc tải trước có thể cải thiện tình trạng này không? Chắc chắn rồi. Bạn có thể tải trước hình ảnh LCP và giảm thời gian LCP, nhưng việc tăng kích thước HTML có thể không lưu vào bộ nhớ đệm bằng các tài nguyên nội tuyến sẽ gây ra những hậu quả tiêu cực khác về hiệu suất. Thời gian hiển thị nội dung đầu tiên (FCP) cũng bị ảnh hưởng bởi mẫu này. Trong phiên bản của trang mà không có gì được nội tuyến, FCP là khoảng 2,7 giây. Trong phiên bản mà mọi thứ đều được nội tuyến, FCP là khoảng 5,8 giây.

Hãy hết sức cẩn thận khi đưa nội dung vào HTML, đặc biệt là các tài nguyên được mã hoá base64. Nhìn chung, bạn không nên dùng phương án này, trừ phi tài nguyên có kích thước rất nhỏ. Hạn chế tối đa việc nội tuyến, vì nội tuyến quá nhiều sẽ gây ra rủi ro.

Kết xuất mã đánh dấu bằng JavaScript phía máy khách

Không còn nghi ngờ gì nữa: JavaScript chắc chắn ảnh hưởng đến tốc độ trang. Không chỉ phụ thuộc vào nó để cung cấp tính tương tác, mà còn có xu hướng dựa vào nó để phân phối chính nội dung. Điều này giúp cải thiện trải nghiệm của nhà phát triển theo một số cách; nhưng lợi ích cho nhà phát triển không phải lúc nào cũng mang lại lợi ích cho người dùng.

Một mẫu có thể đánh bại trình quét tải trước là kết xuất mã đánh dấu bằng JavaScript phía máy khách:

Thác mạng WebPageTest cho thấy một trang cơ bản có hình ảnh và văn bản được hiển thị hoàn toàn trên ứng dụng bằng JavaScript. Vì mã đánh dấu nằm trong JavaScript, nên trình quét tải trước không thể phát hiện bất kỳ tài nguyên nào. Tất cả tài nguyên đều bị trì hoãn thêm do thời gian xử lý và mạng bổ sung mà các khung JavaScript yêu cầu.
Hình 14: Biểu đồ thác nước mạng WebPageTest của một trang web được hiển thị phía máy khách chạy trên Chrome trên thiết bị di động qua kết nối 3G mô phỏng. Vì nội dung nằm trong JavaScript và dựa vào một khung để kết xuất, nên tài nguyên hình ảnh trong mã đánh dấu do máy khách kết xuất sẽ bị ẩn khỏi trình quét tải trước. Trải nghiệm tương đương được kết xuất phía máy chủ được minh hoạ trong Hình 9.

Khi tải trọng đánh dấu được chứa và hiển thị hoàn toàn bằng JavaScript trong trình duyệt, mọi tài nguyên trong mã đánh dấu đó đều không hiển thị cho trình quét tải trước. Điều này làm chậm quá trình khám phá các tài nguyên quan trọng, chắc chắn sẽ ảnh hưởng đến LCP. Trong trường hợp của những ví dụ này, yêu cầu về hình ảnh LCP bị trì hoãn đáng kể so với trải nghiệm tương đương do máy chủ kết xuất mà không cần JavaScript xuất hiện.

Điều này hơi lệch khỏi trọng tâm của bài viết này, nhưng các hiệu ứng của việc kết xuất mã đánh dấu trên ứng dụng khách còn vượt xa việc đánh bại trình quét tải trước. Một trong những lý do là việc giới thiệu JavaScript để hỗ trợ một trải nghiệm không cần đến JavaScript sẽ làm tăng thời gian xử lý không cần thiết, có thể ảnh hưởng đến chỉ số Lượt tương tác đến nội dung hiển thị tiếp theo (INP). Việc hiển thị một lượng lớn mã đánh dấu trên máy khách có nhiều khả năng tạo ra các tác vụ dài so với việc máy chủ gửi cùng một lượng mã đánh dấu. Lý do cho việc này (ngoài quy trình xử lý bổ sung mà JavaScript liên quan) là do các trình duyệt truyền trực tuyến mã đánh dấu từ máy chủ và chia nhỏ quá trình kết xuất theo cách có xu hướng giới hạn các tác vụ dài. Mặt khác, mã đánh dấu do máy khách kết xuất được xử lý dưới dạng một tác vụ đơn lẻ, nguyên khối, có thể ảnh hưởng đến INP của trang.

Cách khắc phục cho trường hợp này phụ thuộc vào câu trả lời cho câu hỏi sau: Có lý do nào khiến máy chủ không thể cung cấp mã đánh dấu của trang thay vì hiển thị trên máy khách không? Nếu câu trả lời là "không", bạn nên cân nhắc việc kết xuất phía máy chủ (SSR) hoặc đánh dấu được tạo tĩnh nếu có thể, vì việc này sẽ giúp trình quét tải trước khám phá và tìm nạp các tài nguyên quan trọng một cách có cơ hội trước thời hạn.

Nếu trang của bạn cần JavaScript để đính kèm chức năng vào một số phần của mã đánh dấu trang, bạn vẫn có thể làm như vậy bằng SSR, bằng JavaScript thuần tuý hoặc hydration để có được cả hai lợi ích.

Giúp trình quét tải trước giúp bạn

Trình quét tải trước là một tính năng tối ưu hoá trình duyệt rất hiệu quả, giúp các trang tải nhanh hơn trong quá trình khởi động. Bằng cách tránh các mẫu làm giảm khả năng khám phá tài nguyên quan trọng trước thời hạn, bạn không chỉ đơn giản hoá quá trình phát triển cho chính mình mà còn tạo ra trải nghiệm người dùng tốt hơn, mang lại kết quả tốt hơn trong nhiều chỉ số, bao gồm cả một số chỉ số quan trọng về trang web.

Tóm lại, sau đây là những điều bạn cần ghi nhớ trong bài đăng này:

  • Trình duyệt tải trước trình quét là một trình phân tích cú pháp HTML phụ, quét trước trình phân tích cú pháp chính nếu trình phân tích cú pháp này bị chặn để khám phá các tài nguyên mà trình phân tích cú pháp có thể tìm nạp sớm hơn.
  • Trình quét tải trước không thể phát hiện các tài nguyên không có trong mã đánh dấu do máy chủ cung cấp theo yêu cầu điều hướng ban đầu. Các cách mà kẻ xấu có thể vượt qua trình quét tải trước bao gồm (nhưng không giới hạn ở):
    • Chèn tài nguyên vào DOM bằng JavaScript, cho dù đó là tập lệnh, hình ảnh, biểu định kiểu hay bất kỳ thứ gì khác sẽ tốt hơn trong tải trọng đánh dấu ban đầu từ máy chủ.
    • Tải từng phần hình ảnh hoặc iframe trong màn hình đầu tiên bằng giải pháp JavaScript.
    • Kết xuất mã đánh dấu trên máy khách có thể chứa các tham chiếu đến tài nguyên phụ của tài liệu bằng JavaScript.
  • Trình quét tải trước chỉ quét HTML. Công cụ này không kiểm tra nội dung của các tài nguyên khác (đặc biệt là CSS) có thể bao gồm các tham chiếu đến những thành phần quan trọng, kể cả các thành phần LCP.

Nếu vì lý do nào đó mà bạn không thể tránh được một mẫu ảnh hưởng tiêu cực đến khả năng tăng tốc hiệu suất tải của trình quét tải trước, hãy cân nhắc gợi ý về tài nguyên rel=preload. Nếu bạn sử dụng rel=preload, hãy kiểm thử trong các công cụ phòng thí nghiệm để đảm bảo rằng bạn nhận được hiệu ứng mong muốn. Cuối cùng, đừng tải trước quá nhiều tài nguyên, vì khi bạn ưu tiên mọi thứ, sẽ không có gì được ưu tiên cả.

Tài nguyên

Hình ảnh chính trên Unsplash, của Mohammad Rahmani .