Netlify’s rate limiting for Functions is surprisingly effective at preventing runaway costs and abuse, even if you don’t realize it’s happening.
Let’s say you have a Netlify Function that’s supposed to fetch data from an external API and return it.
// netlify/functions/fetch-data.js
const fetch = require('node-fetch');
exports.handler = async (event, context) => {
try {
const externalApiUrl = 'https://api.example.com/data';
const response = await fetch(externalApiUrl);
const data = await response.json();
return {
statusCode: 200,
body: JSON.stringify(data),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Failed to fetch data' }),
};
}
};
Without rate limiting, if a malicious actor or a bug in your frontend repeatedly hits this function, you could rack up significant bills from the external API, or even hit Netlify’s Function execution limits, leading to errors.
Netlify’s rate limiting acts as a protective shield around your Functions. It’s not just about preventing DoS attacks; it’s about cost control and ensuring your application remains stable. The system works by tracking requests to your Functions based on different identifiers.
Here’s how it breaks down internally:
- Request Tracking: Netlify monitors incoming requests to your Function endpoints.
- Identifier-Based Limits: Limits are applied based on specific identifiers. The most common are:
- IP Address: The default and most basic level of protection.
- User ID (for authenticated users): If you’re using Netlify Identity or another auth provider, you can tie limits to individual users.
- Custom Headers/Parameters: You can define specific request headers or query parameters to use as keys for rate limiting.
- Configurable Limits: You can set the maximum number of requests allowed within a given time window (e.g., 100 requests per minute).
- Enforcement: When a limit is reached for a specific identifier, subsequent requests from that identifier within the window are rejected with a
429 Too Many Requestsstatus code.
You configure these limits in your netlify.toml file. For example, to limit requests to your fetch-data function based on the client’s IP address:
[[functions.edge_functions]]
name = "fetch-data"
pattern = "/.netlify/functions/fetch-data"
rate_limit = { limit = 100, window = 60 } # 100 requests per 60 seconds per IP
This configuration means that any single IP address can only call the /api/fetch-data endpoint 100 times within any 60-second period. If they exceed this, they’ll get a 429 response.
You can also apply limits based on authenticated users, which is crucial for protecting against abuse from your own user base. If you’re using Netlify Identity, you can access the user’s ID within your Function.
// netlify/functions/fetch-data-user.js
const fetch = require('node-fetch');
exports.handler = async (event, context) => {
// Ensure user is logged in
if (!context.clientContext.user) {
return {
statusCode: 401,
body: JSON.stringify({ error: 'Unauthorized' }),
};
}
const userId = context.clientContext.user.sub; // Unique user identifier
try {
const externalApiUrl = 'https://api.example.com/data';
const response = await fetch(externalApiUrl);
const data = await response.json();
return {
statusCode: 200,
body: JSON.stringify(data),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Failed to fetch data' }),
};
}
};
And in your netlify.toml:
[[functions.edge_functions]]
name = "fetch-data-user"
pattern = "/.netlify/functions/fetch-data-user"
rate_limit = { limit = 60, window = 60, identifier = "user" } # 60 requests per 60 seconds per user
The identifier = "user" tells Netlify to use the authenticated user’s ID as the basis for rate limiting. This is far more granular than IP-based limiting for user-facing APIs.
The window is specified in seconds. So, window = 60 means a 60-second sliding window. If a user makes 100 requests at T=0, and then 1 request at T=59, they are still within their limit. If they make another request at T=60, the first request (at T=0) falls out of the window, and they are allowed to proceed.
One thing most people don’t realize is that Netlify’s rate limiting for Functions is applied before the Function code even starts executing. This is critical because it means you don’t incur Function execution costs or trigger external API calls for requests that are immediately blocked by the rate limiter. The 429 response is served directly by the edge network.
If you’ve configured rate limiting and are seeing 429 errors in your logs, it’s not necessarily a bug. It’s your system telling you that the configured limits are being hit. The next step is to investigate why those limits are being hit and adjust them if necessary, or implement client-side strategies to reduce request frequency.
The next error you’ll likely encounter after successfully implementing and understanding rate limiting is dealing with specific error handling for 429 responses on the client-side to provide a better user experience.