Trải nghiệm chỉ đường tức thì

Bổ sung các kỹ thuật tìm nạp trước truyền thống bằng trình chạy dịch vụ.

Demián Renzulli
Demián Renzulli
Gilberto Cocchi
Gilberto Cocchi

Việc thực hiện một tác vụ trên trang web thường bao gồm nhiều bước. Ví dụ: Việc mua một sản phẩm trên trang web thương mại điện tử có thể bao gồm hoạt động tìm kiếm sản phẩm, chọn một mặt hàng trong danh sách kết quả, thêm mặt hàng vào giỏ hàng rồi hoàn tất thao tác bằng cách thanh toán.

Về mặt kỹ thuật, việc di chuyển qua các trang khác nhau đồng nghĩa với việc tạo ra một yêu cầu điều hướng. Theo quy tắc chung, bạn không muốn sử dụng các tiêu đề Cache-Control dài hạn để lưu phản hồi HTML vào bộ nhớ đệm cho một yêu cầu điều hướng. Thông thường, các API này sẽ được đáp ứng thông qua mạng (bằng Cache-Control: no-cache) để đảm bảo rằng HTML, cùng với chuỗi yêu cầu mạng tiếp theo, luôn được làm mới (hợp lý). Việc phải truy cập mạng mỗi lần người dùng điều hướng đến một trang mới rất tiếc có nghĩa là mỗi lần điều hướng có thể chậm. Ít nhất thì điều đó có nghĩa là quá trình này sẽ không nhanh một cách đáng tin cậy.

Để đẩy nhanh tốc độ xử lý các yêu cầu này, nếu có thể dự đoán được hành động của người dùng, thì bạn có thể yêu cầu trước các trang và thành phần này, rồi lưu chúng trong bộ nhớ đệm trong một khoảng thời gian ngắn cho đến khi người dùng nhấp vào các đường liên kết này. Kỹ thuật này được gọi là tìm nạp trước và thường được triển khai bằng cách thêm thẻ <link rel="prefetch"> vào các trang, cho biết tài nguyên cần tìm nạp trước.

Trong hướng dẫn này, chúng ta sẽ khám phá các cách dùng service worker để bổ sung cho các kỹ thuật tìm nạp trước truyền thống.

Trường hợp sản xuất

MercadoLibre là trang web thương mại điện tử lớn nhất Mỹ Latinh. Để tăng tốc độ điều hướng, họ sẽ tự động chèn các thẻ <link rel="prefetch"> vào một số phần của quy trình. Ví dụ: trong các trang danh sách, chúng tìm nạp trang kết quả tiếp theo ngay khi người dùng cuộn xuống cuối trang thông tin:

Ảnh chụp màn hình các trang danh sách một và hai của MercadoLibre và thẻ Tìm nạp trước liên kết kết nối cả hai.

Tệp được tìm nạp trước được yêu cầu ở mức độ ưu tiên "Thấp nhất" và được lưu trữ trong bộ nhớ đệm HTTP hoặc bộ nhớ đệm của bộ nhớ (tuỳ thuộc vào việc tài nguyên có thể lưu vào bộ nhớ đệm hay không) trong khoảng thời gian khác nhau tuỳ theo trình duyệt. Ví dụ: Kể từ Chrome 85, giá trị này là 5 phút. Các tài nguyên được lưu giữ trong 5 phút, sau đó các quy tắc Cache-Control thông thường đối với tài nguyên sẽ được áp dụng.

Việc sử dụng tính năng lưu vào bộ nhớ đệm của trình chạy dịch vụ có thể giúp bạn kéo dài thời gian tồn tại của tài nguyên tìm nạp trước vượt quá khoảng thời gian 5 phút.

Ví dụ: cổng thông tin thể thao Ý Virgilio Sport sử dụng trình chạy dịch vụ để tìm nạp trước các bài đăng phổ biến nhất trên trang chủ của họ. Họ cũng sử dụng Network Information API để tránh tìm nạp trước đối với người dùng đang sử dụng kết nối 2G.

Biểu trưng của Virgilio Sport.

Nhờ đó, trong hơn 3 tuần quan sát, Virgilio Sport nhận thấy thời gian tải để điều hướng bài viết cải thiện 78% và số lượt hiển thị bài viết tăng 45%.

Ảnh chụp màn hình trang chủ và các trang bài viết của Virgilio Sport, cho thấy các chỉ số về tác động sau khi tìm nạp trước.

Triển khai tính năng lưu vào bộ nhớ đệm bằng Workbox

Trong phần sau, chúng ta sẽ dùng Hộp làm việc để chỉ ra cách triển khai các kỹ thuật lưu bộ nhớ đệm khác nhau trong Service worker có thể dùng làm công cụ bổ sung cho <link rel="prefetch"> hoặc thậm chí là một công cụ thay thế bằng cách uỷ quyền hoàn toàn nhiệm vụ này cho service worker.

1. Các trang tĩnh và tài nguyên phụ của trang được lưu trước trong bộ nhớ đệm

Chuẩn bị là khả năng của trình chạy dịch vụ lưu tệp vào bộ nhớ đệm trong khi cài đặt.

Trong các trường hợp sau đây, quá trình lưu trước vào bộ nhớ đệm được dùng để đạt được mục tiêu tương tự như tìm nạp trước: giúp quá trình điều hướng nhanh hơn.

Đang lưu trước trang tĩnh vào bộ nhớ đệm

Đối với các trang được tạo trong thời gian xây dựng (ví dụ: about.html, contact.html) hoặc trong các trang web hoàn toàn tĩnh, người dùng chỉ cần thêm các tài liệu của trang web vào danh sách bộ nhớ đệm trước để các tài liệu này đã có sẵn trong bộ nhớ đệm mỗi khi người dùng truy cập vào các tài liệu đó:

workbox.precaching.precacheAndRoute([
  {url: '/about.html', revision: 'abcd1234'},
  // ... other entries ...
]);

Tài nguyên phụ của trang lưu trữ dữ liệu

Lưu trước các tài sản tĩnh vào bộ nhớ đệm mà các phần khác nhau của trang web có thể sử dụng (ví dụ: JavaScript, CSS, v.v.) là một phương pháp chung hay nhất và có thể tăng cường hơn nữa các trường hợp tìm nạp trước.

Để tăng tốc độ thao tác trên trang web thương mại điện tử, bạn có thể sử dụng thẻ <link rel="prefetch"> trong các trang danh sách để tìm nạp trước các trang chi tiết sản phẩm đối với một số sản phẩm đầu tiên của trang thông tin. Nếu bạn đã lưu trước các tài nguyên phụ của trang sản phẩm vào bộ nhớ đệm, thì thao tác này có thể giúp bạn điều hướng nhanh hơn nữa.

Cách triển khai:

  • Thêm một thẻ <link rel="prefetch"> vào trang:
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • Thêm tài nguyên phụ của trang vào danh sách bộ nhớ đệm trước trong trình chạy dịch vụ:
workbox.precaching.precacheAndRoute([
  '/styles/product-page.ac29.css',
  // ... other entries ...
]);

2. Kéo dài thời gian tồn tại của tài nguyên tìm nạp trước

Như đã đề cập trước đó, <link rel="prefetch"> tìm nạp và lưu giữ các tài nguyên trong bộ nhớ đệm HTTP trong một khoảng thời gian giới hạn, sau đó các quy tắc Cache-Control đối với tài nguyên sẽ được áp dụng. Kể từ Chrome 85, giá trị này là 5 phút.

Service worker cho phép bạn kéo dài thời gian tồn tại của các trang tìm nạp trước, đồng thời cung cấp thêm lợi ích là làm cho các tài nguyên đó có sẵn để sử dụng ngoại tuyến.

Trong ví dụ trước, bạn có thể bổ sung cho <link rel="prefetch"> dùng để tìm nạp trước một trang sản phẩm bằng Chiến lược lưu vào bộ nhớ đệm trong thời gian chạy Hộp công việc.

Để triển khai việc đó:

  • Thêm một thẻ <link rel="prefetch"> vào trang:
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • Triển khai chiến lược lưu vào bộ nhớ đệm trong thời gian chạy trong trình chạy dịch vụ đối với các loại yêu cầu sau:
new workbox.strategies.StaleWhileRevalidate({
  cacheName: 'document-cache',
  plugins: [
    new workbox.expiration.Plugin({
      maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
    }),
  ],
});

Trong trường hợp này, chúng tôi đã chọn sử dụng chiến lược đã lỗi thời trong khi xác thực lại. Trong chiến lược này, bạn có thể yêu cầu song song các trang từ cả bộ nhớ đệm và mạng. Phản hồi đến từ bộ nhớ đệm nếu có, nếu không thì đến từ mạng. Bộ nhớ đệm luôn được cập nhật với phản hồi mạng với mỗi yêu cầu thành công.

3. Uỷ quyền tìm nạp trước cho trình chạy dịch vụ

Trong hầu hết các trường hợp, cách tốt nhất là sử dụng <link rel="prefetch">. Thẻ này là một gợi ý về tài nguyên được thiết kế để giúp việc tìm nạp trước hiệu quả nhất có thể.

Tuy nhiên, trong một số trường hợp, tốt hơn là bạn nên uỷ quyền hoàn toàn tác vụ này cho trình chạy dịch vụ. Ví dụ: để tìm nạp trước một số sản phẩm đầu tiên trong trang thông tin sản phẩm được hiển thị phía máy khách, bạn có thể phải chèn nhiều thẻ <link rel="prefetch"> vào trang một cách linh động, dựa trên phản hồi của API. Điều này có thể tạm thời làm mất thời gian trên luồng chính của trang và khiến việc triển khai trở nên khó khăn hơn.

Trong những trường hợp như thế này, hãy sử dụng "chiến lược giao tiếp page to service worker" để uỷ quyền hoàn toàn tác vụ tìm nạp trước cho service worker. Bạn có thể thực hiện loại giao tiếp này bằng cách sử dụng worker.postMessage():

Biểu tượng một trang đang giao tiếp hai chiều với một trình chạy dịch vụ.

Gói Cửa sổ hộp công việc đơn giản hoá loại hình giao tiếp này, tóm tắt nhiều thông tin chi tiết của lệnh gọi cơ bản đang được thực hiện.

Bạn có thể triển khai quá trình tìm nạp trước bằng Cửa sổ hộp làm việc theo cách sau:

  • Trên trang: hãy gọi trình chạy dịch vụ, truyền vào loại thông báo và danh sách URL cần tìm nạp trước:
const wb = new Workbox('/sw.js');
wb.register();

const prefetchResponse = await wb.messageSW({type: 'PREFETCH_URLS', urls: […]});
  • Trong trình chạy dịch vụ: triển khai một trình xử lý thông báo để gửi yêu cầu fetch() cho từng URL cần tìm nạp trước:
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});