แอปพลิเคชันหลายหน้าที่เร็วยิ่งขึ้นด้วยสตรีม

ปัจจุบันเว็บไซต์หรือแอปเว็บหากคุณต้องการ มีแนวโน้มที่จะใช้รูปแบบการนำทางแบบใดแบบหนึ่งจาก 2 แบบดังนี้

  • รูปแบบการนำทางที่เบราว์เซอร์ต่างๆ มีให้โดยค่าเริ่มต้น กล่าวคือ คุณต้องป้อน URL ในแถบที่อยู่ของเบราว์เซอร์ และคำขอการนำทางจะแสดงเอกสารเป็นการตอบกลับ จากนั้นคุณคลิกที่ลิงก์ ซึ่งจะยกเลิกการโหลดเอกสารปัจจุบันสำหรับอีกลิงก์หนึ่ง นั่นคือ ad infinitum
  • รูปแบบแอปพลิเคชันหน้าเว็บเดียว ซึ่งเกี่ยวข้องกับคำขอการนำทางเบื้องต้นเพื่อโหลด Application Shell และใช้ JavaScript เพื่อป้อนข้อมูล Application Shell ด้วยมาร์กอัปที่แสดงผลโดยไคลเอ็นต์ด้วยเนื้อหาจาก API แบ็กเอนด์สำหรับ "การนำทาง" แต่ละรายการ

ประโยชน์ของแต่ละแนวทางคือ

  • รูปแบบการนำทางที่เบราว์เซอร์มีให้โดยค่าเริ่มต้นนั้นมีความยืดหยุ่น เนื่องจากไม่ต้องอาศัย JavaScript ในการเข้าถึง การแสดงมาร์กอัปโดยไคลเอ็นต์โดยใช้ JavaScript อาจเป็นกระบวนการที่อาจมีค่าใช้จ่ายสูงเช่นกัน ซึ่งหมายความว่าอุปกรณ์ระดับล่างอาจอยู่ในสถานการณ์ที่เนื้อหาล่าช้าเนื่องจากอุปกรณ์ถูกบล็อกการประมวลผลสคริปต์ที่แสดงเนื้อหา
  • ในทางตรงกันข้าม แอปพลิเคชันหน้าเว็บเดียว (SPA) อาจให้การนำทางที่เร็วขึ้นหลังจากการโหลดครั้งแรก แทนที่จะต้องพึ่งเบราว์เซอร์ในการยกเลิกการโหลดเอกสารสำหรับเอกสารใหม่ทั้งหมด (และทำซ้ำสำหรับทุกการนำทาง) ลูกค้าของคุณสามารถนำเสนอสิ่งที่รู้สึกว่า เร็วขึ้น และ "คล้ายแอป" มากขึ้นได้ ประสบการณ์แม้ว่าจะต้องใช้ JavaScript ในการทำงานก็ตาม

ในโพสต์นี้ เราจะพูดถึงวิธีที่ 3 ที่สร้างสมดุลระหว่าง 2 แนวทางที่อธิบายไว้ข้างต้น คือการอาศัยโปรแกรมทำงานของบริการเพื่อแคชองค์ประกอบทั่วไปของเว็บไซต์ล่วงหน้า เช่น มาร์กอัปส่วนหัวและส่วนท้าย และการใช้สตรีมเพื่อให้การตอบสนอง HTML แก่ไคลเอ็นต์โดยเร็วที่สุด ขณะที่ยังคงใช้รูปแบบการนำทางเริ่มต้นของเบราว์เซอร์

ทำไมจึงต้องสตรีมการตอบกลับ HTML ใน Service Worker

สตรีมมิงเป็นสิ่งที่เว็บเบราว์เซอร์ของคุณทำอยู่แล้วเมื่อมีการส่งคำขอ สิ่งนี้มีความสำคัญอย่างยิ่งในบริบทของคำขอการนำทาง เนื่องจากจะช่วยให้มั่นใจได้ว่าเบราว์เซอร์จะไม่ถูกบล็อก เพื่อรอการตอบกลับทั้งหมดก่อนที่จะสามารถเริ่มแยกวิเคราะห์มาร์กอัปเอกสารและแสดงผลหน้า

แผนภาพแสดง HTML ที่ไม่ใช่สตรีมมิงเทียบกับ HTML แบบสตรีมมิง ในกรณีก่อนหน้านี้ ระบบจะไม่ประมวลผลเพย์โหลดมาร์กอัปทั้งหมดจนกว่าเพย์โหลดมาร์กอัปจะมาถึง ในภายหลัง มาร์กอัปจะได้รับการประมวลผลทีละน้อยเมื่อมีปริมาณมากจากเครือข่าย

สําหรับโปรแกรมทำงานของบริการ สตรีมมิงจะแตกต่างเล็กน้อยเนื่องจากใช้ Streams API ของ JavaScript งานที่สำคัญที่สุดที่ Service Worker ดำเนินการคือการสกัดกั้นและตอบสนองต่อคำขอ ซึ่งรวมถึงคำขอการนำทาง

คำขอเหล่านี้จะโต้ตอบกับแคชได้หลายวิธี แต่รูปแบบการแคชที่พบได้ทั่วไปสำหรับมาร์กอัปคือชอบใช้การตอบสนองจากเครือข่ายแรก แต่จะกลับไปใช้แคชหากมีสำเนาที่เก่ากว่า และให้การตอบกลับสำรองทั่วไปหากไม่มีการตอบกลับที่ใช้งานได้ในแคช

นี่เป็นรูปแบบที่ผ่านการทดสอบมายาวนานสำหรับมาร์กอัปที่ทำงานได้ดี แต่ในขณะที่จะช่วยเพิ่มความน่าเชื่อถือในแง่ของการเข้าถึงแบบออฟไลน์ แต่ก็ไม่ได้ให้ข้อได้เปรียบด้านประสิทธิภาพแฝงสำหรับคำขอการนำทางที่อาศัยกลยุทธ์แบบเครือข่ายก่อนหรือเครือข่ายเท่านั้น สตรีมมิงจะช่วยแก้ปัญหานี้ได้และเราจะสำรวจวิธีใช้โมดูล workbox-streams ที่ขับเคลื่อนโดย Streams API ในโปรแกรมทำงานของบริการ Workbox เพื่อเพิ่มความเร็วคำขอการนำทางในเว็บไซต์ที่มีหลายหน้า

แจกแจงหน้าเว็บทั่วไป

โดยทั่วไปแล้ว เว็บไซต์มีแนวโน้มที่จะมีองค์ประกอบทั่วไปที่มีอยู่ในทุกหน้า การจัดเรียงองค์ประกอบหน้าเว็บโดยทั่วไปมักมีลักษณะดังนี้

  • ส่วนหัว
  • เนื้อหา
  • ส่วนท้าย

เมื่อใช้ web.dev เป็นตัวอย่าง รายละเอียดขององค์ประกอบทั่วไปจะมีลักษณะดังนี้

รายละเอียดองค์ประกอบทั่วไปในเว็บไซต์ web.dev พื้นที่ส่วนกลางที่ทำเครื่องหมายไว้คือ "header" "content" และ "footer"

เป้าหมายที่อยู่เบื้องหลังการระบุส่วนต่างๆ ของหน้าเว็บคือเรากำหนดว่าเนื้อหาใดที่สามารถแคชล่วงหน้าและดึงข้อมูลได้โดยไม่ต้องไปที่เครือข่าย ซึ่งก็คือมาร์กอัปส่วนหัวและส่วนท้ายที่นิยมใช้กันในทุกหน้า และส่วนหนึ่งของหน้าเว็บที่เราจะไปที่เครือข่ายก่อนเสมอคือเนื้อหาในกรณีนี้

เมื่อรู้วิธีแบ่งกลุ่มส่วนต่างๆ ของหน้าและระบุองค์ประกอบทั่วไปแล้ว เราสามารถเขียน Service Worker ที่เรียกมาร์กอัปส่วนหัวและส่วนท้ายได้ทันทีจากแคชเสมอ ขณะที่ขอเฉพาะเนื้อหาจากเครือข่าย

จากนั้นเราใช้ Streams API ผ่าน workbox-streams ต่อชิ้นส่วนเหล่านี้ทั้งหมดเข้าด้วยกัน และตอบสนองคำขอการนำทางได้ทันที พร้อมกับขอมาร์กอัปในจำนวนขั้นต่ำที่จำเป็นจากเครือข่าย

การสร้างโปรแกรมทำงานของบริการสตรีมมิง

เมื่อสตรีมเนื้อหาบางส่วนใน Service Worker มีหลายส่วนที่ต้องเคลื่อนไหว แต่เราจะสำรวจกระบวนการในแต่ละขั้นตอนอย่างละเอียดเมื่อคุณเริ่มวางโครงสร้างเว็บไซต์

การแบ่งกลุ่มเว็บไซต์ของคุณออกเป็นส่วนๆ

ก่อนที่จะเริ่มเขียนโปรแกรมทำงานของบริการสตรีมมิงได้ คุณจะต้องทำ 3 สิ่งต่อไปนี้

  1. สร้างไฟล์ที่มีเฉพาะมาร์กอัปส่วนหัวของเว็บไซต์
  2. สร้างไฟล์ที่มีเฉพาะมาร์กอัปส่วนท้ายของเว็บไซต์
  3. ดึงเนื้อหาหลักของแต่ละหน้าออกมาเป็นไฟล์แยกต่างหาก หรือตั้งค่าระบบแบ็กเอนด์ให้แสดงเฉพาะเนื้อหาของหน้าโดยมีเงื่อนไขโดยอิงจากส่วนหัวของคำขอ HTTP

อย่างที่คุณคาดไว้ ขั้นตอนสุดท้ายคือขั้นตอนที่ยากที่สุด โดยเฉพาะหากเว็บไซต์ไม่มีการเปลี่ยนแปลง ในกรณีนี้คุณจะต้องสร้างหน้าเว็บ 2 เวอร์ชัน โดยแต่ละเวอร์ชันจะมีมาร์กอัปหน้าแบบเต็ม ส่วนอีกเวอร์ชันจะมีเฉพาะเนื้อหา

การเขียน Service Worker สตรีมมิง

หากยังไม่ได้ติดตั้งโมดูล 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-3 อย่าง:

  1. เปิดใช้การโหลดการนำทางล่วงหน้าสำหรับเบราว์เซอร์ที่รองรับ
  2. แคชล่วงหน้าสำหรับมาร์กอัปส่วนหัวและส่วนท้าย ซึ่งหมายความว่าระบบจะเรียกข้อมูลมาร์กอัปส่วนหัวและส่วนท้ายสำหรับทุกหน้าโดยทันที เนื่องจากเครือข่ายจะไม่บล็อกมาร์กอัปดังกล่าว
  3. แคชชิ้นงานแบบคงที่ล่วงหน้าในตัวยึดตำแหน่ง __WB_MANIFEST ที่ใช้เมธอด injectManifest

คำตอบสตรีมมิง

การทำให้โปรแกรมทำงานของบริการสตรีมคำตอบที่เชื่อมถึงกันคือส่วนสำคัญที่สุดของความพยายามนี้ แต่ถึงอย่างนั้น 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 ส่วนซึ่งเป็นไปตามข้อกำหนดต่อไปนี้

  1. ใช้กลยุทธ์ NetworkFirst เพื่อจัดการคำขอสำหรับเนื้อหาบางส่วน เมื่อใช้กลยุทธ์นี้ ระบบจะระบุชื่อแคชที่กำหนดเองของ content ให้มีบางส่วนของเนื้อหา รวมทั้งปลั๊กอินที่กำหนดเองซึ่งจัดการว่าจะตั้งค่าส่วนหัวของคำขอ X-Content-Mode สำหรับเบราว์เซอร์ที่ไม่รองรับการโหลดการนำทางล่วงหน้าหรือไม่ (ดังนั้นจึงไม่ส่งส่วนหัว Service-Worker-Navigation-Preload) ปลั๊กอินนี้ยังคำนวณด้วยว่าจะส่งเวอร์ชันแคชล่าสุดของเนื้อหาบางส่วน หรือส่งหน้าสำรองแบบออฟไลน์ในกรณีที่ไม่ได้เก็บเวอร์ชันแคชสำหรับคำขอปัจจุบันไว้
  2. เมธอด strategy ใน workbox-streams (ใช้นามแฝงเป็น composeStrategies ที่นี่) ใช้เพื่อต่อส่วนส่วนหัวและส่วนท้ายที่เก็บแคชล่วงหน้าไว้รวมกับเนื้อหาบางส่วนที่ขอจากเครือข่าย
  3. ระบบจะเชื่อมโยงรูปแบบทั้งหมดผ่าน 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> ในบรรทัดลงในบางส่วนของเนื้อหาที่มาจากเครือข่ายเพื่ออัปเดตสิ่งสำคัญบางอย่าง ดังนี้

<!-- The JSON below contains information about the current page. -->
<script id="page-data" type="application/json">'{"title":"Sand Wasp &mdash; 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>

นี่เป็นเพียงตัวอย่างหนึ่งของสิ่งที่คุณอาจต้องทำหากเลือกที่จะตั้งค่า Service Worker นี้ เช่น สำหรับแอปพลิเคชันที่ซับซ้อนขึ้นซึ่งมีข้อมูลผู้ใช้ คุณอาจต้องเก็บข้อมูลที่เกี่ยวข้องเล็กๆ น้อยๆ ไว้ในเว็บสโตร์ เช่น localStorage และอัปเดตหน้าเว็บจากที่นั่น

การจัดการเครือข่ายที่ช้า

ข้อเสียอย่างหนึ่งของการสตรีมคำตอบที่ใช้มาร์กอัปจากแคชล่วงหน้าอาจเกิดขึ้นเมื่อการเชื่อมต่อเครือข่ายช้า ปัญหาคือมาร์กอัปส่วนหัวจาก Precache จะมาถึงทันที แต่เนื้อหาบางส่วนจากเครือข่ายอาจใช้เวลานานพอสมควรกว่าจะมาถึงหลังจากที่การแสดงผลครั้งแรกของมาร์กอัปส่วนหัว

ซึ่งอาจทำให้เกิดความสับสนและหากเครือข่ายทำงานช้ามาก อาจทำให้รู้สึกเหมือนหน้าเว็บแตกและไม่แสดงผลอีกเลย ในกรณีเช่นนี้ คุณสามารถเลือกวางไอคอนหรือข้อความที่กำลังโหลดลงในมาร์กอัปบางส่วนของเนื้อหาที่คุณสามารถซ่อนได้เมื่อเนื้อหาโหลดเสร็จแล้ว

ซึ่งวิธีหนึ่งที่ทำได้คือการดำเนินการผ่าน CSS สมมติว่าส่วนหัวของคุณลงท้ายด้วยองค์ประกอบ <article> ที่เปิดอยู่ซึ่งว่างเปล่าจนกว่าเนื้อหาบางส่วนจะมาถึงเพื่อเติมข้อมูล คุณสามารถเขียนกฎ CSS ที่มีลักษณะดังนี้

article:empty::before {
  text-align: center;
  content: 'Loading...';
}

ซึ่งจะใช้ได้แต่จะแสดงข้อความกำลังโหลดบนไคลเอ็นต์ไม่ว่าเครือข่ายจะมีความเร็วเท่าใดก็ตาม หากต้องการเลี่ยงการแสดง Flash ของข้อความแปลกๆ ให้ลองใช้วิธีนี้เมื่อเราฝังตัวเลือกในข้อมูลโค้ดด้านบนไว้ในคลาส 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 ขั้นตอนดังนี้

  1. แคชการตอบกลับสำรองแบบออฟไลน์ไว้ล่วงหน้า
  2. ตั้งค่า handlerDidError Callback ในปลั๊กอินสำหรับกลยุทธ์แบบเน้นเครือข่ายเป็นหลัก เพื่อตรวจสอบแคชของหน้าเว็บเวอร์ชันที่มีการเข้าถึงล่าสุด หากไม่เคยเข้าถึงหน้าดังกล่าว คุณจะต้องใช้เมธอด matchPrecache จากโมดูล workbox-precaching เพื่อเรียกการตอบกลับสำรองจากแคชล่วงหน้า

การแคชและ CDN

หากคุณใช้รูปแบบสตรีมมิงนี้ใน Service Worker ให้ประเมินว่ารายการต่อไปนี้มีผลกับสถานการณ์ของคุณหรือไม่

  • คุณใช้ CDN หรือแคชกลาง/สาธารณะประเภทอื่นๆ
  • คุณได้ระบุส่วนหัว Cache-Control ด้วยคำสั่ง max-age ที่ไม่ใช่ 0 และ/หรือ s-maxage ร่วมกับคำสั่ง public

หากทั้ง 2 กรณีนี้เป็นกรณีของคุณ แคชกลางอาจกักเก็บการตอบกลับคำขอการนำทาง อย่างไรก็ตาม โปรดทราบว่าเมื่อคุณใช้รูปแบบนี้ คุณอาจจะแสดงการตอบกลับที่แตกต่างกันสองชุดสำหรับ URL หนึ่งๆ:

  • การตอบกลับแบบเต็ม ซึ่งมีมาร์กอัปส่วนหัว เนื้อหา และส่วนท้าย
  • คำตอบบางส่วนที่มีเฉพาะเนื้อหา

การทำเช่นนี้อาจทำให้เกิดการทำงานที่ไม่พึงประสงค์บางอย่าง ซึ่งส่งผลให้เกิดมาร์กอัปส่วนหัวและส่วนท้ายเพิ่มเป็น 2 เท่า เนื่องจาก Service Worker อาจดึงข้อมูลการตอบสนองเต็มรูปแบบจากแคช CDN และรวมข้อมูลดังกล่าวเข้ากับมาร์กอัปส่วนหัวและส่วนท้ายที่เก็บไว้ล่วงหน้า

หากต้องการแก้ปัญหานี้ คุณจะต้องใช้ส่วนหัว Vary ซึ่งส่งผลต่อลักษณะการแคชด้วยคีย์การตอบกลับที่แคชได้กับส่วนหัวอย่างน้อย 1 รายการที่มีอยู่ในคําขอ เนื่องจากเราจะเปลี่ยนแปลงการตอบกลับคำขอการนำทางโดยอิงตามส่วนหัวของคำขอ Service-Worker-Navigation-Preload และ X-Content-Mode ที่กำหนดเอง เราจึงต้องระบุส่วนหัว Vary นี้ในการตอบ

Vary: Service-Worker-Navigation-Preload,X-Content-Mode

เมื่อใช้ส่วนหัวนี้ เบราว์เซอร์จะแยกความแตกต่างระหว่างการตอบกลับที่สมบูรณ์และบางส่วนสำหรับคำขอการนำทาง หลีกเลี่ยงปัญหาเกี่ยวกับมาร์กอัปส่วนหัวและส่วนท้ายที่เพิ่มขึ้นเป็นสองเท่า เช่นเดียวกับแคชกลาง

ผลลัพธ์

คำแนะนำเกี่ยวกับประสิทธิภาพเวลาที่ใช้ในการโหลดส่วนใหญ่จะมุ่งเน้นที่ "แสดงสิ่งที่คุณได้รับ" เท่านั้น อย่ายอมแพ้ อย่ารอจนกว่าคุณจะมีทุกสิ่งทุกอย่างครบก่อนที่จะแสดงให้ผู้ใช้เห็น

Jake Archibald ในเคล็ดลับสนุกๆ เพื่อเนื้อหาที่รวดเร็วขึ้น

เบราว์เซอร์ทำงานได้ดีเมื่อต้องจัดการการตอบสนองคำขอการนำทาง แม้จะเป็นการตอบสนองของ HTML ที่มีขนาดใหญ่ก็ตาม โดยค่าเริ่มต้น เบราว์เซอร์จะสตรีมและประมวลผลมาร์กอัปเป็นช่วงๆ อย่างต่อเนื่อง ซึ่งจะหลีกเลี่ยงงานที่ใช้เวลานาน ซึ่งส่งผลดีต่อประสิทธิภาพของสตาร์ทอัพ

ซึ่งเป็นข้อได้เปรียบของเราเมื่อเราใช้รูปแบบ Service Worker สตรีมมิง เมื่อใดก็ตามที่คุณตอบกลับคำขอจากแคชของ Service Worker ตั้งแต่ต้น การตอบกลับจะเริ่มต้นแทบจะในทันที เมื่อรวมมาร์กอัปส่วนหัวและส่วนท้ายที่แคชล่วงหน้าเข้ากับการตอบกลับจากเครือข่าย คุณจะเห็นข้อได้เปรียบด้านประสิทธิภาพที่โดดเด่นดังต่อไปนี้

  • Time to First Byte (TTFB) มักจะลดลงอย่างมากเนื่องจากไบต์แรกของการตอบกลับคำขอการนำทางเป็นแบบทันที
  • First Contentful Paint (FCP) จะทำงานเร็วมากเนื่องจากมาร์กอัปส่วนหัวที่แคชล่วงหน้าจะมีการอ้างอิงไปยังสไตล์ชีตที่แคชไว้ ซึ่งหมายความว่าหน้าเว็บจะแสดงผลได้รวดเร็วมาก
  • ในบางกรณี การแสดงผลเนื้อหาขนาดใหญ่ที่สุด (LCP) ก็ทำงานได้เร็วขึ้นเช่นกัน โดยเฉพาะอย่างยิ่งหากส่วนหัวบางส่วนที่แคชไว้ล่วงหน้าเป็นองค์ประกอบที่ใหญ่ที่สุดบนหน้าจอ อย่างไรก็ตาม การแสดงบางอย่างจากแคชของ Service Worker ให้เร็วที่สุดควบคู่กับเพย์โหลดมาร์กอัปที่เล็กลงอาจส่งผลให้ LCP ดีขึ้น

สถาปัตยกรรมสตรีมมิงแบบหลายหน้าอาจยุ่งยากเล็กน้อยในการตั้งค่าและทำซ้ำ แต่ความซับซ้อนที่เกี่ยวข้องมักไม่ยุ่งยากไปกว่า SPA ในทางทฤษฎี ประโยชน์หลักก็คือ คุณไม่ได้แทนที่รูปแบบการนำทางเริ่มต้นของเบราว์เซอร์ แต่คุณกำลังปรับปรุงให้ดีขึ้น

ยิ่งไปกว่านั้น Workbox ทำให้สถาปัตยกรรมนี้ไม่เพียงแต่เป็นไปได้ยากเท่านั้น แต่ยังทำได้ง่ายกว่าการที่คุณนำไปใช้งานด้วยตัวเอง ลองใช้เว็บไซต์ของคุณเอง แล้วดูว่าเว็บไซต์ที่มีหลายหน้าจะเร็วขึ้นสำหรับผู้ใช้ในภาคสนามได้มากน้อยเพียงใด

แหล่งข้อมูล