ข้ามไปยังเนื้อหา

Offline และ App Shell

เมื่อผู้ใช้เปิดแอปโดยไม่มีอินเทอร์เน็ต browser จะยังต้องมีบางอย่างแสดงผล หากไม่มีกลยุทธ์ offline เลย Chrome จะแสดง offline dinosaur ที่โด่งดัง Firefox แสดง “Server not found” และ Safari จะหน้าจอว่างเปล่า PWA สามารถทำได้ดีกว่านั้น

“ทำงานแบบ offline” ไม่ได้แปลว่าทุกฟีเจอร์จะใช้งานได้โดยไม่มีเครือข่าย แต่หมายความว่าผู้ใช้จะเห็น UI ที่มีความหมายและใช้งานได้ แทนที่จะเป็นหน้า error ของ browser ขึ้นอยู่กับแอปของคุณ อาจหมายความว่า:

  • แอปอ่านข่าวที่แสดงบทความที่ดึงมาล่าสุด
  • รายการสิ่งที่ต้องทำที่ให้ผู้ใช้สร้างรายการและซิงค์เมื่อมีการเชื่อมต่อกลับ
  • แอปแผนที่ที่แสดงชุดไทล์ที่แคชสำหรับเมืองที่ผู้ใช้เคยเข้าชมก่อนหน้า
  • อย่างน้อยก็ shell page ที่อธิบายว่าผู้ใช้ offline อยู่และยังคง branding ไว้

สิ่งที่เหมือนกันคือ browser ไม่ต้องรับผิดชอบต่อการเชื่อมต่อแต่เพียงผู้เดียว — service worker ต่างหากที่รับผิดชอบ

หน้าเว็บแบบดั้งเดิมพึ่งพาเครือข่ายทุกไบต์: HTML, CSS, JavaScript, รูปภาพ การขาดการเชื่อมต่อเพียงครั้งเดียวหมายความว่าไม่มีอะไรโหลดได้ Service workers ทำลายการพึ่งพานี้โดยทำหน้าที่เป็น programmable proxy ที่สามารถให้บริการ assets จาก cache ในเครื่อง

App shell คือชุดขั้นต่ำของ HTML, CSS, และ JavaScript ที่จำเป็นสำหรับการ render โครงของแอป — navigation bar, sidebar, loading skeleton, และ chrome อื่น ๆ ที่เหมือนกันในทุกหน้า shell เปลี่ยนแปลงน้อยมาก (คิดว่า: ครั้งต่อ deployment) และมักมีขนาดเล็ก

Dynamic content — ข้อมูลจริงที่แอปแสดง — โหลดแยกกัน มักมาจาก API หรือ IndexedDB เนื้อหานี้เปลี่ยนแปลงตลอดเวลาและไม่สามารถ precache ได้ทั้งหมด

แนวคิดนั้นเรียบง่าย: cache shell ครั้งเดียว โหลดได้ทันทีในทุกการเข้าชม แล้วดึงเฉพาะ content เท่านั้น

ใน การเข้าชมครั้งแรก browser ยังไม่มี cache ดังนั้นทุกอย่างมาจากเครือข่าย Service worker ติดตั้งในพื้นหลังและเติมข้อมูล cache ด้วย shell assets

ใน ทุกการเข้าชมถัดไป (รวมถึงเมื่อ offline) service worker จะดักจับ navigation request และตอบกลับจาก cache Shell ปรากฏขึ้นทันที — โดยปกติน้อยกว่า 100 ms — ขณะที่แอป fetch เนื้อหาใหม่แบบ asynchronous

flowchart TD
  subgraph CacheStorage["Cache Storage (precached on install)"]
    A[index.html]
    B[app.css]
    C[app.js]
  end

  subgraph Network["Network / IndexedDB"]
    D[Article data]
    E[User profile]
    F[Product listings]
  end

  V[Browser visits URL]
  SW[Service Worker]

  V --> SW
  SW -->|"cache hit — instant"| CacheStorage
  CacheStorage -->|"shell rendered"| R[Page visible]
  R -->|"runtime fetch"| Network
  Network -->|"fill in data"| R
App shell ให้บริการจาก cache; dynamic content โหลดจาก network หรือ IndexedDB

นอกจากการรองรับ offline แล้ว การ cache shell ยังให้ประสิทธิภาพที่วัดได้:

  • Time to First Byte (TTFB) สำหรับ shell ลดลงเกือบเป็นศูนย์ — response มาจาก local storage ไม่ใช่ server
  • Cumulative Layout Shift (CLS) ลดลงเพราะ layout ของ shell อยู่ในตำแหน่งแล้วก่อนที่ dynamic content จะมาถึง
  • การเข้าชมซ้ำ รู้สึกทันทีแม้บนการเชื่อมต่อช้า เพราะ navigation ไม่ต้องรอเครือข่ายอีกต่อไป

รูปแบบ app-shell ทำงานได้ดีที่สุดสำหรับ Single Page Applications (SPAs) และไซต์ที่มี layout ทั่วไปครอบทุกหน้า เหมาะน้อยกว่าสำหรับ:

  • ไซต์ที่เน้นเนื้อหาที่ทุก URL มีโครงสร้าง HTML เฉพาะ
  • ไซต์ที่ shell มีขนาดใหญ่ (หลาย MB) — ค่าใช้จ่ายของ precache มากกว่าประโยชน์
  • แอปที่ต้องแสดงข้อมูลล่าสุดที่สุดเสมอสำหรับทุก pixel
เป้าหมายหลักของสถาปัตยกรรม app-shell คืออะไร?
service worker เติม cache ของ app shell ครั้งแรกเมื่อใด?
content ประเภทใดที่ไม่เหมาะจะ precache ไว้ใน app shell?
เมื่อ app shell ถูก cache ไว้แล้ว จะเกิดอะไรขึ้นตอนเข้าเว็บซ้ำ?