Rate limiting isn’t “npm install + two lines in nginx.”
Most tutorials make it look trivial: add a throttler package to NestJS, add limit_req to nginx, done.
Problem 1: your server sees the proxy, not the user.
Behind nginx, every request arrives from the proxy’s IP. So the whole site shared ONE rate limit. Ten login attempts per minute for everyone. Anyone could lock all users out.
The fix is one line in main.ts (app.set('trust proxy', 1)) plus forwarding X-Forwarded-For in nginx. The number matters: true trusts any client-supplied header and lets attackers spoof their IP. 1 trusts exactly one hop.
Problem 2: CSR and SSR see different IPs.
Client-side calls (login, refresh) go browser --> nginx --> API, so the API sees the real user IP.
But with Next.js SSR on a serverless platform like Vercel, the page is rendered on Vercel’s servers. The API sees a pool of cloud IPs, not your visitors. Every SSR visitor shares the same few IPs, and therefore the same quota.
The fix: the SSR layer forwards the visitor’s IP in a custom header, signed with a shared secret. The API only trusts that header when the secret matches. Without the secret check, anyone could forge the header and bypass your limits.
Lesson: rate limiting is an architecture problem, not a package. Know where your IP is lost, and who you trust.