이 게시물에서는 Vercel Edge 기능과 Upstash Redis를 사용하여 Next.js 애플리케이션을 위한 대기실을 만들 것입니다.
소스코드와 데모앱을 확인하실 수 있습니다.
대기실?
대기실은 리소스에 과부하가 걸리지 않도록 웹사이트의 활성 방문자 수를 제한하려는 경우에 유용합니다.
구현 시 최대 활성 방문자 수를 설정할 수 있습니다. 트래픽을 제어하는 두 가지 매개변수가 있습니다.
- 최대 웹사이트 용량:웹사이트에 동시에 접속할 수 있는 최대 방문자 수
- 최대 세션 시간 초과:방문자가 유휴 상태를 유지할 수 있는 최대 시간(초)
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은 Next.js 미들웨어를 통해 Edge 기능을 지원합니다. 따라서 pages/api/
아래에 _middleware.ts를 추가합니다. . 미들웨어 코드는 /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를 설정합니다.
애플리케이션은 신규 방문자를 위한 고유 ID를 생성하고 이를 쿠키로 설정하고 이를 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를 실행합니다.
결론
이 튜토리얼은 Vercel과 Upstash 덕분에 엣지에서 동적 애플리케이션을 구축하는 것이 얼마나 쉬운지 보여줍니다. 더 많은 예를 보려면 예를 확인하세요.
Twitter 또는 Discord에서 여러분의 피드백을 기다리고 있습니다.