ในโพสต์นี้ เราจะสร้างห้องรอสำหรับแอปพลิเคชัน Next.js ของคุณโดยใช้ฟังก์ชัน Vercel Edge และ Upstash Redis
คุณสามารถตรวจสอบซอร์สโค้ดและแอปสาธิตได้
ห้องรอ?
ห้องรอมีประโยชน์เมื่อคุณต้องการจำกัดจำนวนผู้เข้าชมเว็บไซต์ของคุณเพื่อไม่ให้ใช้ทรัพยากรมากเกินไป
ในการใช้งานของเรา คุณจะสามารถกำหนดจำนวนผู้เยี่ยมชมที่ใช้งานได้สูงสุด จะมีพารามิเตอร์สองตัวเพื่อควบคุมการรับส่งข้อมูล:
- ความจุเว็บไซต์สูงสุด:จำนวนผู้เข้าชมเว็บไซต์สูงสุดพร้อมกันหรือไม่
- หมดเวลาเซสชันสูงสุด:จำนวนวินาทีสูงสุดที่ผู้เยี่ยมชมสามารถอยู่นิ่งได้
ขั้นตอนที่ 1:การตั้งค่าโครงการ
สร้างแอป Next.js:
examples git:(master) ✗ npx create-next-app@latest --typescript
✔ What is your project named? … nextjs-waiting-room
Creating a new Next.js app in /Users/enes/dev/examples/nextjs-waiting-room.
ติดตั้ง upstash-redis:
npm install @upstash/redis
ขั้นตอนที่ 2:การนำไปใช้
Vercel รองรับฟังก์ชัน Edge ผ่านมิดเดิลแวร์ Next.js ดังนั้นเราจะเพิ่ม _middleware.ts ภายใต้ pages/api/
. รหัสมิดเดิลแวร์สกัดกั้นคำขอทั้งหมดที่ส่งไปยัง /api สำหรับการกำหนดค่าต่างๆ ดูที่นี่
อัปเดต pages/api/_middleware.ts
ดังต่อไปนี้:
import { Redis } from "@upstash/redis";
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
const COOKIE_NAME_ID = "__waiting_room_id";
const COOKIE_NAME_TIME = "__waiting_room_last_update_time";
const UPSTASH_REDIS_REST_TOKEN = "REPLACE_HERE";
const UPSTASH_REDIS_REST_URL = "REPLACE_HERE";
const TOTAL_ACTIVE_USERS = 10;
const SESSION_DURATION_SECONDS = 30;
const redis = new Redis({
url: UPSTASH_REDIS_REST_URL,
token: UPSTASH_REDIS_REST_TOKEN,
});
export async function middleware(req: NextRequest, ev: NextFetchEvent) {
const cookies = req.cookies;
let userId;
if (cookies[COOKIE_NAME_ID] != null) {
userId = cookies[COOKIE_NAME_ID];
} else {
userId = makeid(8);
}
const size = await redis.dbsize();
console.log("current capacity:" + size);
// there is enough capacity
if (size < TOTAL_ACTIVE_USERS) {
return getDefaultResponse(req, userId);
} else {
// site capacity is full
const user = await redis.get(userId);
if (user === "1") {
// the user has already active session
return getDefaultResponse(req, userId);
} else {
// capacity is full so the user is forwarded to waiting room
return getWaitingRoomResponse();
}
}
}
function makeid(length: number) {
let result = "";
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
async function getDefaultResponse(request: NextRequest, userId: string) {
// uncomment below to test the function with a static html content
let newResponse = new NextResponse(default_html);
newResponse.headers.set("content-type", "text/html;charset=UTF-8");
// const response = await fetch(request)
// const newResponse = new Response(response.body, response)
const cookies = request.cookies;
const now = Date.now();
let lastUpdate = cookies[COOKIE_NAME_TIME];
let lastUpdateTime = 0;
if (lastUpdate) lastUpdateTime = parseInt(lastUpdate);
const diff = now - lastUpdateTime;
const updateInterval = (SESSION_DURATION_SECONDS * 1000) / 2;
if (diff > updateInterval) {
await redis.setex(userId, SESSION_DURATION_SECONDS, "1");
newResponse.cookie(COOKIE_NAME_TIME, now.toString());
}
newResponse.cookie(COOKIE_NAME_ID, userId);
return newResponse;
}
async function getWaitingRoomResponse() {
const newResponse = new NextResponse(waiting_room_html);
newResponse.headers.set("content-type", "text/html;charset=UTF-8");
return newResponse;
}
const waiting_room_html = `
<title>Waiting Room</title>
<meta http-equiv='refresh' content='30' />
<style>*{box-sizing:border-box;margin:0;padding:0}body{line-height:1.4;font-size:1rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;padding:2rem;display:grid;place-items:center;min-height:100vh}.container{width:100%;max-width:800px}p{margin-top:.5rem}</style>
<div class='container'>
<h1>
<div>You are now in line.</div>
<div>Thanks for your patience.</div>
</h1>
<p>We are experiencing a high volume of traffic. Please sit tight and we will let you in soon. </p>
<p><b>This page will automatically refresh, please do not close your browser.</b></p>
</div>
`;
const default_html = `
<title>Waiting Room Demo</title>
<style>*{box-sizing:border-box;margin:0;padding:0}body{line-height:1.4;font-size:1rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;padding:2rem;display:grid;place-items:center;min-height:100vh}.container{width:100%;max-width:800px}p{margin-top:.5rem}</style>
<div class="container">
<h1>
<div>Waiting Room Demo</div>
</h1>
<p>
Visit this site from a different browser, you will be forwarded to the waiting room when the capacity is full.
</p>
<p> Check <a href='//github.com/upstash/redis-examples/tree/master/nextjs-waiting-room' style={{"color": "blue"}}>this project </a> to set up a waiting room for your website.</p>
</div>
`;
เราใช้ Upstash Redis เป็นที่เก็บสถานะเพื่อเก็บเซสชันผู้ใช้ที่ใช้งานอยู่ ด้วย REST API ทำให้ upstash-redis เข้ากันได้กับฟังก์ชัน Vercel Edge
คุณต้องสร้างฐานข้อมูลส่วนกลางจากคอนโซล Upstash คัดลอกและวางโทเค็น REST และ REST URL จากคอนโซล ฐานข้อมูล Redis ควรว่างเปล่าและใช้โดยแอปพลิเคชันนี้เท่านั้น
ตั้งค่า TOTAL_ACTIVE_USERS และ SESSION_DURATION_SECONDS ด้วย ขึ้นอยู่กับความต้องการของคุณเอง
แอปพลิเคชันสร้างรหัสเฉพาะสำหรับผู้เยี่ยมชมใหม่และตั้งเป็นคุกกี้และพุชไปที่ Redis ดังนั้นในครั้งต่อไป แอปพลิเคชันจะตรวจสอบว่าผู้เยี่ยมชมได้ตรวจสอบเซสชัน Redis แล้วหรือยัง ขณะแทรกลงใน Redis จะตั้งเวลาหมดอายุเป็นการหมดเวลาของเซสชันที่ไม่ได้ใช้งาน หากจำนวนเซสชันเกินความจุสูงสุด ผู้ใช้ใหม่จะถูกส่งต่อไปยังหน้าห้องรอ
คุณสามารถอัปเดต waiting_room_html
เพื่อปรับแต่งหน้าห้องรอ
คุณสามารถอัปเดต getDefaultResponse()
วิธีการส่งต่อไปยังหน้าของคุณเองโดยใช้ NextResponse
ขั้นตอนที่ 3:เรียกใช้และปรับใช้
เรียกใช้แอปพลิเคชันในเครื่องโดย npm run dev
. คุณอาจต้องการตั้งค่า 1 เป็น TOTAL_ACTIVE_USERS
และเปิดหน้า (https://localhost:3000/api/hello) ในเบราว์เซอร์ต่างๆ เพื่อทดสอบห้องรออย่างง่ายดาย
คุณสามารถปรับใช้แอปพลิเคชันของคุณกับ Vercel โดย
vercel deploy –prod
Vercel จะเรียกใช้ _middleware.ts ในตำแหน่งขอบเพื่อลดเวลาในการตอบสนองทั่วโลก
บทสรุป
บทช่วยสอนนี้แสดงให้เห็นว่าการสร้างแอปพลิเคชันแบบไดนามิกที่ Edge นั้นง่ายเพียงใดด้วย Vercel และ Upstash ตรวจสอบตัวอย่างของเราสำหรับตัวอย่างเพิ่มเติม
รอความคิดเห็นของคุณใน Twitter หรือ Discord