Rate Limiting Next.js API Routes using Cloudflare

Next.js API Routes are a powerful feature of Next.js, and you can effectively design and run your entire REST API from them. However, I run Next.js on Vercel, and unfortunately they do not provide a first party solution for rate limiting (I asked for one in GitHub Discussions).
Vercel recommends their Upstash integration, but I found the Upstash console confusing and having to use Redis just for this one feature seemed to be overkill. On the other hand, Cloudflare is primarily a cloud security platform and rate limiting is something they have robust solutions for and a comprehensive article on.
I attempted the following before landing on this solution:
Follow these 3 steps to use Cloudflare to rate limit your API Routes:
Step 1: Use Cloudflare With Proxy Enabled
First open the Cloudflare dashboard and select your website / domain.
Go to DNS → Records.
Make sure Proxy status is turned on.
Go to SSL/TLS → Overview.
Make sure SSL/TLS encryption mode is set to Full.


Step 2: Set Up Page Rule for Vercel Domain Verification Checks
Go to Rules → Page Rules → Create Page Rule
For your URL use *<YOUR_DOMAIN>/.well-known/acme-challenge/*
. Use the first asterisk *
wildcard to match all your subdomains. Pick the SSL
setting and select Off
. Vercel needs this path whitelisted to do domain verification checks.

Step 3: Set up Rate Limiting on Domain / Website
Go to Security → WAF → Rate Limiting rules → Create rule
Create a rate limiting rule that checks if the incoming request path starts with /api
with your desired rate limit (e.g. 10 requests / 10 seconds).

See it in Action
If you hit https://thomas.wang/api more than 10 times in 10 seconds and you’ll get:

HTTP/1.1 429 Too Many Requests
Date: Sat, 28 Jan 2023 23:41:15 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 16
Connection: close
Retry-After: 9
X-Frame-Options: SAMEORIGIN
Referrer-Policy: same-origin
Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Report-To: {"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v3?s=P5oxpN13lxERhT8b110mjwqdHvnazEtWVbwLNNmotg%2F4X%2FV%2FsGf8YRkGhd3YyVNwQjDlD%2FMiEqZzioygfQBovZ7QY8dap8fSj19g54dmmUHFz4ZE9kelL3QL3JnVJw%3D%3D"}],"group":"cf-nel","max_age":604800}
NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
Strict-Transport-Security: max-age=15552000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Server: cloudflare
CF-RAY: 790d93e96ee02ec1-LAX
alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
error code: 1015