Next.js เป็น Web Framework ที่ประสบความสำเร็จมาก รวม Server Side Rendering (SSR) และ Static Site Generation (SSG) SSG ทำให้เว็บไซต์เร็วขึ้นขอบคุณ CDN Caching ในขณะที่ SSR ช่วย SEO และ Dynamic Data
Server Side Rendering เป็นฟีเจอร์ที่ยอดเยี่ยมช่วยให้เขียน Full Stack Apps ได้ แต่ถ้าไม่ระวัง ประสิทธิภาพของเว็บ Next.js อาจได้รับผลกระทบง่าย ในบล็อกนี้จะอธิบายการใช้ Redis เร่งความเร็ว Next.js API Calls ก่อนจะกล่าวถึงวิธีง่ายกว่านั้น
ใช้ SWR สำหรับ API Calls
SWR เป็น Data Fetching Library ที่ฉลาด ใช้ HTTP Cache Invalidation Strategy (Stale-while-revalidate) ตาม HTTP RFC 5861 เมื่อเรียก API ด้วย SWR มันคืน Cached Data ทันที แต่ Asynchronously ดึงข้อมูลปัจจุบันและอัปเดต UI คุณตั้งค่า RefreshInterval ได้ตามความทนทานต่อ Staleness
const { data: user } = useSWR('/api/user', { refreshInterval: 2000 })
ในโค้ดด้านบน User API จะ Refresh ทุก 2 วินาที
Caching ด้วย Redis
SWR เรียบง่ายและมีประสิทธิภาพ แต่มีกรณีที่ต้องการ Server-side Caching:
- Client-side Caching ปรับปรุงประสิทธิภาพให้ Clients แต่ถ้าจำนวน Clients มาก อาจทำให้ Server-side Resources โหลดสูง ส่งผลให้ Client-side Performance ตกในที่สุด
- หาก Consume External API ที่มี Quota คุณจะต้องการควบคุม API Usage ด้าน Server มิฉะนั้น Clients มากเกินไปจะกิน Quota เร็ว
- หากมี Resources ที่คำนวณ ดึง หรือประมวลผลที่ Server ด้วย Dynamic Inputs Client-side Caching จะไม่ค่อยมีประโยชน์
โปรเจกต์ตัวอย่าง: Covid Tracker
ในโปรเจกต์นี้ เราใช้ Javier Aviles' Covid API หา Top 10 ประเทศที่ Cases มากที่สุด ดู เว็บไซต์ และ Source Code
เราจะใช้ Redis แคช Responses จาก Covid API เพื่อ:
- Response เร็วขึ้นมาก หากเช็ค เว็บไซต์ จะเห็นว่าการเรียก Covid API ใช้หลายร้อยมิลลิวินาที ในขณะที่ดึงจาก Redis ใช้แค่ 1-2 มิลลิวินาที
- ไม่ท่วม Covid API ด้วย Requests มากเกินไป
API Code
โค้ดตรวจสอบก่อนว่ามี API Result แคชใน Redis หรือไม่ หากไม่มี จะดึง Country List ทั้งหมดจาก Covid API เรียงตาม Cases ของวันปัจจุบัน และบันทึก Top 10 ไป Redis ตอนบันทึก ตั้งค่า "EX" 60 หมายถึง Redis จะ Evict Entry ใน 60 วินาที
import Redis from "ioredis";
let redis = new Redis(process.env.REDIS_URL);
export default async (req, res) => {
let start = Date.now();
let cache = await redis.get("cache");
cache = JSON.parse(cache);
let result = {};
if (cache) {
console.log("loading from cache");
result.data = cache;
result.type = "redis";
result.latency = Date.now() - start;
return res.status(200).json(result);
} else {
console.log("loading from api");
start = Date.now();
return fetch("https://coronavirus-19-api.herokuapp.com/countries")
.then((r) => r.json())
.then((data) => {
data.sort(function (a, b) {
return b.todayCases - a.todayCases;
});
result.data = data.splice(1, 11);
result.type = "api";
result.latency = Date.now() - start;
redis.set("cache", JSON.stringify(result.data), "EX", 60);
return res.status(200).json(result);
});
}
};
UI Code
UI เป็น React Code เรียบง่าย ดึงข้อมูลจาก API ด้วย SWR
export default function Home() {
function refresh(e) {
e.preventDefault();
window.location.reload();
}
const { data, error } = useSWR("api/data", fetcher);
if (error) return "An error has occurred.";
if (!data) return "Loading...";
return (
<div className={styles.container}>
<Head>
<title>Covid Tracker</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Covid Tracker</h1>
<p className={styles.description}>
Top 10 countries with the most cases today
</p>
<div className={styles.grid}>
<div className={styles.card} onClick={refresh}>
<table className={styles.table}>
<thead>
<tr>
<th>Country</th>
<th>Today Cases</th>
<th>Today Deaths</th>
</tr>
</thead>
<tbody>
{data.data.map((item) => (
<tr>
<td>{item.country}</td>
<td>{item.todayCases}</td>
<td>{item.todayDeaths}</td>
</tr>
))}
</tbody>
</table>
<br />
<em>
Loaded from {data.type} in <b>{data.latency}</b> milliseconds.
Click to reload.
</em>
</div>
</div>
</main>
<footer className={styles.footer}>
This is a sample project for the blogpost
<a href="https://blog.upstash.com/nextjs-caching-with-redis" target="_blank" rel="noopener noreferrer">
Speed up your Next.js application using Serverless Redis for caching
</a>
</footer>
</div>
);
}
External Links
https://swr.vercel.app/docs/with-nextjs
https://brianlovin.com/writing/caching-api-routes-with-next-js