وقتی اسلاگ فارسی در Next.js درست کار نمیکنه؛ مشکل Double URL Encoding
بررسی یک باگ در Dynamic Routeهای Next.js که باعث میشه اسلاگهای فارسی در محیط Production درست پردازش نشن. در این یادداشت با مفهوم Double URL Encoding، نحوهی تشخیص مشکل و راهکار Normalize کردن Route Parameters با decodeURIComponent آشنا میشیم.
گاهی ممکنه یک صفحه در محیط Development بدون هیچ مشکلی کار کنه، اما بعد از انتشار روی سرور، فقط بعضی از آدرسها با خطای 404 مواجه بشن.
این مشکل بهخصوص وقتی از اسلاگهای فارسی یا کاراکترهای غیر ASCII در Dynamic Routeهای Next.js استفاده میکنید، میتونه گیجکننده باشه.
در این یادداشت، یک مشکل واقعی از این نوع رو بررسی میکنیم: صفحهی دستهبندی و برچسب در Next.js با وجود دریافت پاسخ موفق از API برای اطلاعات اولیه، محتوای اصلی رو نمایش نمیداد.
ریشهی مشکل نه Cache بود، نه ISR و نه تنظیمات سرور؛ مسئله از چندبار Encode شدن پارامترهای مسیر (Double URL Encoding) و ناهماهنگی بین مقادیر رمزگذاریشده و رمزگشاییشده میاومد.
مشکل از کجا شروع شد؟
فرض کنید یک وبلاگ با آدرسهای زیر دارید:
متن ساده
/categories/تکنولوژی
/tags/احراز-هویت
این آدرسها در ظاهر کاملاً عادی هستن، اما مرورگر برای انتقال کاراکترهای فارسی در URL از مکانیزم Percent-Encoding استفاده میکنه.
برای مثال، اسلاگتکنولوژیبه شکلی شبیه این تبدیل میشه:
متن ساده
%D8%AA%DA%A9%D9%86%D9%88%D9%84%D9%88%DA%98%DB%8C
این رفتار کاملاً طبیعیه و بهخودیخود مشکلی ایجاد نمیکنه.
مشکل زمانی شروع میشه که بخشی از برنامه تصور کنه مقدار دریافتشده از Route هنوز Encode نشده و دوباره اون رو Encode کنه.
حالا اگر سرور این مقدار رو فقط یک بار Decode کنه، بهجای اسلاگ فارسی، همچنان یک رشتهی Percent-Encoded دریافت میکنه.
بنابراین مقایسهی اسلاگ با مقدار واقعی دیتابیس شکست میخوره و ممکنه API پاسخ 404 بده یا نتیجهی خالی برگردونه.
چرا این مشکل فقط در Production دیده میشه؟
در این مورد، بررسی درخواستهای واقعی نشون داد که رفتار بخشهای مختلف برنامه در برخورد با Route Parameters یکسان نبود.
برای مثال، در یک مسیر، اطلاعات Metadata با اسلاگ صحیح دریافت میشد، اما هنگام رندر محتوای صفحه، همون پارامتر با یک لایه Encoding اضافی به API ارسال میشد.
نتیجه این بود که:
عنوان و Metadata صفحه درست بودن.
درخواست مربوط به اطلاعات اصلی دستهبندی با اسلاگ Double-Encoded ارسال میشد.
جستوجوی برچسبها بهدلیل مقایسهی مقدار Encodeشده با اسلاگ Decodeشده نتیجهای پیدا نمیکرد.
صفحه با وجود پاسخ HTTP 200، محتوای مورد انتظار رو نمایش نمیداد.
نکتهی مهم اینه که این رفتار رو نباید بهعنوان یک قانون عمومی برای Next.js در نظر گرفت. مستندات Next.js، پارامترهای Dynamic Route رو بهعنوان مقادیرparamsدر اختیارpageوgenerateMetadataقرار میدن، اما نباید بدون بررسی فرض کنیم که تمام مسیرهای پردازش پارامتر در هر محیطی دقیقاً یک نمایش یکسان دارن.
بنابراین بهتره بهجای حدس زدن، مقدار واقعی پارامتر و URL درخواست خروجی رو در هر مرحله بررسی کنیم.
چطور مشکل رو پیدا کنیم؟
اولین اشتباه رایج اینه که وقتی صفحه در Production درست نمایش داده نمیشه، بلافاصله سراغ Cache یا تنظیمات سرور بریم.
اما قبل از تغییر تنظیمات، بهتره زنجیرهی پردازش URL رو بررسی کنیم.
چند مورد مهم برای Debug کردن:
۱. مقدار Route Parameter رو بررسی کنید.
درpage.tsxوgenerateMetadataمقدارslugرو بررسی کنید. آیا مقدار دریافتی فارسیه یا یک رشتهی Percent-Encoded؟
۲. URL واقعی درخواست API رو بررسی کنید.
صرفاً به مقدار ورودی تابع اکتفا نکنید. URL نهایی Fetch رو بررسی کنید.
اگر در یک اسلاگ فارسی، رشتههایی مثل%25D8مشاهده کردید، احتمال Double Encoding وجود داره.
۳. پاسخ API رو بهتنهایی بررسی کنید.
همون URL رو مستقیماً درخواست بدید و ببینید API چه پاسخی برمیگردونه.
در این مرحله باید تفاوت بین سه حالت رو تشخیص بدید:
404بهدلیل پیدا نشدن Route در Backend
404بهدلیل پیدا نشدن رکورد با اسلاگ اشتباه
پاسخ200با نتیجهی خالی بهدلیل اشتباه بودن فیلتر
۴. فقط به HTTP 200 اعتماد نکنید.
ممکنه درخواست اصلی صفحه با وضعیت 200 برگرده، اما در محتوای HTML هیچکدوم از اطلاعات مورد انتظار وجود نداشته باشه.
پس محتوای رندرشده، عنوان صفحه و کارتهای مقاله رو هم بررسی کنید.
راهحل: Normalize کردن Route Parameter
راهحل این مشکل اینه که پارامتر Route رو در اولین نقطهی ورود به منطق برنامه Normalize کنیم.
یعنی قبل از اینکهslugبه API، توابع جستوجو، Metadata یا کامپوننتهای Client ارسال بشه، مطمئن بشیم مقدارش در یک قالب مشخص قرار داره.
اگر مقدار Encode شده باشه، یک بار اون رو Decode میکنه.
اگر مقدار از قبل Decode شده باشه، همون مقدار رو برمیگردونه.
اگر رشتهی ورودی دارای Encoding نامعتبر باشه، برنامه بهدلیلURIErrorمتوقف نمیشه.
هیچوقت بهصورت بازگشتی Decode انجام نمیده.
نکتهی آخر خیلی مهمه. هدف ما این نیست که هر چقدر لازم بود Decode کنیم تا به یک رشتهی ظاهراً خوانا برسیم. فقط باید یک مرحله رمزگشایی در مرز ورود داده انجام بشه.
و برای API که اسلاگ رو بهصورت Query Parameter دریافت میکنه:
TypeScript
const query =newURLSearchParams({
tag: slug,
});
const url =`${API_URL}/tags?${query.toString()}`;
در هر دو حالت، تابع دریافتکنندهی اسلاگ باید مقدار Decodeشده دریافت کنه و خودش مسئول Encode کردن مقدار در مرز HTTP باشه.
همین قرارداد باید برای ساخت URLهای Canonical، Open Graph و Pagination هم رعایت بشه.
اگر تابعی انتظار اسلاگ Decodeشده داره، نباید قبل از فراخوانی اون، اسلاگ رو Encode کنیم.
چطور مطمئن بشیم مشکل دوباره برنمیگرده؟
برای چنین باگی، تست کردن فقط اسلاگهای انگلیسی کافی نیست.
بهتره چند سناریوی مشخص رو پوشش بدیم:
سناریو
نتیجهی مورد انتظار
اسلاگ انگلیسی مثلlaravel
بدون تغییر باقی بمونه
اسلاگ فارسی Encodeشده
به مقدار فارسی تبدیل بشه
اسلاگ فارسی Decodeشده
بدون تغییر باقی بمونه
مقدار%252F
فقط یک بار Decode بشه
Encoding نامعتبر
باعث Crash نشه
اسلاگ ناموجود
همچنان 404 دریافت کنه
برچسب معتبر بدون مقاله
وضعیت خالی صحیح نمایش داده بشه
علاوه بر تست تابع Decode، بهتره URLهای نهایی API هم بررسی بشن تا مطمئن بشیم فقط یک لایه Encoding دارن.
برای مثال، در تست درخواستهای API میتونیم بررسی کنیم که هیچ رشتهی%25D8ناشی از Double Encoding در URL نهایی وجود نداشته باشه.
همچنین تست باید فقط به وضعیت HTTP محدود نشه. بهتره بررسی کنیم عنوان دستهبندی، مقالات و اطلاعات مورد انتظار واقعاً در خروجی رندرشده وجود داشته باشن.
جمعبندی
مشکلات مربوط به URL Encoding همیشه در نگاه اول مشخص نیستن، بهخصوص وقتی فقط در Production اتفاق میافتن.
در این مورد، مشکل اصلی ناهماهنگی بین اسلاگ Encodeشده و Decodeشده در بخشهای مختلف برنامه بود. با Normalize کردن Route Parameter در مرز ورود و رعایت قرارداد Encode کردن فقط در مرز HTTP، میتونیم از Double Encoding جلوگیری کنیم.
یک اصل ساده که ارزش رعایت کردن داره:
داخل منطق برنامه با دادهی Decodeشده کار کنید و فقط هنگام ساخت URL اون رو Encode کنید.
این اصل علاوه بر اسلاگهای فارسی، برای هر نوع دادهای که ممکنه در URL قرار بگیره کاربرد داره.
نظرات
هنوز نظری ثبت نشده است. اولین نفر باشید!