سئوی فنی Next.js: پراکسی، کنونیکال و locale
در Next.js نسخهٔ ۱۶ میانافزار جای خود را به پراکسی داد. تلههای پراکسی، کنونیکال و locale در سایتهای چندزبانهٔ Next.js.
نوشته Roozbeh Nazari · CEO
در سایتهای چندزبانهای که با Next.js ساخته شدهاند، بیشتر ایرادهای سئوی فنی نه روی تکتک صفحات بلکه در لایهای رخ میدهند که درخواست پیش از رسیدن به صفحه از آن عبور میکند. این لایه مدتها با نام middleware شناخته میشد. با Next.js نسخهٔ ۱۶ آن قرارداد فایل منسوخ و به proxy تغییر نام داد؛ middleware.ts شد proxy.ts و نام تابع صادرشده اکنون proxy است. دلیل اعلامشدهٔ این تغییر نام آن است که اصطلاح «middleware» اغلب با میانافزار Express.js اشتباه گرفته میشد و همین باعث میشد این قابلیت گستردهتر از آنچه در نظر بوده به کار رود.
از منظر سئو این صرفاً یک تغییر نام نیست. در تیمهایی که بدون بهروزرسانی پیکربندی ارتقا میدهند، ترتیب locale و کنونیکال میتواند بیسروصدا و در نقطهای که کسی متوجه نمیشود از کار بیفتد. نکات زیر ایرادهایی را پوشش میدهند که این لایه بیشتر از همه در پیکربندیهای چندزبانهٔ Next.js تولید میکند. برای بقیهٔ ممیزی به خدمت سئوی فنی ما و چکلیست ۲۰ موردی ممیزی production نگاه کنید.
لایهای که در مهاجرت ناپدید میشود
در پروژهای که به Next.js نسخهٔ ۱۶ ارتقا یافته، اگر middleware.ts سر جایش بماند دیگر آن قرارداد فایل شناسایی نمیشود. نتیجه پیام خطا نیست بلکه تغییری خاموش در رفتار است: ریدایرکتهای locale، بازنویسیهای کوکی زبان و هدرهای افزودهشده که در آن فایل تعریف شدهاند بهسادگی دیگر اجرا نمیشوند. سایت همچنان سرویس میدهد، صفحات کد ۲۰۰ برمیگردانند، هیچ تستی نمیشکند. فقط ترتیب زبان از میان رفته است.
خود Next.js برای این مهاجرت یک codemod ارائه میکند؛ این ابزار نام فایل و نام تابع را با هم عوض میکند. برای تیمهایی که ارتقا میدهند، ترتیب درست آن است که ابتدا codemod اجرا شود و سپس رفتار locale بهصورت دستی در محیطی شبیه به production راستیآزمایی گردد. این راستیآزمایی باید با درخواستهایی انجام شود که هدر Accept-Language آنها دستی تنظیم شده است، نه در مرورگر؛ مرورگر بههرحال زبان خودش را میفرستد و میتواند مسئله را بپوشاند.
Matcher: پرهزینهترین سه خط
وقتی matcher تعریف نشده باشد، پراکسی برای هر درخواست اجرا میشود. این شامل فایلهای ایستا، مسیرهای بهینهسازی تصویر و داراییهای داخل پوشهٔ public هم میشود. ایراد معمول در پیکربندیهای چندزبانه آن است که بازنویسیای که پیشوند زبان اضافه میکند، داراییهای ایستا را هم میگیرد؛ نتیجه این است که شیوهنامهها و اسکریپتها بارگذاری نمیشوند یا به مسیرهای غیرمنتظره ریدایرکت میشوند.
- هنگام نوشتن الگوی تطبیق منفی باید مسیرهای sitemap.xml و robots.txt کنار گذاشته شوند. نشانی نقشهٔ سایتی که پیشوند زبان گرفته باشد آن نشانیای نیست که موتور جستوجو انتظارش را دارد.
- مسیرهای API باید کنار گذاشته شوند؛ نشانی API که پیشوند زبان به آن افزوده شده کار نخواهد کرد.
- مقادیر matcher باید ثابت باشند. matcherای که از یک متغیر تولید شود در زمان build قابل تحلیل نیست و بیسروصدا نادیده گرفته میشود؛ آنگاه تصور میشود پیکربندی کار میکند.
یک ظرافت دیگر: حتی وقتی در الگوی تطبیق منفی کنار گذاشته شده باشد، پراکسی همچنان برای مسیرهای داده فراخوانی میشود. این رفتار عمدی است و برای جلوگیری از این اشتباه وجود دارد که صفحهای محافظت شود اما مسیر دادهٔ متناظرش بیمحافظ بماند. در یک پیکربندی زبانی این یعنی درخواستهای داده هم از همان منطق بازنویسی عبور میکنند و آن منطق نباید نشانی مسیر داده را خراب کند.
کنونیکال: در متادیتا، نه در پراکسی
خطای رایج در پیکربندیهای چندزبانه افزودن نشانی کنونیکال بهصورت هدر در لایهٔ پراکسی است. آن لایه پیش از رندر شدن صفحه اجرا میشود و همیشه نمیداند صفحه چه محتوایی نشان خواهد داد؛ کنونیکالی که تولید میکند ممکن است با نشانی پس از بازنویسی همخوان نباشد. جای درست، شیء متادیتایی است که در سطح صفحه یا layout تعریف میشود.
در سمت متادیتا، کنونیکال و نسخههای زبانی جایگزین زیر فیلد alternates تعریف میشوند: canonical نشانی ترجیحی خود صفحه را حمل میکند و languages نشانی نسخههای زبانی را. در مسیرهای پویا این مقادیر باید داخل generateMetadata تولید شوند؛ کنونیکالی که با یک شیء متادیتای ایستا تعریف شده باشد همهٔ صفحات پویا را به یک نشانی واحد اشاره میدهد و ایراد کلاسیک قالب را میسازد.
قاعده در سمت گوگل مستقل از این است: کنونیکال یک سیگنال قوی است، نه یک دستور. ریدایرکت قویترین روش حذف نسخههای تکراری است و تگ کنونیکال در جایگاه دوم. پس وقتی سیگنالهای متناقض تولید میکنید — مثلاً کنونیکالی که به یک نشانی اشاره میکند در حالی که اعلان زبان به نشانی دیگری اشاره دارد — این شما نیستید که نتیجه را تعیین میکنید.
Locale: تلهٔ ریدایرکت خودکار
وسوسهانگیزترین کاربرد لایهٔ پراکسی، ریدایرکت بازدیدکننده به نسخهٔ زبانی بر پایهٔ زبان مرورگر اوست. این ترتیب از نظر تجربهٔ کاربری منطقی به نظر میرسد و از نظر سئو پرهزینه است. خزندهٔ موتور جستوجو با یک ترجیح زبانی واحد وارد میشود؛ وقتی ریدایرکت اجباری برقرار باشد، نسخههای زبانی دیگر هرگز خزیده نمیشوند و اعضای گروه زبانی کشف نمیگردند.
این تله را با جزئیات در نوشتهٔ ما دربارهٔ ریدایرکت خودکار زبان بررسی کردهایم. نکتهٔ خاص Next.js که باید افزود این است: وقتی منطق ریدایرکت داخل پراکسی نوشته شود، هر درخواست — بسته به matcher، از جمله درخواست داراییها — از آن عبور میکند و زنجیرههای ریدایرکت شکل میگیرد. طول زنجیره با شمار نسخههای زبانی رشد میکند.
ترتیب امن آن است که زبان پیشنهاد شود نه تحمیل: بگذارید بازدیدکننده روی همان نسخهای که هست بماند و گزینهای آشکار برای زبان دلخواهش عرضه کنید. این رویکرد هر نسخهٔ زبانی را در نشانی خودش قابل خزش نگه میدارد و انتخاب را از کاربر نمیگیرد.
Runtime و راستیآزمایی
پراکسی بهصورت پیشفرض از runtime نود استفاده میکند و گزینهٔ پیکربندی runtime را نمیتوان داخل این فایل تنظیم کرد؛ تلاش برای تنظیم آن خطا میدهد. بنابراین اعلانهای runtime که از پیکربندیهای قدیمیتر باقی ماندهاند باید هنگام ارتقا پاک شوند.
در بحث راستیآزمایی، ابزارهای تست معرفیشده در Next.js نسخهٔ ۱۵٫۱ به شما امکان میدهند با تستهای واحد ادعا کنید فایل پراکسی روی کدام نشانیها اجرا میشود. در پیکربندیهای چندزبانه بازده این کار ملموس است: چند تست که ادعا کنند مسیرهای sitemap و robots و داراییها بیرون از پوشش پراکسی و مسیرهای دارای پیشوند زبان درون آن قرار میگیرند، ایرادهایی را که در production هفتهها طول میکشد تا دیده شوند در زمان build میگیرند.
قاعدهٔ عملی برای کل این لایه: پراکسی باید برای ریدایرکت و بازنویسی به کار رود، نه برای سیگنالهایی که هویت یک صفحه را برقرار میکنند. کنونیکال، اعلانهای زبانی و تگ عنوان به لایهٔ صفحه تعلق دارند. وقتی این دو در هم بیامیزند، ایراد حاصل روی یک صفحه نیست بلکه در سراسر قالب است و معمولاً با شمار صفحات مقیاس میگیرد.
چکلیست پس از ارتقا
در پروژهای چندزبانه که به Next.js نسخهٔ ۱۶ منتقل میشود، پس از پایان ارتقا فهرست کوتاهی هست که باید دستی اجرا شود. این فهرست جایگزین تستهای خودکار نیست؛ برای گرفتن آن دسته از ایرادها وجود دارد که «کار میکند اما اشتباه کار میکند» و تستهای خودکار پوششش نمیدهند.
- آیا proxy.ts در ریشه هست و نام تابع صادرشده proxy است؟ فایلی که با نام قدیمی مانده باشد بیسروصدا نادیده گرفته میشود.
- با تنظیم دستی هدر Accept-Language، آیا هر نسخهٔ زبانی در نشانی خودش کد ۲۰۰ برمیگرداند یا همه چیز به یک نسخه ریدایرکت میشود؟
- آیا sitemap.xml و robots.txt بدون گرفتن پیشوند زبان در دسترساند؟
- در یک صفحهٔ پویا، آیا کنونیکال به نشانی خود آن صفحه اشاره میکند یا به یک نشانی واحد در سراسر قالب؟
- آیا اعلانهای زبانی متقابلاند و به همان نشانیای اشاره میکنند که کنونیکال اشاره دارد؟