Rate Limiting در Next.js: محافظت از API Routeها با Upstash Redis
نویسنده: آرش لطیفی
راهنمای عملی محدودسازی درخواست در Route Handlerهای Next.js با Upstash Redis و fallback حافظهای، همراه با پاسخ 429 و هدر Retry-After.
خلاصه: برای هر Route Handler حساس یک لایه محدودیت بگذار: کلید درخواست را بشمار، از پاسخ 429 با هدر
Retry-Afterاستفاده کن و برای سرور بدون Redis یک fallback حافظهای داشته باش.
اگر یک Route Handler داری که هزینهبر است — ارسال پیامک، ساخت تصویر، تماس به درگاه پرداخت، ثبتنام — بدون محدودیت یعنی هر کسی میتواند با یک حلقه ساده کل سهمیهات را مصرف کند. کاربر واقعی هم قفل میشود و پشتیبانیات پیامهای «سایت کند شده» میگیرد.
در این راهنما دو لایه میسازیم: لایه اصلی روی Redis (که بین چند نمونه سرور مشترک است) و یک fallback ساده حافظهای برای محیط توسعه و جاهایی که Redis نداری.
معماری کلی همان چیزی است که در ذخیرهسازی تصویر روی R2r2-image-storage-tech توضیح دادم: یک سرویس بیرونی که stateless بودن اپلیکیشن را حفظ میکند. اینجا هم اگر Rate Limiter را داخل حافظه هر نمونه نگه داری، با اضافه شدن نمونه جدید همهچیز از صفر شروع میشود.
لایه اول: Redis با sliding window
اول کتابخانهها را نصب کن: npm i @upstash/redis @upstash/ratelimit.
بعد یک نمونه مشترک بساز. @upstash/ratelimit خودش کش شدن را مدیریت میکند، پس این singleton را یک بار در ماژول بساز و در همه Routeها import کن:
حالا همان limiter را داخل Route Handler صدا بزن و در صورت رد شدن، پاسخ 429 برگردان:
نکته مهم: کلید را بر اساس کار انتخاب کن، نه فقط IP. برای ورود با پیامک، کلید ترکیبی otp:${ip}:${phone} بهتر است تا یک آیپی اشتراکی در اینترنت ایران کل محدودیت را مصرف نکند.
لایه دوم: fallback حافظهای
اگر محیط deploy شما Redis ندارد یا هنوز env آن کامل نشده، یک شمارنده ساده در ماژول بساز. این روش روی یک نمونه سرور کار میکند و برای dev و تست کافی است:
فقط حواست باشد buckets را با یک بازه پاکسازی سبک کوتاه نگه داری، وگرنه در طول زمان رشد میکند. برای دیموی محلی کافی است، ولی روی سرور چند نمونهای این را جایگزین Redis نکن.
انتخاب سقف و کلید
| مسیر حساس | سقف پیشنهادی | کلید پیشنهادی | دلیل | |---|---|---|---| | ارسال OTP پیامکی | ۳ در دقیقه | آیپی + شماره | هر پیامک هزینه واقعی دارد | | ثبتنام / ورود | ۵ در ۱۵ دقیقه | آیپی | جلوگیری از حمله brute-force | | API عمومی خواندنی | ۶۰ در دقیقه | آیپی یا کلید کاربر | کنترل هزینه دیتابیس | | نرخ تبدیل ارسالی | ۱۰۰ در ساعت | کلید کاربر | جلوگیری از سوءاستفاده از تخفیف |
قاعده کلی: سقف را بر اساس رفتار عادی کاربر انتخاب کن، بعد کمی سخاوتمندانهتر. محدودیتی که کاربر واقعی را میبندد، یعنی محدودیتی که از آن متنفری.
پاسخ درست به کاربر فارسیزبان
خطای 429 بدون پیام فارسی یعنی کاربر فکر میکند سایت خراب است. پیام را ساده و بدون اصطلاح فنی بنویس و بگو کِی دوباره تلاش کند:
چکلیست قبل از رفتن به پروداکشن
- [ ] سقف برای هر مسیر حساس جدا تنظیم شده باشد، نه یک عدد سراسری.
- [ ] کلید بر اساس کار انتخاب شده باشد، نه فقط IP.
- [ ] پاسخ 429 همراه با
Retry-Afterبرگردد. - [ ] متغیرهای محیطی Redis در پروداکشن ست شده باشند.
- [ ] مسیرهای حساس لاگ خطا را هم ثبت کنند تا الگوی حمله دیده شود.
- [ ] محدودیت روی خود Middleware احراز هویت هم سوار شده باشد.
تست سریع
با یک حلقه کوچک، همان مسیر را ۲۰ بار صدا بزن و ببین از بار چنجم به بعد 429 برمیگردد. اگر همه پاسخ 200 گرفتند، یعنی کلید یا هدر پروکسی درست خوانده نمیشود — معمولاً مشکل از x-forwarded-for است.
اشتباههای رایج
- فقط محدود کردن ورودی و نه مسیر حساس. ورودی رندر میشود، ولی هزینه واقعی در ارسال پیامک و تماس با API بیرونی است.
- پاک کردن کامل محدودیت با هر deploy. اگر کلید بر اساس نسخه باشد، هر بار همهچیز باز میشود.
- بیتوجهی به آیپیهای اشتراکی. در ایران خیلیها یک آیپی ثابت دارند؛ کلید ترکیبی نجاتبخش است.
اگر محدودیت را در لایه سرور میگذاری، معمولاً منطق دسترسی را هم در همان فایل middleware نگه میداری — راهنمای احراز هویت با Middleware همان الگو را نشان میدهد.
Q: محدودیت را در Middleware بگذارم یا داخل Route؟
داخل Route دقیقتر است چون فقط همان کد اجرا میشود. Middleware سریعتر است و کل درخواستهای مسیر را میبیند. برای احراز هویت، Middleware؛ برای هزینههای API، داخل Route.
Q: اگر چند نمونه سرور داشته باشم چه میشود؟
همین دلیلی است که Redis لازم داری: شمارنده حافظهای بین نمونهها مشترک نیست و محدودیت واقعی سختتر میشود. با Redis همه نمونهها یک شمارنده میبینند.
Q: سقف پلن رایگان Redis برای پروداکشن کافی است؟
سقف ماهانهاش محدود است و برای پروداکشن جدی بهتنهایی کافی نیست؛ برای تست و پروژههای کوچک مناسب است. اگر ترافیکت زیاد شد سرویس را عوض کن — منطق کلیدسازیات تغییری نمیکند.