حالة الـ URL في موجّه تطبيقات Next.js: معاملات بحث مُنمّطة تصمد أمام التحديث
جدول المحتويات
عن الكاتب

Ahmed Mahmoud
كاتب ومطوّر
مهندس برمجيات شغوف بتطوير الويب وتصميم تجربة المستخدم.
مؤسّس ديفيا · eng-ahmed.com ↗أبرز النقاط
حالة الـ URL تعني تخزين حالة الواجهة — الفلاتر، التبويب النشط، نص البحث، ترقيم الصفحات — في سلسلة استعلام الـ URL بدلاً من `useState` في React، بحيث يعيد التحديث أو الرابط المُشارَك أو زر الرجوع في المتصفح عرض ما رآه المستخدم بالضبط.
في موجّه التطبيقات (App Router) في Next.js نقرأ حالة الاستعلام باستخدام `useSearchParams()` داخل مكوّن عميل، أو عبر خاصية `searchParams` غير المتزامنة في مكوّن خادم، وهي في Next.js 15 وعد (Promise) ينتظره الخادم قبل العرض.
نكتب حالة الاستعلام باستخدام `router.replace()` وكائن `URLSearchParams`، ونستخدم `replace` بدلاً من `push` حتى لا تُغرق تغييرات الفلاتر سجل زر الرجوع في المتصفح.
مكتبة `nuqs` توفّر خُطّافاً على شكل `useState` اسمه `useQueryState`، مع محلّلات مُنمّطة مثل `parseAsInteger` و`parseAsBoolean` تُعيد قيماً مُنمّطة بدلاً من سلاسل نصية خام.
أي مكوّن يستدعي `useSearchParams()` يجب أن يكون داخل حدّ `<Suspense>` في React أثناء العرض الساكن، وإلا فشل بناء الإنتاج في Next.js.
متى نخزّن الحالة في الـ URL بدلاً من useState؟
خزّن الحالة في الـ URL عندما يتوقّع المستخدم أن تبقى بعد التحديث أو عبر رابط مُشارَك أو إشارة مرجعية. الفلاتر والتبويب النشط ونص البحث وترتيب الفرز وترقيم الصفحات كلها تنطبق عليها هذه القاعدة. أبقِ الحالة في `useState` عندما تكون عابرة ضمن جلسة واحدة ومكوّن واحد، مثل حالة التمرير فوق عنصر، أو ما إذا كانت قائمة منسدلة مفتوحة، أو مسودّة نموذج غير محفوظة.
الاختبار الذي يستخدمه فريقنا سؤال واحد: إذا نسخ المستخدم هذا الـ URL وأرسله إلى زميل، فهل ينبغي أن يرى الزميل العرض نفسه؟ إذا كانت الإجابة نعم، فإن تلك الحالة تنتمي إلى الـ URL. حالة الـ URL تُزيل أيضاً فئة كاملة من أخطاء التزامن، لأن هناك مصدراً واحداً للحقيقة — سلسلة الاستعلام — بدلاً من قيمة `useState` قد تنحرف عمّا يظهر في شريط العنوان.
كيف نقرأ معاملات البحث في موجّه التطبيقات في Next.js؟
هناك طريقتان بحسب مكان تشغيل الكود. في مكوّن عميل نستدعي الخُطّاف `useSearchParams()` من `next/navigation`، وهو يُعيد كائن `URLSearchParams` للقراءة فقط، ونقرأ منه مفتاحاً مع قيمة افتراضية احتياطية مثل `searchParams.get('status') ?? 'all'`.
في مكوّن خادم نتجاوز الخُطّاف تماماً ونقرأ خاصية `searchParams` التي تتلقّاها كل صفحة. في Next.js 15 هذه الخاصية وعد (Promise)، لذا ينتظرها المكوّن ويجلب البيانات الصحيحة قبل أن يعرض أي شيء. هذه القراءة على جانب الخادم هي ما يتيح فلترة حقيقية على الخادم دون أي جلب من العميل.
كيف نكتب إلى الـ URL دون تشغيل تنقّل كامل؟
نبني كائن `URLSearchParams` جديداً من المعاملات الحالية، ونضبط المفتاح المستهدف عليه، ونستدعي `router.replace()` مع المسار وسلسلة الاستعلام الجديدة. تهيئة المعاملات الجديدة من الحالية تحافظ على كل مفتاح استعلام آخر موجود على الصفحة.
التفصيل المهم هو استخدام `router.replace()` بدلاً من `router.push()` لتغييرات الفلاتر والتبويبات. `push` يضيف مُدخلاً في السجل مع كل تغيير، فيتنقّل زر الرجوع عبر كل تعديل منفرد بدلاً من العودة إلى الصفحة السابقة، بينما يُبقي `replace` مُدخلاً واحداً.
ما هي nuqs، وماذا يضيف useQueryState فوق useSearchParams؟
`nuqs` هي مدير حالة مُنمّط لمعاملات البحث في React يحوّل نمط القراءة مع `router.replace` إلى خُطّاف واحد على شكل `useState`. يُعيد `useQueryState(key, parser)` صفّاً من قيمة ودالة ضبط، غير أن القيمة تعيش في الـ URL لا في ذاكرة المكوّن.
المحلّلات هي المكسب الحقيقي: `parseAsInteger` و`parseAsBoolean` و`parseAsArrayOf` و`parseAsStringEnum` و`parseAsIsoDateTime` تحوّل النص في الـ URL إلى النوع الذي يريده المكوّن فعلاً، وبالعكس عند الكتابة. ضبط القيمة إلى `null` يزيل المفتاح من الـ URL. في الإصدار الثاني من `nuqs` نغلّف التطبيق مرة واحدة بـ `<NuqsAdapter>` من `nuqs/adapters/next/app`، ونجمع عدة مفاتيح دفعةً واحدة باستخدام `useQueryStates`.
searchParams الأصلي أم nuqs: أيّهما نختار؟
نختار `useSearchParams` الأصلي لمعامل واحد أو اثنين من النصوص، ونختار `nuqs` عندما تكون هناك معاملات مُنمّطة كثيرة أو نريد التخلّص من الكود المتكرر.
`useSearchParams` الأصلي يعطي نصوصاً فقط، فنحلّلها بأنفسنا، ولا يحتاج إعداداً، ويكتب عبر `URLSearchParams` يدوي مع `router.replace`.
`nuqs` يعطي محلّلات مُنمّطة للأعداد الصحيحة والقيم المنطقية والمصفوفات والقوائم المعدودة والتواريخ، ويحتاج غلاف `<NuqsAdapter>` مرة واحدة، ويكشف صفّاً على شكل `useState`، ويجمع مفاتيح كثيرة عبر `useQueryStates`، ويعالج القيم الافتراضية عبر `withDefault` و`clearOnDefault`، ويكبح الكتابات السريعة نيابةً عنّا.
ما الذي يتعطّل — Suspense والكبح وبيانات الخادم القديمة؟
العطل الذي يستهلك وقت الفرق هو توقّف البناء برسالة مفادها أن `useSearchParams()` يجب أن يكون داخل حدّ suspense. أثناء العرض الساكن، أي مكوّن يقرأ `useSearchParams()` يجب أن يكون داخل حدّ `<Suspense>` في React، لأن المعاملات لا تُعرف إلا وقت الطلب. تغليف المكوّن القارئ يجعل البناء ينجح.
هناك حالتان طرفيتان أخريان جديرتان بالمعرفة. الكتابة إلى الـ URL مع كل ضغطة مفتاح تُغرق السجل وعمليات إعادة العرض، لذا يكبح `nuqs` تحديثات الـ URL افتراضياً ويتيح رفع `throttleMs`، بينما مع الكود الأصلي نكبح الكتابة بأنفسنا. وما إذا كان تغيير الـ URL يُعيد تشغيل مكوّنات الخادم يعتمد على التوجيه السطحي: يجعل `nuqs` القيمة الافتراضية `shallow: true`، وهي تحدّث الـ URL على العميل دون رحلة إلى الخادم، لذا نضبط `shallow: false` عندما يفلتر مكوّن خادم عبر خاصية `searchParams` ويحتاج القيمة الجديدة لإعادة الجلب.
الأسئلة الشائعة
س: متى نُبقي الحالة في الـ URL مقابل useState؟
ج: استخدم الـ URL للحالة التي يجب أن تبقى بعد التحديث أو عبر رابط مُشارَك أو إشارة مرجعية — الفلاتر والتبويبات والبحث والفرز وترقيم الصفحات. واستخدم `useState` لواجهة عابرة مثل القوائم المفتوحة أو التمرير فوق عنصر أو مسودّة نموذج غير محفوظة.
س: هل نحتاج nuqs، أم أن useSearchParams كافٍ؟
ج: `useSearchParams` مع `router.replace` يغطّي القراءة والكتابة. يضيف `nuqs` محلّلات مُنمّطة، وواجهة على شكل `useState`، وتجميع مفاتيح متعددة، وكبحاً. الأصلي كافٍ لمعامل واحد أو اثنين من النصوص؛ ويستحقّ nuqs العناء عند وجود معاملات مُنمّطة كثيرة.
س: لماذا يفشل البناء برسالة "useSearchParams() should be wrapped in a suspense boundary"؟
ج: أثناء العرض الساكن يتطلّب Next.js أن يكون أي مكوّن يستدعي `useSearchParams()` داخل حدّ `<Suspense>`، لأن القيمة لا تُعرف إلا وقت الطلب. غلّف ذلك المكوّن بـ `<Suspense>`.
س: هل نستخدم router.push أم router.replace لتغييرات الفلاتر؟
ج: استخدم `router.replace`. يضيف `push` مُدخلاً في السجل مع كل تغيير، فيتنقّل زر الرجوع عبر كل تعديل، بينما يُبقي `replace` مُدخلاً واحداً ويعيد المستخدم إلى الصفحة السابقة.
س: هل تحديث الـ URL يُعيد تشغيل مكوّنات الخادم لدينا؟
ج: مع `router.replace` الأصلي، نعم — يُعيد عرض مكوّنات الخادم للمسار. ومع `nuqs` يعتمد الأمر على خيار `shallow`: القيمة الافتراضية `shallow: true` على العميل فقط، و`shallow: false` تُطلق إعادة الجلب من الخادم.
قراءات إضافية
هندسة الواجهات
الوضع الداكن دون وميض في Next.js 16: كيف نستخدم سكربت سمة حاجبًا وcolor-scheme وزر تبديل آمنًا مع الـ Hydration
وميض الوضع الداكن عند إعادة تحميل الصفحة مشكلة توقيت: الخادم لا يستطيع قراءة localStorage. نشرح سكربت السمة الحاجب الذي نستخدمه في Next.js 16، ولماذا لا نضع السمة في cookie حفاظًا على الرسم الثابت، وكيف نبني زر تبديل يمر بالـ hydration دون أي عدم تطابق.
هندسة الواجهات
صور Open Graph الديناميكية في Next.js 16: كيف نبني opengraph-image.tsx ونتحقق من إصدار Satori الذي تضمّنه next/og ونحافظ على صحة معاينات الروابط
واجهت بطاقات المشاركة المولّدة لدينا ثلاث مفاجآت: خط WOFF2 لم يستطع المحرّك قراءته، وملف README لـ Satori يصف محرّكًا أحدث من المضمّن في Next.js، وصفحة ظلّت تعرض صورة الـ layout. هذه ملاحظاتنا الميدانية حول opengraph-image.tsx في Next.js 16 وأولوية البيانات الوصفية وفحص og:image قبل مشاركة أي رابط.