โหลดหน้าแอพ NextJS ให้เร็วขึ้นด้วย Suspense

ดึงข้อมูลจาก Database ช้า หน้าต่างโหลดนาน
หากใครดึงข้อมูลจาก Database ใน React Server Component จะพบว่าเมื่อข้อมูลที่ดึงมาจาก Database มีขนาดใหญ่ หรือบางครั้ง Database อาจจะกำลังทำงานหนัก จะพบว่าหน้าต่างจะเกิดการค้างเมื่อเราโหลดไปยังหน้านั้นๆ สาเหตุก็มาจากการที่ตัว React Server จะทำการรอข้อมูลจาก Database ก่อน ถึงจะทำการ Render หน้าต่างให้ User ได้เห็น เมื่อ User ใช้งานก็จะเห็นว่าหน้าจอเกิดการค้าง ไม่มีอะไรเกิดขึ้น ต้องรอสักพักถึงจะเห็นหน้าต่างใหม่
ตัวอย่าง Code ข้างล่างจะเป็นการจำลองการดึงข้อมูลจาก Database ด้วยฟังก์ชั่น delay ที่จะทำการรอเป็นเวลา 1 วินาที
// ./data/page.tsx
const DataPage = async () => {
const { data } = await fetchData();
return (
<div className="space-y-4">
<h1 className="text-lg font-semibold">Data Page</h1>
<div>data = {data}</div>
</div>
);
};
export default DataPage;
const fetchData = async () => {
await delay(1000);
return { data: "fetched!!" };
};
function delay(delayInms: number) {
return new Promise((resolve) => setTimeout(resolve, delayInms));
}
จาก code ในหน้า Home เราจะทำการดึงข้อมูลด้วยฟังก์ชั่น fetchData
และแสดงผลที่ได้ใน <div>
เราจะเห็นว่าเมื่อเราทำการ Refresh หน้า Home ตัวหน้าจะไม่เกิดอะไรขึ้น จนเมื่อเวลาผ่านไป 1 วินาที จึงจะเห็นว่าตัวหน้าต่างโหลดสำเร็จ

อาการค้างของหน้าจอนี้จะเป็นผลเสียทำให้เกิด Bad user experience และอาจทำให้ User เลิกใช้งาน App ของเรา แล้วเราจะทำยังไงเพื่อทำให้ประสบการณ์การใช้งานของ User ดีขึ้นได้บ้าง...
ใช้ Suspense เพื่อใส่ Loading State
เราสามารถแก้ด้วยวิธีง่ายๆคือการใช้ Suspense จาก react library ดัง code ด้านล่างนี้
// ./data/page.tsx
import { Suspense } from "react";
const DataPage = () => {
return (
<div className="space-y-4">
<h1 className="text-lg font-semibold">Data Page</h1>
<div>
<span>data =</span>
<Suspense fallback={<span>loading...</span>}>
<AsyncDataUi />
</Suspense>
</div>
</div>
);
};
export default DataPage;
async function AsyncDataUi() {
const { data } = await fetchData();
return <span>{data}</span>;
}
จาก code เราจะทำการแยก Ui ออกเป็นสองส่วน คือส่วนที่ไม่ต้องการใช้ข้อมูล <h1>Data Page</h1>
และส่วนที่ต้องการดึงข้อมูล <AsyncDataUi/>
โดยเราจะครอบส่วน Ui ที่ต้องการแสดงข้อมูลด้วย <Suspense>
และมีการแสดงผลด้วย <span>loading...</span>
เมื่อตัว Ui กำลังรอข้อมูลจาก Database
แค่นี้เราก็จะสามารถโหลดหน้าต่างได้อย่างรวดเร็ว และ User ก็จะสามารถเห็นว่าหน้าต่างกำลังรอข้อมูล ก่อนจะแสดงผล

นอกจากนี้ หากเรามีการดึงข้อมูลที่ไม่เกี่ยวข้องกัน หลายๆชุด เรายังสามารถแยก Server component ออกจากกันได้อีกด้วย เช่น
// ./data/page.tsx
import { Suspense } from "react";
const DataPage = () => {
return (
<div className="space-y-4">
<h1 className="text-lg font-semibold">Data Page</h1>
<div>
<span>book =</span>
<Suspense fallback={<span>loading...</span>}>
<AsyncBookUi />
</Suspense>
</div>
<div>
<span>pencil =</span>
<Suspense fallback={<span>loading...</span>}>
<AsyncPencilUi />
</Suspense>
</div>
</div>
);
};
export default DataPage;
async function AsyncBookUi() {
const { data } = await fetchBook();
return <span>{data}</span>;
}
async function AsyncPencilUi() {
const { data } = await fetchPencil();
return <span>{data}</span>;
}

สรุป
หากเราต้องการดึงข้อมูลจาก Database แล้วไม่อยากให้หน้าเพจเกิดอาการค้างเพราะกำลังรอการดึงข้อมูล เราสามารถใช้ Suspense จาก react ได้ ซึ่งจะช่วยเพิ่มประสบการณ์การใช้งานของ User ให้ดียิ่งขึ้น หากใครมีคำถามเพิ่มเติมสามารถคอมเม้นท์กันได้ หรือส่งข้อความหาผมได้ที่ kangtlee.com/contact

Kang T Lee
ผม Kang T Lee ผมเขียนบทความเกี่ยวกับ Web development, IC Design, Business and Entrepreneur และเนื้อหาที่น่าสนใจจากหนังสือที่ผมอ่าน