Vercel’s API routes are surprisingly resilient to abuse, but hitting them too hard without a plan can lead to unexpected 429 Too Many Requests errors and a denial of service for your users.
Let’s see this in action. Imagine a simple Vercel API route designed to fetch data from a third-party API:
// pages/api/external-data.js
export default async function handler(req, res) {
const apiKey = process.env.EXTERNAL_API_KEY;
const externalApiUrl = 'https://api.example.com/data';
try {
const apiResponse = await fetch(externalApiUrl, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
if (!apiResponse.ok) {
// Handle potential errors from the external API
return res.status(apiResponse.status).json({ error: 'Failed to fetch data from external API' });
}
const data = await apiResponse.json();
res.status(200).json(data);
} catch (error) {
console.error('Error in /api/external-data:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
If you were to hammer this endpoint from a single client, or worse, from multiple clients all pointing to your Vercel deployment, you’d quickly encounter rate limits. Vercel, by default, applies rate limiting to protect its infrastructure and prevent DoS attacks.
The problem this solves is straightforward: uncontrolled traffic can overwhelm your serverless functions, leading to increased latency, failed requests, and potentially high costs. Without explicit rate limiting, a rogue script or a sudden surge in legitimate traffic could bring your API down.
Internally, Vercel’s edge network monitors incoming requests to your API routes. It uses algorithms to track request counts per IP address (or other identifiers) within defined time windows. When a threshold is exceeded, it injects a 429 Too Many Requests response. This isn’t just about protecting Vercel; it’s about ensuring your application remains available and performant for all users.
The primary lever you control is how you implement rate limiting. Vercel doesn’t expose granular configuration for its own internal rate limits on API routes directly in vercel.json. Instead, you build it into your API routes. The most common approach is using a library. A popular choice is express-rate-limit (even though you’re not using Express, its principles are transferable) or a more lightweight, Vercel-friendly alternative like next-rate-limiter or a custom implementation.
Here’s how you might implement a basic rate limit within your API route using a hypothetical rateLimiter utility:
// pages/api/external-data.js
import rateLimiter from '../../utils/rateLimiter'; // Assume this utility exists
const handler = async (req, res) => {
// Apply rate limiting: 100 requests per hour per IP
const { ok, limit, current } = await rateLimiter.check(req, res, {
windowMs: 60 * 60 * 1000, // 1 hour
limit: 100,
message: 'Too many requests from this IP, please try again after an hour'
});
if (!ok) {
return res.status(429).json({ message: limit.message });
}
// ... rest of your API logic as before ...
const apiKey = process.env.EXTERNAL_API_KEY;
const externalApiUrl = 'https://api.example.com/data';
try {
const apiResponse = await fetch(externalApiUrl, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
if (!apiResponse.ok) {
return res.status(apiResponse.status).json({ error: 'Failed to fetch data from external API' });
}
const data = await apiResponse.json();
res.status(200).json(data);
} catch (error) {
console.error('Error in /api/external-data:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
export default handler;
In this example, rateLimiter.check would typically inspect req.socket.remoteAddress to identify the client. If the client exceeds 100 requests within a 60-minute window, a 429 response is immediately returned. The limit object would contain the message to send back to the client.
When you implement rate limiting within your API routes, you’re not just preventing Vercel from returning a 429; you’re defining your own acceptable traffic thresholds. This is crucial because Vercel’s default limits are more about infrastructure protection, whereas your application-level limits are about user experience and resource management for your specific services. For instance, if your API route makes calls to a paid third-party API, you’ll want to limit requests to control costs, independent of Vercel’s limits.
The one thing most people don’t realize is that Vercel’s API routes, when deployed, run within a serverless environment that is inherently stateless and scales automatically. This means that a simple in-memory store for your rate limiter (like express-rate-limit might use by default) will not work correctly across different serverless function invocations. Each invocation is a new, isolated environment. To maintain a consistent rate limit count across all instances of your function, you need to use an external, shared store like Redis, an Edge KV store, or a database.
Your next step will be to manage the state of your rate limiter, often by integrating with a distributed cache like Redis or Vercel’s KV.