ทำไมแอปของเราต้องรองรับการใช้งานแบบ Offline-First
ลองนึกภาพว่าคุณกำลังใช้แอปสั่งอาหารในมือถือ แต่จู่ ๆ คุณก็เดินเข้าลิฟต์ที่สัญญาณอินเทอร์เน็ตเข้าไม่ถึง แอปที่คุณกำลังเปิดอยู่กลับหมุนติ้ว หรือขึ้นหน้าจอว่างเปล่าจนคุณหงุดหงิด นี่คือปัญหาคลาสสิกที่เกิดขึ้นเพราะนักพัฒนาส่วนใหญ่เขียนแอปโดยหวังว่าอินเทอร์เน็ตจะแรงตลอดเวลา การทำ Offline-First (การออกแบบแอปให้ใช้งานได้แม้ไม่มีอินเทอร์เน็ต) จึงเป็นวิธีแก้ปัญหาที่ดีที่สุด
การคิดแบบนี้ไม่ได้หมายความว่าแอปต้องใช้งานได้ทุกฟีเจอร์ตอนไม่มีเน็ต แต่มันคือการเปลี่ยนวิธีคิดจากการที่ต้องรอเน็ตเสมอ มาเป็นการดึงข้อมูลจากตัวเครื่องก่อนเสมอ แล้วค่อยอัปเดตข้อมูลกับเซิร์ฟเวอร์ (คอมพิวเตอร์ที่เก็บข้อมูลหลัก) ในภายหลัง วิธีนี้ทำให้ผู้ใช้งานรู้สึกว่าแอปของคุณลื่นไหลและเสถียร ไม่ว่าจะอยู่ที่ไหนหรือสัญญาณจะแย่แค่ไหนก็ตาม
สำหรับมือใหม่ที่กำลังฝึกเขียนโปรแกรม การฝึกทำแอปสไตล์นี้จะช่วยให้คุณเข้าใจการจัดการข้อมูลที่ซับซ้อนขึ้น คุณจะได้เรียนรู้ว่าการเก็บข้อมูลไว้ในเครื่องเป็นเรื่องสำคัญแค่ไหน และจะจัดการอย่างไรให้ข้อมูลในเครื่องกับบนเซิร์ฟเวอร์ตรงกันเสมอ นี่เป็นทักษะที่บริษัทเทคโนโลยีชั้นนำมองหา เพราะมันแสดงถึงความใส่ใจในประสบการณ์ของผู้ใช้งานอย่างแท้จริง
รู้จัก 4 ชั้นโครงสร้างเพื่อสร้างแอปสายอึด
การสร้างแอปที่ทำงานแบบออฟไลน์ได้ ไม่ได้ใช้แค่เครื่องมือเดียว แต่เป็นการทำงานร่วมกันของ 4 ชั้น (เลเยอร์) เหมือนกับการสร้างบ้านที่ต้องมีทั้งฐานราก โครงสร้าง ผนัง และหลังคา แต่ละชั้นทำหน้าที่ต่างกันเพื่อให้แอปของคุณทำงานได้แม้จะตัดขาดจากโลกภายนอก
ชั้นแรกคือ Service Worker (โปรแกรมเล็กๆ ที่ทำงานเบื้องหลังเบราว์เซอร์) ซึ่งเปรียบเสมือนพนักงานต้อนรับที่คอยเก็บไฟล์หน้าเว็บไว้ในเครื่อง ทำให้ผู้ใช้เปิดแอปได้ทันทีแม้ไม่มีเน็ต ชั้นที่สองคือ IndexedDB (ฐานข้อมูลขนาดเล็กในเบราว์เซอร์) ที่ทำหน้าที่เป็นตู้เก็บเอกสารส่วนตัว ไว้เก็บข้อมูลที่แอปดึงมาได้เพื่อให้ดูซ้ำได้ในภายหลัง
ชั้นที่สามคือ Cache-Aware Fetch (ตัวช่วยดึงข้อมูลที่ฉลาดพอจะเลือกดึงจากเครื่องก่อน) ทำหน้าที่ตัดสินใจว่าควรไปขอข้อมูลจากเน็ตหรือดึงจากเครื่อง ชั้นสุดท้ายคือ TanStack Query (เครื่องมือจัดการสถานะข้อมูล) ที่ช่วยให้ข้อมูลในแอปของคุณสดใหม่อยู่เสมอ และยังช่วยเก็บข้อมูลลงเครื่องให้เราแบบอัตโนมัติอีกด้วย
สร้างห้องเก็บเอกสารด้วย IndexedDB
IndexedDB คือฐานข้อมูลที่ฝังตัวอยู่ในเบราว์เซอร์ ช่วยให้เราเก็บข้อมูลแบบถาวรได้เหมือนฐานข้อมูลบนเซิร์ฟเวอร์จริง ๆ เพื่อความสะดวกเราจะใช้ไลบรารี (ชุดคำสั่งสำเร็จรูป) ที่ชื่อว่า idb เพื่อช่วยให้เขียนคำสั่งติดต่อกับฐานข้อมูลได้ง่ายขึ้นผ่านระบบ Promises (การเขียนคำสั่งให้ทำงานเสร็จในอนาคต)
เราต้องสร้างที่เก็บข้อมูล 3 ส่วน คือ apiCache ไว้เก็บผลลัพธ์จากเซิร์ฟเวอร์ syncQueue ไว้เก็บรายการคำสั่งที่รอส่งไปเซิร์ฟเวอร์ และ offlineMeta สำหรับเก็บข้อมูลตั้งค่าทั่วไป ขั้นตอนนี้ทำครั้งเดียวตอนเริ่มโปรเจกต์เพื่อให้ฐานข้อมูลพร้อมใช้งาน
import { openDB } from "idb";
// สร้างฐานข้อมูลชื่อ app-offline-db
export async function getDb() {
return openDB("app-offline-db", 1, {
upgrade(db) {
// สร้างตู้เก็บเอกสาร 3 ตู้
db.createObjectStore("apiCache", { keyPath: "key" });
db.createObjectStore("syncQueue", { keyPath: "id", autoIncrement: true });
db.createObjectStore("offlineMeta", { keyPath: "key" });
},
});
}
บรรทัดที่ 4 คือการสั่งเปิดฐานข้อมูล ถ้าไม่มีฐานข้อมูลนี้อยู่ ระบบจะวิ่งเข้าฟังก์ชัน upgrade เพื่อสร้างตู้เก็บข้อมูล 3 ตู้ตามที่กำหนดไว้ในบรรทัดที่ 6-8 ผลลัพธ์คือคุณจะได้ฐานข้อมูลที่พร้อมเก็บข้อมูลแบบถาวรไว้ในเครื่องของผู้ใช้งานทันที
ดึงข้อมูลอย่างฉลาดด้วย Cache-Aware Fetch
หัวใจสำคัญคือการเขียนฟังก์ชันตัวกลางที่คอยเช็กว่า เรามีข้อมูลอยู่ในเครื่องหรือยัง ถ้ามีและยังไม่เก่าเกินไป เราจะคืนค่าข้อมูลนั้นให้ผู้ใช้ทันทีโดยไม่ต้องรอเน็ตเลย วิธีนี้ทำให้แอปดูเร็วเหมือนติดจรวดเพราะไม่ต้องเสียเวลาหมุนรอข้อมูลจากเซิร์ฟเวอร์
เราจะกำหนด TTL (ระยะเวลาที่ข้อมูลยังถือว่าสดใหม่) เช่น เก็บข้อมูลไว้ 5 นาทีหรือ 1 ชั่วโมง ถ้าผู้ใช้งานพยายามดึงข้อมูลตอนไม่มีเน็ต ฟังก์ชันนี้จะไปค้นใน IndexedDB ถ้าเจอข้อมูลก็จะส่งให้หน้าจอทันที ทำให้ผู้ใช้ไม่รู้สึกว่าแอปพัง
export async function cacheResponse(key, data, ttlMs) {
const db = await getDb();
await db.put("apiCache", {
key,
data,
expiresAt: Date.now() + ttlMs, // คำนวณเวลาหมดอายุ
});
}
ฟังก์ชันนี้รับค่า key (ชื่อข้อมูล) data (ตัวข้อมูล) และ ttlMs (อายุของข้อมูล) บรรทัดที่ 3 คือการบันทึกข้อมูลลงฐานข้อมูลโดยเพิ่มเวลาหมดอายุเข้าไปด้วย ผลลัพธ์คือข้อมูลจะถูกเก็บไว้ในเครื่องพร้อมมีวันหมดอายุคอยกำกับไว้ให้เราตรวจสอบในภายหลัง
ประสานพลังด้วย TanStack Query
TanStack Query เป็นพระเอกตัวจริงในการจัดการข้อมูลจากเซิร์ฟเวอร์ มันช่วยให้เราไม่ต้องเขียนโค้ดจัดการสถานะข้อมูลเองให้วุ่นวาย โดยเฉพาะฟีเจอร์ Persistence (การบันทึกสถานะลงหน่วยความจำถาวร) ที่ช่วยบันทึกทุกอย่างลง localStorage (ที่เก็บข้อมูลขนาดเล็กในเบราว์เซอร์) ได้อัตโนมัติ
เมื่อผู้ใช้ปิดหน้าเว็บแล้วกลับมาเปิดใหม่ ข้อมูลที่เคยดึงไว้จะถูกโหลดกลับมาทันทีโดยไม่ต้องรอโหลดใหม่จากเซิร์ฟเวอร์อีกรอบ นี่คือสิ่งที่ทำให้แอปของคุณดูเป็นมืออาชีพและตอบสนองได้รวดเร็วมาก ยิ่งถ้าทำคู่กับ IndexedDB คุณจะได้แอปที่แข็งแกร่งมาก
ขั้นตอนการใช้งานคือการตั้งค่า persistQueryClient ให้ชี้ไปที่ที่เก็บข้อมูลของเรา วิธีนี้ช่วยให้ข้อมูลในหน่วยความจำของแอปไม่หายไปแม้ผู้ใช้จะกดรีเฟรชหน้าเว็บ ทำให้ประสบการณ์การใช้งานต่อเนื่องและไม่ขาดตอน
สรุป: เปลี่ยนแอปธรรมดาให้เป็น Offline-First
การทำแอปแบบ Offline-First ไม่ใช่เรื่องยากเกินความสามารถ แต่มันคือการปรับจูนความคิดให้มองว่า "อินเทอร์เน็ตคือตัวเลือกเสริม" ไม่ใช่ "หัวใจหลัก" ของแอป เริ่มจากการวางโครงสร้างฐานข้อมูลในเครื่องให้ดี แล้วใช้ตัวช่วยอย่าง TanStack Query เข้ามาจัดการข้อมูลให้เรา
ตัวอย่างการนำไปใช้จริง: หากคุณทำแอปบันทึกงาน (To-do List) ให้คุณเซฟงานทุกอย่างลง IndexedDB ก่อนเสมอ แล้วค่อยใช้ฟังก์ชันเบื้องหลังส่งข้อมูลไปเซิร์ฟเวอร์ทีหลัง หากเน็ตหลุด ข้อมูลในเครื่องจะยังอยู่และผู้ใช้ยังทำงานต่อได้ทันทีโดยไม่เห็นหน้าจอ Error
จำไว้ว่าโปรแกรมเมอร์ที่เก่งไม่ได้ดูแค่ว่าโค้ดทำงานได้ไหม แต่ดูว่าแอปที่เขียนขึ้นมาทำให้ชีวิตผู้ใช้งานง่ายขึ้นอย่างไร การฝึกทำแอปให้รองรับการใช้งานแบบออฟไลน์จะทำให้คุณเข้าใจหัวใจของการพัฒนาเว็บแอปพลิเคชันอย่างลึกซึ้ง และช่วยให้คุณก้าวสู่การเป็นนักพัฒนาที่มีคุณภาพได้แน่นอนครับ
ที่มา: Building an Offline-First React App: A Practical Guide for Frontend Developers — DEV Community