ในปัจจุบัน เว็บไซต์ หรือแอปเว็บหากคุณต้องการมีแนวโน้มที่จะใช้รูปแบบการนำทางแบบใดแบบหนึ่งจาก 2 แบบดังนี้
- เบราว์เซอร์รูปแบบการนำทางจะจัดเตรียมไว้ให้โดยค่าเริ่มต้น กล่าวคือ คุณจะป้อน URL ในแถบที่อยู่ของเบราว์เซอร์ แล้วคำขอการนำทางจะแสดงเอกสารเป็นการตอบกลับ จากนั้นคุณคลิกที่ลิงก์ ซึ่งจะยกเลิกการโหลดเอกสารปัจจุบันของอีกเอกสารหนึ่ง นั่นคือ ad infinitum
- รูปแบบแอปพลิเคชันหน้าเว็บเดียวซึ่งเกี่ยวข้องกับคำขอการนำทางเริ่มต้นเพื่อโหลด Application Shell และอาศัย JavaScript เพื่อป้อนข้อมูล Application Shell ด้วยมาร์กอัปที่แสดงผลโดยไคลเอ็นต์ด้วยเนื้อหาจาก API แบ็กเอนด์สำหรับ "การนำทาง" แต่ละรายการ
ผู้เสนอได้แนะนำประโยชน์ของแต่ละแนวทางไว้ดังนี้
- รูปแบบการนำทางที่เบราว์เซอร์มีให้โดยค่าเริ่มต้นนั้นมีความยืดหยุ่น เนื่องจากเส้นทางไม่จําเป็นต้องมี JavaScript จึงจะเข้าถึงได้ การแสดงมาร์กอัปโดยไคลเอ็นต์ผ่าน JavaScript ก็อาจเป็นขั้นตอนที่อาจมีค่าใช้จ่ายสูงเช่นกัน ซึ่งหมายความว่าอุปกรณ์ระดับล่างอาจอยู่ในสถานการณ์ที่เนื้อหาล่าช้าเนื่องจากอุปกรณ์ถูกบล็อกสคริปต์ในการประมวลผลเนื้อหา
- ในทางกลับกัน แอปพลิเคชันหน้าเว็บเดียว (SPA) อาจให้การนำทางที่รวดเร็วขึ้นหลังจากการโหลดครั้งแรก เบราว์เซอร์สามารถนำเสนอประสบการณ์ "คล้ายแอป" ที่รวดเร็วและคล้ายแอป แทนที่จะอาศัยเบราว์เซอร์ในการยกเลิกการโหลดเอกสารใหม่ทั้งหมด (และทำซ้ำๆ สำหรับทุกการนำทาง) แม้จะต้องใช้ JavaScript ในการทำงานก็ตาม
ในโพสต์นี้ เราจะพูดถึงวิธีที่ 3 ที่สร้างสมดุลระหว่าง 2 แนวทางที่อธิบายไว้ข้างต้น นั่นคือการใช้ Service Worker เพื่อแคชองค์ประกอบทั่วไปของเว็บไซต์ไว้ล่วงหน้า เช่น มาร์กอัปส่วนหัวและส่วนท้าย และใช้สตรีมเพื่อให้การตอบสนอง HTML แก่ลูกค้าโดยเร็วที่สุดเท่าที่จะเป็นไปได้ ในขณะที่ยังคงใช้รูปแบบการนำทางเริ่มต้นของเบราว์เซอร์
เหตุใดจึงควรสตรีมคำตอบ HTML ใน Service Worker
สตรีมมิงเป็นสิ่งที่เว็บเบราว์เซอร์ของคุณทำอยู่แล้วเมื่อส่งคำขอ ขั้นตอนนี้มีความสำคัญมากในบริบทของคำขอการนำทาง เนื่องจากจะทำให้มั่นใจได้ว่าเบราว์เซอร์จะไม่ถูกบล็อกเพื่อรอการตอบกลับทั้งหมด จึงจะสามารถเริ่มต้นแยกวิเคราะห์มาร์กอัปเอกสารและแสดงหน้าเว็บ
สำหรับโปรแกรมทำงานของบริการ สตรีมมิงจะแตกต่างกันเล็กน้อยเนื่องจากใช้ Streams API ของ JavaScript งานที่สำคัญที่สุดที่โปรแกรมทำงานของบริการต้องดำเนินการคือการสกัดกั้นและตอบกลับคำขอ ซึ่งรวมถึงคำขอการนำทาง
คำขอเหล่านี้อาจโต้ตอบกับแคชได้หลายวิธี แต่รูปแบบการแคชที่พบบ่อยสำหรับมาร์กอัปคือควรใช้การตอบกลับจากเครือข่ายก่อนแต่จะกลับไปที่แคชหากมีสำเนาเก่า และระบุการตอบสนองสำรองทั่วไปหากการตอบสนองที่ใช้งานได้ไม่อยู่ในแคช (ไม่บังคับ)
นี่เป็นรูปแบบที่ทดสอบมาเป็นระยะเวลาแล้วสำหรับมาร์กอัปที่ทำงานได้ดี แต่ถึงแม้ว่าจะช่วยเพิ่มความน่าเชื่อถือในแง่ของการเข้าถึงแบบออฟไลน์ แต่ก็ไม่ได้ให้ข้อได้เปรียบด้านประสิทธิภาพที่มีอยู่ตามธรรมชาติสำหรับคำขอการนำทางที่อาศัยกลยุทธ์แบบเครือข่ายเป็นหลักหรือเครือข่ายเท่านั้น นี่เป็นที่มาของการสตรีม และเราจะศึกษาวิธีใช้โมดูล workbox-streams
ที่ขับเคลื่อนโดย Streams API ในโปรแกรม Workbox เพื่อเร่งคำขอการนำทางในเว็บไซต์ที่มีหลายหน้าของคุณ
รายละเอียดหน้าเว็บทั่วไป
หากกล่าวอย่างตรงไปตรงมา เว็บไซต์มีแนวโน้มที่จะมีองค์ประกอบทั่วไปที่มีอยู่ในทุกหน้า การจัดเรียงองค์ประกอบของหน้าตามปกติมักมีลักษณะดังนี้
- ส่วนหัว
- เนื้อหา
- ส่วนท้าย
เมื่อใช้ web.dev เป็นตัวอย่าง รายละเอียดขององค์ประกอบทั่วไปจะมีลักษณะดังนี้
เป้าหมายที่อยู่เบื้องหลังการระบุส่วนต่างๆ ของหน้าเว็บคือ เรากำหนดสิ่งที่สามารถแคชล่วงหน้าและดึงข้อมูลได้โดยไม่ต้องไปที่เครือข่าย ซึ่งได้แก่ มาร์กอัปส่วนหัวและส่วนท้ายที่ใช้กันโดยทั่วไปในทุกหน้า และส่วนหนึ่งของหน้าเว็บที่เราจะไปที่เครือข่ายก่อนเสมอ ซึ่งในกรณีนี้คือมาร์กอัปของส่วนหัวและส่วนท้ายที่ใช้กันทั่วไป
เมื่อรู้วิธีแบ่งส่วนของหน้าเว็บและระบุองค์ประกอบทั่วไปแล้ว เราสามารถเขียน Service Worker ที่ดึงมาร์กอัปส่วนหัวและส่วนท้ายทันทีจากแคชทันทีในขณะที่ขอเฉพาะเนื้อหาจากเครือข่าย
จากนั้น เราจึงใช้ Streams API ผ่าน workbox-streams
ต่อส่วนต่างๆ เหล่านี้เข้าด้วยกันและตอบกลับคำขอการนำทางได้ทันที ขณะขอมาร์กอัปในจำนวนขั้นต่ำที่จำเป็นจากเครือข่าย
การสร้างโปรแกรมทำงานของบริการสตรีมมิง
เมื่อพูดถึงการสตรีมเนื้อหาบางส่วนในโปรแกรมทำงานของบริการ (Service Worker) ก็มีปัจจัยหลายอย่าง แต่จะต้องสำรวจดูกระบวนการในแต่ละขั้นตอนอย่างละเอียดโดยเริ่มจากวิธีจัดโครงสร้างเว็บไซต์
การแบ่งเว็บไซต์ของคุณออกเป็นส่วนๆ
ก่อนเริ่มเขียนโปรแกรมทำงานของบริการสตรีมมิง คุณต้องทำ 3 สิ่งต่อไปนี้
- สร้างไฟล์ที่มีเฉพาะมาร์กอัปส่วนหัวของเว็บไซต์
- สร้างไฟล์ที่มีเฉพาะมาร์กอัปส่วนท้ายของเว็บไซต์
- ดึงเนื้อหาหลักของแต่ละหน้าออกมาในไฟล์แยกต่างหาก หรือตั้งค่าให้ระบบแสดงเฉพาะเนื้อหาของหน้าตามเงื่อนไขตามส่วนหัวของคำขอ HTTP
อย่างที่คุณอาจคาดหวังไว้ ขั้นตอนสุดท้ายคือส่วนที่ยากที่สุด โดยเฉพาะหากเว็บไซต์ของคุณเป็นแบบคงที่ หากเป็นเช่นนั้น คุณจะต้องสร้างหน้าเว็บแต่ละหน้า 2 เวอร์ชัน โดยเวอร์ชันหนึ่งจะมีมาร์กอัปหน้าแบบเต็ม ขณะที่อีกเวอร์ชันจะมีเฉพาะเนื้อหา
การแต่งโปรแกรมทำงานของบริการสตรีมมิง
หากคุณยังไม่ได้ติดตั้งโมดูล workbox-streams
คุณจะต้องติดตั้งเพิ่มเติมนอกเหนือจากโมดูล Workbox ใดก็ตามที่คุณติดตั้งอยู่ในปัจจุบัน ในตัวอย่างเฉพาะนี้ที่เกี่ยวข้องกับแพ็กเกจต่อไปนี้
npm i workbox-navigation-preload workbox-strategies workbox-routing workbox-precaching workbox-streams --save
จากตรงนี้ ขั้นตอนถัดไปคือการสร้าง Service Worker ใหม่และแคชส่วนต่างๆ ของส่วนหัวและส่วนท้ายไว้ล่วงหน้า
กำลังแคชบางส่วนล่วงหน้า
สิ่งแรกที่คุณจะทำคือสร้าง Service Worker ในรูทของโปรเจ็กต์ชื่อ sw.js
(หรือชื่อไฟล์ใดก็ได้ที่คุณต้องการ) ซึ่งคุณจะเริ่มต้นด้วยสิ่งต่อไปนี้
// sw.js
import * as navigationPreload from 'workbox-navigation-preload';
import {NetworkFirst} from 'workbox-strategies';
import {registerRoute} from 'workbox-routing';
import {matchPrecache, precacheAndRoute} from 'workbox-precaching';
import {strategy as composeStrategies} from 'workbox-streams';
// Enable navigation preload for supporting browsers
navigationPreload.enable();
// Precache partials and some static assets
// using the InjectManifest method.
precacheAndRoute([
// The header partial:
{
url: '/partial-header.php',
revision: __PARTIAL_HEADER_HASH__
},
// The footer partial:
{
url: '/partial-footer.php',
revision: __PARTIAL_FOOTER_HASH__
},
// The offline fallback:
{
url: '/offline.php',
revision: __OFFLINE_FALLBACK_HASH__
},
...self.__WB_MANIFEST
]);
// To be continued...
โค้ดนี้จะทำงาน 2 ประการดังนี้
- เปิดใช้การโหลดการนำทางล่วงหน้าสำหรับเบราว์เซอร์ที่รองรับ
- แคชมาร์กอัปส่วนหัวและส่วนท้ายไว้ล่วงหน้า ซึ่งหมายความว่าระบบจะดึงข้อมูลมาร์กอัปส่วนหัวและส่วนท้ายของทุกหน้าทันที เนื่องจากจะไม่ถูกบล็อกโดยเครือข่าย
- แคชเนื้อหาแบบคงที่ล่วงหน้าในตัวยึดตำแหน่ง
__WB_MANIFEST
ที่ใช้เมธอดinjectManifest
การตอบกลับสตรีมมิง
การทำให้ Service Worker สตรีมคำตอบแบบต่อกันเป็นงานใหญ่ที่สุดในความพยายามนี้ แต่ถึงอย่างนั้น Workbox และ workbox-streams
ก็ทำให้เรื่องนี้กระชับขึ้นมากเมื่อคุณทำทั้งหมดนี้ด้วยตัวเอง
// sw.js
import * as navigationPreload from 'workbox-navigation-preload';
import {NetworkFirst} from 'workbox-strategies';
import {registerRoute} from 'workbox-routing';
import {matchPrecache, precacheAndRoute} from 'workbox-precaching';
import {strategy as composeStrategies} from 'workbox-streams';
// ...
// Prior navigation preload and precaching code omitted...
// ...
// The strategy for retrieving content partials from the network:
const contentStrategy = new NetworkFirst({
cacheName: 'content',
plugins: [
{
// NOTE: This callback will never be run if navigation
// preload is not supported, because the navigation
// request is dispatched while the service worker is
// booting up. This callback will only run if navigation
// preload is _not_ supported.
requestWillFetch: ({request}) => {
const headers = new Headers();
// If the browser doesn't support navigation preload, we need to
// send a custom `X-Content-Mode` header for the back end to use
// instead of the `Service-Worker-Navigation-Preload` header.
headers.append('X-Content-Mode', 'partial');
// Send the request with the new headers.
// Note: if you're using a static site generator to generate
// both full pages and content partials rather than a back end
// (as this example assumes), you'll need to point to a new URL.
return new Request(request.url, {
method: 'GET',
headers
});
},
// What to do if the request fails.
handlerDidError: async ({request}) => {
return await matchPrecache('/offline.php');
}
}
]
});
// Concatenates precached partials with the content partial
// obtained from the network (or its fallback response).
const navigationHandler = composeStrategies([
// Get the precached header markup.
() => matchPrecache('/partial-header.php'),
// Get the content partial from the network.
({event}) => contentStrategy.handle(event),
// Get the precached footer markup.
() => matchPrecache('/partial-footer.php')
]);
// Register the streaming route for all navigation requests.
registerRoute(({request}) => request.mode === 'navigate', navigationHandler);
// Your service worker can end here, or you can add more
// logic to suit your needs, such as runtime caching, etc.
โค้ดนี้ประกอบด้วยส่วนหลักๆ 3 ส่วนที่เป็นไปตามข้อกำหนดต่อไปนี้
- กลยุทธ์
NetworkFirst
ใช้เพื่อจัดการกับคำขอบางส่วนของเนื้อหา เมื่อใช้กลยุทธ์นี้ จะมีการระบุชื่อแคชที่กำหนดเองของcontent
ให้มีเนื้อหาบางส่วน รวมทั้งปลั๊กอินที่กำหนดเองซึ่งกำหนดว่าจะตั้งค่าส่วนหัวของคำขอX-Content-Mode
สำหรับเบราว์เซอร์ที่ไม่รองรับการโหลดการนำทางล่วงหน้าหรือไม่ (ดังนั้นจึงไม่ส่งส่วนหัวService-Worker-Navigation-Preload
) ปลั๊กอินนี้ยังคำนวณด้วยว่าจะส่งเวอร์ชันที่แคชไว้ล่าสุดของเนื้อหาบางส่วน หรือส่งหน้าสำรองแบบออฟไลน์ในกรณีที่ไม่มีเวอร์ชันที่แคชไว้สำหรับคำขอปัจจุบัน - เมธอด
strategy
ในworkbox-streams
(ใช้นามแฝงเป็นcomposeStrategies
ที่นี่) เพื่อเชื่อมโยงส่วนหัวและส่วนท้ายที่ทำแคชล่วงหน้าไว้กับเนื้อหาบางส่วนที่มีการร้องขอจากเครือข่าย - ระบบจะปรับเปลี่ยนรูปแบบทั้งหมดผ่าน
registerRoute
สำหรับคำขอการนำทาง
เราจึงตั้งค่าการสตรีมคำตอบไว้แล้วพร้อมกับใช้ตรรกะนี้ อย่างไรก็ตาม อาจมีการดำเนินการบางอย่างที่คุณจะต้องดำเนินการในส่วนหลัง เพื่อให้แน่ใจว่าเนื้อหาจากเครือข่ายเป็นหน้าเว็บบางส่วนที่คุณสามารถรวมเข้ากับบางส่วนที่เก็บไว้ล่วงหน้าได้
หากเว็บไซต์ของคุณมีระบบแบ็กเอนด์
คุณจะต้องทราบว่าเมื่อเปิดใช้การโหลดการนำทางล่วงหน้า เบราว์เซอร์จะส่งส่วนหัว Service-Worker-Navigation-Preload
ที่มีค่าเป็น true
อย่างไรก็ตาม ในตัวอย่างโค้ดข้างต้น เราได้ไม่รองรับส่วนหัวที่กำหนดเองของ X-Content-Mode
ในการโหลดล่วงหน้าสำหรับการนำทางของเหตุการณ์ในเบราว์เซอร์ ที่แบ็กเอนด์ คุณจะต้องเปลี่ยนการตอบสนองตามการมีส่วนหัวเหล่านี้ ในเบื้องหลัง PHP ของหน้าเว็บหนึ่งๆ อาจมีลักษณะดังนี้
<?php
// Check if we need to render a content partial
$navPreloadSupported = isset($_SERVER['HTTP_SERVICE_WORKER_NAVIGATION_PRELOAD']) && $_SERVER['HTTP_SERVICE_WORKER_NAVIGATION_PRELOAD'] === 'true';
$partialContentMode = isset($_SERVER['HTTP_X_CONTENT_MODE']) && $_SERVER['HTTP_X_CONTENT_MODE'] === 'partial';
$isPartial = $navPreloadSupported || $partialContentMode;
// Figure out whether to render the header
if ($isPartial === false) {
// Get the header include
require_once($_SERVER['DOCUMENT_ROOT'] . '/includes/site-header.php');
// Render the header
siteHeader();
}
// Get the content include
require_once('./content.php');
// Render the content
content($isPartial);
// Figure out whether to render the footer
if ($isPartial === false) {
// Get the footer include
require_once($_SERVER['DOCUMENT_ROOT'] . '/includes/site-footer.php');
// Render the footer
siteFooter();
}
?>
ในตัวอย่างข้างต้น จะมีการเรียกส่วนของเนื้อหาเป็นฟังก์ชัน ซึ่งจะนำค่าของ $isPartial
มาเปลี่ยนวิธีแสดงผลส่วนดังกล่าว เช่น ฟังก์ชันแสดงผล content
อาจรวมมาร์กอัปบางอย่างในเงื่อนไขเมื่อดึงข้อมูลเป็นบางส่วนเท่านั้น ซึ่งเป็นสิ่งที่จะครอบคลุมในเร็วๆ นี้
ข้อควรพิจารณา
ก่อนที่จะทำให้ Service Worker ใช้งานได้เพื่อสตรีมและเชื่อมต่อส่วนต่างๆ เข้าด้วยกัน มีบางสิ่งที่คุณควรคำนึงถึง แม้ว่าจะเป็นความจริงที่ว่าการใช้โปรแกรมทำงานของบริการในลักษณะนี้ไม่ได้เปลี่ยนแปลงลักษณะการนำทางเริ่มต้นของเบราว์เซอร์ไปโดยพื้นฐาน แต่ก็มีบางสิ่งที่คุณอาจต้องแก้ไข
การอัปเดตองค์ประกอบของหน้าเมื่อไปยังที่ต่างๆ
ส่วนที่ยากที่สุดของวิธีการนี้คือต้องมีการอัปเดตบางอย่างกับลูกค้า เช่น มาร์กอัปส่วนหัวล่วงหน้าหมายความว่าหน้าจะมีเนื้อหาเดียวกันในองค์ประกอบ <title>
หรือแม้แต่การจัดการสถานะเปิด/ปิดสำหรับรายการการนำทางจะต้องอัปเดตทุกครั้งที่ไปยังส่วนต่างๆ สิ่งเหล่านี้หรือสิ่งอื่นๆ อาจต้องมีการอัปเดตไคลเอ็นต์สำหรับคำขอการนำทางแต่ละรายการ
วิธีหลีกเลี่ยงปัญหานี้คือการวางองค์ประกอบ <script>
ในบรรทัดลงในเนื้อหาบางส่วนที่มาจากเครือข่ายเพื่ออัปเดตสิ่งสำคัญ 2-3 อย่างต่อไปนี้
<!-- The JSON below contains information about the current page. -->
<script id="page-data" type="application/json">'{"title":"Sand Wasp — World of Wasps","description":"Read all about the sand wasp in this tidy little post."}'</script>
<script>
const pageData = JSON.parse(document.getElementById('page-data').textContent);
// Update the page title
document.title = pageData.title;
</script>
<article>
<!-- Page content omitted... -->
</article>
นี่เป็นเพียงตัวอย่างหนึ่งของสิ่งที่คุณอาจต้องทำหากตัดสินใจเลือกตั้งค่าโปรแกรมทำงานของบริการนี้ ตัวอย่างเช่น สำหรับแอปพลิเคชันที่ซับซ้อนมากขึ้นซึ่งมีข้อมูลผู้ใช้ เช่น คุณอาจต้องจัดเก็บข้อมูลที่เกี่ยวข้องในเว็บสโตร์ เช่น localStorage
และอัปเดตหน้าเว็บจากที่นั่น
การรับมือกับเครือข่ายที่ช้า
ข้อเสียประการหนึ่งของการตอบสนองสตรีมมิงที่ใช้มาร์กอัปจาก Precache อาจเกิดขึ้นเมื่อการเชื่อมต่อเครือข่ายช้า ปัญหาคือมาร์กอัปส่วนหัวจาก Precache จะปรากฏขึ้นทันที แต่เนื้อหาที่บางส่วนจากเครือข่ายอาจใช้เวลาพอสมควรในการมาถึงหลังจากการแสดงผลครั้งแรกของมาร์กอัปส่วนหัว
ซึ่งอาจทำให้เกิดประสบการณ์ที่สับสนได้ และถ้าเครือข่ายทำงานช้ามาก ก็อาจรู้สึกว่าหน้าเว็บเสียหายและไม่แสดงผลต่ออีก ในกรณีเช่นนี้ คุณสามารถเลือกใส่ไอคอนหรือข้อความในมาร์กอัปบางส่วนของเนื้อหา ซึ่งคุณสามารถซ่อนได้เมื่อเนื้อหาโหลดแล้ว
โดยวิธีหนึ่งคือการใช้ CSS สมมติว่าบางส่วนของส่วนหัวลงท้ายด้วยองค์ประกอบ <article>
ที่เปิดอยู่ ซึ่งว่างเปล่าจนกว่าเนื้อหาบางส่วนจะเข้ามาเติมข้อมูล คุณอาจเขียนกฎ CSS ให้มีลักษณะคล้ายกับด้านล่างนี้
article:empty::before {
text-align: center;
content: 'Loading...';
}
วิธีนี้ได้ผล แต่จะแสดงข้อความการโหลดบนไคลเอ็นต์โดยไม่คำนึงถึงความเร็วของเครือข่าย หากไม่ต้องการให้แสดงข้อความแบบแปลกๆ ให้ลองใช้วิธีนี้เพื่อฝังตัวเลือกไว้ในข้อมูลโค้ดด้านบนในคลาส slow
.slow article:empty::before {
text-align: center;
content: 'Loading...';
}
จากที่นี่ คุณสามารถใช้ JavaScript ในบางส่วนของส่วนหัวเพื่ออ่านประเภทการเชื่อมต่อที่มีประสิทธิภาพ (อย่างน้อยในเบราว์เซอร์ Chromium) เพื่อเพิ่มคลาส slow
ไปยังองค์ประกอบ <html>
ในการเชื่อมต่อบางประเภท
<script>
const effectiveType = navigator?.connection?.effectiveType;
if (effectiveType !== '4g') {
document.documentElement.classList.add('slow');
}
</script>
การดำเนินการนี้จะช่วยให้มั่นใจว่าประเภทการเชื่อมต่อที่มีประสิทธิภาพซึ่งช้ากว่าประเภท 4g
จะได้รับข้อความการโหลด จากนั้นในส่วนของเนื้อหา คุณสามารถใส่องค์ประกอบ <script>
ในบรรทัดเพื่อนำคลาส slow
ออกจาก HTML เพื่อกำจัดข้อความการโหลด
<script>
document.documentElement.classList.remove('slow');
</script>
การจัดทำคำตอบสำรอง
สมมติว่าคุณกำลังใช้กลยุทธ์ที่เน้นเครือข่ายเป็นหลักสำหรับบางส่วนของเนื้อหา หากผู้ใช้ออฟไลน์อยู่และไปยังหน้าที่เคยเข้าชมมาก่อน ก็จะถูกครอบคลุม อย่างไรก็ตาม หากผู้ใช้ไปที่หน้าเว็บยังไม่เคยไปมาก่อน ก็จะไม่ได้ดำเนินการใดๆ คุณจะต้องใช้การตอบสนองสำรองเพื่อหลีกเลี่ยงปัญหานี้
โค้ดที่จำเป็นต้องใช้เพื่อสร้างการตอบสนองสำรองจะแสดงในตัวอย่างโค้ดก่อนหน้านี้ กระบวนการนี้มี 2 ขั้นตอน ดังนี้
- แคชการตอบสนองแบบออฟไลน์สำรองไว้ล่วงหน้า
- ตั้งค่าโค้ดเรียกกลับ
handlerDidError
ในปลั๊กอินสำหรับกลยุทธ์ที่เน้นเครือข่ายเป็นหลักเพื่อตรวจสอบแคชของหน้าเว็บเวอร์ชันล่าสุดที่เข้าถึง หากไม่มีการเข้าถึงหน้าเว็บ คุณจะต้องใช้เมธอดmatchPrecache
จากโมดูลworkbox-precaching
เพื่อเรียกการตอบสนองสำรองจากแคชล่วงหน้า
การแคชและ CDN
หากคุณกำลังใช้รูปแบบสตรีมมิงนี้ใน Service Worker ให้ประเมินว่าสถานการณ์ของคุณเป็นไปตามเงื่อนไขต่อไปนี้หรือไม่
- คุณใช้ CDN หรือแคชระดับกลาง/สาธารณะประเภทอื่นๆ
- คุณได้ระบุส่วนหัว
Cache-Control
ที่มีคำสั่งmax-age
และ/หรือs-maxage
ที่ไม่ใช่ 0 ร่วมกับคำสั่งpublic
หากคุณสะดวกทั้ง 2 กรณีนี้ แคชกลางอาจเก็บการตอบกลับสำหรับคำขอการนำทางไว้ อย่างไรก็ตาม โปรดทราบว่าเมื่อคุณใช้รูปแบบนี้ คุณอาจแสดงการตอบสนองสองลักษณะที่แตกต่างกันสำหรับ URL ใดๆ ที่ระบุ:
- การตอบกลับแบบเต็มที่มีมาร์กอัปส่วนหัว เนื้อหา และส่วนท้าย
- การตอบกลับบางส่วนที่มีเฉพาะเนื้อหา
การดำเนินการนี้อาจทำให้เกิดลักษณะการทำงานที่ไม่พึงประสงค์บางอย่าง ซึ่งส่งผลให้มีมาร์กอัปส่วนหัวและส่วนท้ายเพิ่มขึ้น 2 เท่า เนื่องจากโปรแกรมทำงานของบริการอาจดึงการตอบสนองเต็มรูปแบบจากแคช CDN และรวมเข้ากับมาร์กอัปส่วนหัวและส่วนท้ายที่ทำแคชล่วงหน้า
ในการแก้ปัญหานี้ คุณจะต้องใช้ส่วนหัว Vary
ซึ่งส่งผลต่อลักษณะการแคชด้วยการใส่คำตอบที่แคชได้ลงในส่วนหัวอย่างน้อย 1 รายการซึ่งอยู่ในคำขอ เนื่องจากเราเปลี่ยนแปลงการตอบกลับคำขอการนำทางที่แตกต่างกันตามส่วนหัวคำขอ Service-Worker-Navigation-Preload
และ X-Content-Mode
แบบกำหนดเอง เราจึงต้องระบุส่วนหัว Vary
นี้ในการตอบสนองดังนี้
Vary: Service-Worker-Navigation-Preload,X-Content-Mode
ส่วนหัวนี้ช่วยให้เบราว์เซอร์แยกความแตกต่างระหว่างการตอบกลับที่สมบูรณ์และบางส่วนสำหรับคำขอการนำทาง และจะหลีกเลี่ยงปัญหาเกี่ยวกับมาร์กอัปส่วนหัวและส่วนท้ายแบบทวีคูณ เช่นเดียวกับแคชระดับกลาง
ผลลัพธ์
คำแนะนำเกี่ยวกับประสิทธิภาพเวลาที่ใช้ในการโหลดส่วนใหญ่จะมุ่งเน้นที่ "แสดงให้พวกเขาเห็นสิ่งที่คุณมี" อย่ารั้งรอ และอย่ารอจนคุณมีทุกอย่างเสร็จก่อนแล้วจึงจะแสดงให้ผู้ใช้เห็น
Jake Archibald ใน เคล็ดลับสนุกๆ สําหรับเนื้อหาที่ทํางานได้เร็วขึ้น
เบราว์เซอร์มีประสิทธิภาพในการรับมือกับการตอบสนองต่อคำขอการนำทาง แม้จะเป็นเนื้อหาการตอบสนอง HTML ขนาดใหญ่ก็ตาม โดยค่าเริ่มต้น เบราว์เซอร์จะสตรีมและประมวลผลมาร์กอัปเป็นส่วนๆ อย่างต่อเนื่องโดยจะได้ไม่ต้องทำงานเป็นเวลานาน ซึ่งส่งผลดีต่อประสิทธิภาพในการเริ่มต้นทำงาน
วิธีนี้ถือเป็นข้อได้เปรียบของเราเมื่อใช้รูปแบบโปรแกรมทำงานของบริการสตรีมมิง เมื่อใดก็ตามที่คุณตอบกลับคำขอจากโปรแกรมทำงานผ่านทางแคชตั้งแต่เริ่มต้น จุดเริ่มต้นของการตอบกลับก็จะตามมาเกือบจะทันที เมื่อคุณต่อมาร์กอัปส่วนหัวและส่วนท้ายที่บันทึกไว้ล่วงหน้าเข้าด้วยกันกับการตอบสนองจากเครือข่าย คุณจะได้รับประโยชน์ด้านประสิทธิภาพที่เห็นได้ชัดดังต่อไปนี้
- เวลาเป็นไบต์แรก (TTFB) มักจะลดลงอย่างมาก เนื่องจากไบต์แรกของการตอบสนองต่อคำขอการนำทางจะเกิดขึ้นทันที
- First Contentful Paint (FCP) จะทำงานเร็วมาก เนื่องจากมาร์กอัปส่วนหัวในแคชล่วงหน้าจะมีการอ้างอิงไปยังสไตล์ชีตที่แคชไว้ ซึ่งหมายความว่าหน้าเว็บจะแสดงผลอย่างรวดเร็วมาก
- ในบางกรณี Largest Contentful Paint (LCP) ก็อาจเร็วขึ้นด้วย โดยเฉพาะในกรณีที่องค์ประกอบบนหน้าจอที่ใหญ่ที่สุดมาจากบางส่วนของส่วนหัวที่จัดเก็บถาวรล่วงหน้า อย่างไรก็ตาม การแสดงบางอย่างนอกแคชของโปรแกรมทำงานให้เร็วที่สุดควบคู่ไปกับเพย์โหลดมาร์กอัปที่เล็กลงก็อาจส่งผลให้ LCP ดีขึ้นได้
สถาปัตยกรรมสตรีมมิงแบบหลายหน้าอาจยุ่งยากเล็กน้อยในการตั้งค่าและทำซ้ำ แต่ในเชิงทฤษฎีนั้น ความซับซ้อนที่เกี่ยวข้องมักไม่ยากเท่า SPA ในทางทฤษฎี ข้อดีหลักคือคุณไม่ต้องแทนที่รูปแบบการนำทางเริ่มต้นของเบราว์เซอร์ แต่จะเป็นการปรับปรุงรูปแบบดังกล่าว
ยิ่งไปกว่านั้น Workbox ทำให้สถาปัตยกรรมนี้ไม่เพียงแค่เกิดขึ้นได้ แต่ง่ายกว่าการที่คุณนำไปติดตั้งใช้งานด้วยตนเอง ลองใช้เว็บไซต์ของคุณเอง แล้วดูว่าเว็บไซต์แบบหลายหน้าของคุณสามารถให้ผู้ใช้ในวงการทำงานได้เร็วเพียงใด