Giới thiệu giải pháp giới hạn tốc độ theo token cho API AI trên Next.js
Khi phát triển ứng dụng sử dụng API AI như Claude hay GPT-4o, việc giới hạn tốc độ truy cập theo số token thay vì số yêu cầu giúp kiểm soát chi phí hiệu quả. Bài viết hướng dẫn cách triển khai rate limiting chuyên nghiệp cho các route AI trên Next.js cùng mã nguồn tham khảo.

Giới thiệu giải pháp giới hạn tốc độ theo token cho API AI trên Next.js
Điều quan trọng khi xây dựng ứng dụng sử dụng các API ngôn ngữ lớn (LLM) như Claude hay GPT-4o là cần có cơ chế giới hạn tốc độ (rate limiting). Nếu không, chỉ một sự kiện “viral” hay một đoạn code bị lỗi lặp vô hạn có thể khiến bạn tiêu hết ngân sách API trong vài giờ.
Bài viết sẽ hướng dẫn một giải pháp rate limiting đạt chuẩn production cho các route AI trên Next.js, đi kèm với mã nguồn thực tế để bạn dễ dàng sử dụng ngay.
Tại sao các route AI cần cách giới hạn tốc độ khác?
Các cơ chế rate limiting truyền thống dựa trên số lượng yêu cầu (request) hay địa chỉ IP hoặc user ID đã rất phổ biến. Tuy nhiên, các route AI gặp vấn đề khó hơn do mức tiêu thụ token biến động lớn.
Ví dụ, một khách gửi câu “hi” chỉ tiêu tốn khoảng 0.0001 USD trong khi một tài liệu dài 10.000 token có thể tốn đến 0.03 USD. Nếu giới hạn theo số request, bạn không hề giới hạn được chi phí thực tế.
Giải pháp là phải giới hạn theo số token dùng, thay vì theo số lần gọi API.
Hướng dẫn triển khai
1. Cài đặt Upstash Redis
Upstash cung cấp dịch vụ Redis serverless với tầng miễn phí và SDK hỗ trợ Next.js rất tiện lợi cho các dự án serverless.
npm install @upstash/redis @upstash/ratelimit
Thêm biến môi trường .env.local:
UPSTASH_REDIS_REST_URL=your_url
UPSTASH_REDIS_REST_TOKEN=your_token
2. Tạo bộ giới hạn tốc độ (rate limiter)
Tạo file src/lib/rate-limit.ts:
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// Giới hạn theo số request: 20 request / phút / user
export const requestLimiter = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(20, "1 m"),
analytics: true,
prefix: "ratelimit:requests",
});
// Giới hạn theo số token: 100.000 token / ngày / user
export const tokenLimiter = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(100_000, "24 h"),
analytics: true,
prefix: "ratelimit:tokens",
});
3. Áp dụng vào route AI
Trong src/app/api/chat/route.ts, kiểm tra giới hạn request và token trước khi gọi API Claude:
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import Anthropic from "@anthropic-ai/sdk";
import { requestLimiter, tokenLimiter } from "@/lib/rate-limit";
import { authOptions } from "@/lib/auth";
const client = new Anthropic();
export async function POST(req: NextRequest) {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const userId = session.user.id;
// Kiểm tra giới hạn số request
const { success: requestOk, remaining: requestsLeft } = await requestLimiter.limit(userId);
if (!requestOk) {
return NextResponse.json(
{ error: "Too many requests. Please wait a minute." },
{ status: 429, headers: { "Retry-After": "60" } }
);
}
const { messages } = await req.json();
// Ước lượng số token đầu vào (token ước tính bằng độ dài / 4)
const estimatedInputTokens = messages.reduce((sum: number, m: any) => sum + m.content.length / 4, 0);
// Kiểm tra giới hạn số token
const { success: tokenOk } = await tokenLimiter.limit(userId, { rate: estimatedInputTokens });
if (!tokenOk) {
return NextResponse.json(
{ error: "Daily token limit reached. Resets at midnight UTC." },
{ status: 429 }
);
}
// Gọi API AI
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages,
});
// Tính số token thực tế đã dùng (input + output)
const actualTokens = response.usage.input_tokens + response.usage.output_tokens;
const adjustment = actualTokens - estimatedInputTokens;
if (adjustment > 0) {
// Điều chỉnh lại số token nếu ước lượng ban đầu thiếu
await tokenLimiter.limit(userId, { rate: adjustment });
}
return NextResponse.json({
content: response.content[0].type === "text" ? response.content[0].text : "",
usage: response.usage,
limits: { requestsRemaining: requestsLeft },
});
}
4. Hiển thị giới hạn cho người dùng
Khi sắp hết giới hạn request, hãy thông báo để người dùng không bị bực bội vì bị chặn đột ngột:
const { data } = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages }),
}).then(r => r.json());
if (data.limits?.requestsRemaining < 5) {
showToast(`${data.limits.requestsRemaining} requests remaining this minute`);
}
Thiết lập giới hạn theo gói người dùng
Bạn có thể tuỳ chỉnh giới hạn theo các gói trả phí (free, pro) để tối ưu kinh doanh:
export async function getRateLimiters(userId: string, plan: "free" | "pro") {
const limits = {
free: { requests: 10, tokens: 50_000 },
pro: { requests: 100, tokens: 500_000 },
};
const { requests, tokens } = limits[plan];
return {
requestLimiter: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(requests, "1 m"),
prefix: `ratelimit:${plan}:requests`,
}),
tokenLimiter: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(tokens, "24 h"),
prefix: `ratelimit:${plan}:tokens`,
}),
};
}
Xử lý lỗi 429 (quá giới hạn) trên frontend
Frontend nên bắt lỗi 429 để thông báo thân thiện, không làm gián đoạn trải nghiệm:
const sendMessage = async (content: string) => {
try {
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages: [...history, { role: "user", content }] }),
});
if (res.status === 429) {
const retryAfter = res.headers.get("Retry-After");
setError(
retryAfter
? `Rate limit hit. Try again in ${retryAfter} seconds.`
: "Daily limit reached. Resets at midnight UTC."
);
return;
}
const data = await res.json();
// xử lý thành công
} catch (e) {
setError("Something went wrong. Please try again.");
}
};
Giám sát chi phí sử dụng
Sau mỗi lần gọi API, bạn nên ghi lại lượng token tiêu thụ theo user vào cơ sở dữ liệu để theo dõi và ra quyết định:
await db.aiUsage.create({
data: {
userId,
inputTokens: response.usage.input_tokens,
outputTokens: response.usage.output_tokens,
model: "claude-sonnet-4-6",
cost: calculateCost(response.usage),
createdAt: new Date(),
},
});
Bạn có thể xây dựng dashboard để nhanh chóng phát hiện người dùng sử dụng bất thường, tránh hao tổn tài nguyên.
Bộ khởi tạo AI SaaS (AI SaaS Starter Kit)
Tất cả cấu hình trên (bao gồm cả auth, thanh toán Stripe, dashboard, trang landing) đã có trong AI SaaS Starter Kit - một sản phẩm mẫu bạn có thể mua với giá 99 USD. Chỉ cần clone, thêm API key và triển khai lên Vercel. Rate limiting tự động tích hợp theo session người dùng.
Giải pháp này giúp các startup, nhà phát triển tại Việt Nam có thể xây dựng ứng dụng AI an toàn, kiểm soát chi phí hiệu quả trong bối cảnh dịch vụ API đám mây tính phí dựa trên token sử dụng.



