---
title: TanStack Router Type-Safe Search Params باستخدام
description: 'التحقق من صحة الـ URL search params في TanStack Router باستخدام Zod: القيم الافتراضية، والبدائل، و search middlewares، ومشكلة الـ Zod 4 adapter. دليل 2026 قابل للتنفيذ.'
url: https://nerdleveltech.com/ar/tanstack-router-type-safe-search-params-zod-tutorial
language: ar
date: '2026-06-04'
category: web-frontend
tags:
  - tanstack-router
  - zod
  - search-params
  - type-safe-routing
  - react
  - typescript
  - vite
  - tutorial
  - web-frontend
markdown_url: https://nerdleveltech.com/ar/tanstack-router-type-safe-search-params-zod-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/tanstack-router-type-safe-search-params-zod-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# TanStack Router Type-Safe Search Params باستخدام

في TanStack Router، تعتبر معاملات البحث في URL (search params) حالة مُتحقق منها وليست مجرد نصوص عشوائية. تقوم بربط مخطط Zod بمسار معين باستخدام `validateSearch`، وتصبح كل عملية قراءة عبر `useSearch()` مكتوبة بالكامل (fully typed)، ومُحللة، ولها قيم افتراضية. يبني هذا الدليل مساراً قابلاً للتشغيل لقائمة منتجات — يتضمن الترقيم، الترتيب، فلاتر الوسوم، وتبديل حالة `inStock` — مع معالجة مرنة للمدخلات غير الصالحة، وبرمجيات وسيطة للبحث تحافظ على نظافة URL، وإجابة واضحة على سؤال يربك الكثيرين في عام 2026: **هل يعمل محول Zod مع إصدار Zod 4؟** (الإجابة هي لا — والحل يتكون من سطر واحد). كل شيء هنا يتم التحقق من نوعه (type-checks) مقابل `@tanstack/React-router@1.170.11` في تاريخ الكتابة.

## ما ستتعلمه

- كيفية التحقق من صحة معاملات البحث في URL في TanStack Router باستخدام مخطط Zod عبر `validateSearch` و `zodValidator`.
- كيفية جعل المعاملات غير الصالحة أو المفقودة تتراجع بأمان باستخدام `fallback()` مدمجاً مع `.default()`.
- لماذا يرفض `@tanstack/zod-adapter@1.167.0` التثبيت مع Zod 4، والطريقتان النظيفتان لإصلاح ذلك.
- كيفية قراءة المعاملات المكتوبة بالكامل في مكون باستخدام `Route.useSearch()` وخطاف `useSearch({ from, select })` المستقل.
- كيفية الاحتفاظ ببعض المعاملات وإسقاط البعض الآخر عبر التنقل باستخدام البرمجيات الوسيطة للبحث `retainSearchParams` و `stripSearchParams`.
- كيفية تحديث المعاملات بطريقة آمنة النوع (type-safely) باستخدام المحدثات الوظيفية لـ `Link` و `navigate()`، وتغذيتها في `loader` المسار.

## المتطلبات الأساسية

- **Node.js 22.12+ أو 24 LTS.** أسقط Vite 8 دعم Node 18 ويتطلب Node 20.19+ / 22.12+[^vite-node]. إصدار Node 24 هو خط LTS النشط الحالي[^node-lts].
- **تطبيق Vite + React 19** (`React@19.2.7`، `React-dom@19.2.7`).
- **TypeScript في الوضع الصارم (strict mode)** مع `moduleResolution: "bundler"` (أو `nodenext`). توصي وثائق TanStack Router بأكثر إعدادات TypeScript صرامة للحصول على تجربة أمان نوع كاملة[^tsr-types].
- الإلمام بـ React ومخططات Zod الأساسية. لا يشترط خبرة سابقة بـ TanStack Router.

يستخدم هذا البرنامج التعليمي التوجيه القائم على الملفات (الإعداد الموصى به)، ولكن كل استدعاء لـ API هو نفسه تحت التوجيه القائم على الكود إذا كنت تفضل ذلك.

## الخطوة 1 — تثبيت الحزم

قم بتثبيت إصدارات محددة ليتطابق بناؤك مع هذا الدليل. سطرا التثبيت أدناه متعمدان — اختيار Zod يعتمد على مسار التحقق الذي ستختاره، والموضح في الخطوة 4.

```bash
# الراوتر + إضافة Vite للتوجيه القائم على الملفات
npm install --save-exact @tanstack/React-router@1.170.11
npm install --save-exact -D @tanstack/router-plugin@1.168.14

# التحقق: Zod 3 + المحول الرسمي (الخطوة 4 تشرح بديل Zod 4)
npm install --save-exact zod@3.25.76 @tanstack/zod-adapter@1.167.0
```

النتيجة المتوقعة — يظهر `npm ls` الإصدارات المحددة دون تحذيرات من تبعيات النظراء (peer-dependency):

```text
├── @tanstack/React-router@1.170.11
├── @tanstack/zod-adapter@1.167.0
├── zod@3.25.76
└── @tanstack/router-plugin@1.168.14
```

إذا قمت بدلاً من ذلك بتشغيل `npm install zod@4`، فستواجه خطأ `ERESOLVE`. هذا أمر متوقع وليس خطأ من جانبك — توضح الخطوة 4 بالضبط لماذا وكيف تختار مسارك.

## الخطوة 2 — ربط إضافة Vite للتوجيه القائم على الملفات

يقوم التوجيه القائم على الملفات بإنشاء شجرة مسارات مكتوبة من دليل `src/routes` الخاص بك. أضف الإضافة إلى `vite.config.ts` **قبل** `@vitejs/plugin-React` — تتطلب الوثائق هذا الترتيب، وتثير الإضافة خطأً صريحاً إذا جاءت `@vitejs/plugin-React` أولاً[^tsr-vite].

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import React from '@vitejs/plugin-React'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({ target: 'React', autoCodeSplitting: true }),
    React(),
  ],
})
```

بإعداداتها الافتراضية، تراقب الإضافة `./src/routes` وتكتب شجرة مسارات تم إنشاؤها إلى `./src/routeTree.gen.ts` في كل عملية تشغيل تطوير أو بناء[^tsr-vite]. لا تقم أبداً بتعديل هذا الملف يدوياً — فهو الغراء آمن النوع الذي يجعل كل مسار وكل مخطط بحث معروفاً للمترجم. قم بتشغيل `npm run dev` مرة واحدة وتأكد من ظهور `src/routeTree.gen.ts`.

## الخطوة 3 — تسجيل الراوتر لأمان نوع شامل

يجب تسجيل مثيل الراوتر مع نظام النوع حتى تعرف `Link` و `navigate` و `useSearch` شجرة المسارات الخاصة بك. أنشئ الراوتر وقم بتوسيع واجهة `Register`[^tsr-validate].

```ts
// src/router.ts
import { createRouter } from '@tanstack/React-router'
import { routeTree } from './routeTree.gen'

export const router = createRouter({ routeTree })

declare module '@tanstack/React-router' {
  interface Register {
    router: typeof router
  }
}
```

كتلة `declare module` هذه هي ما يرفع كل API للراوتر من "مكتوب نصياً" إلى "يعرف مساراتك الدقيقة". إذا تخطيتها، فلن يكون للراوتر شجرة مسارات ليستنتج منها — وستنهار نتائج `useSearch` إلى `never` بدلاً من نوع المخطط الخاص بك. قم بتثبيت الراوتر في `src/main.tsx` باستخدام `<RouterProvider router={router} />` كالمعتاد.

## الخطوة 4 — تعريف مخطط البحث (وقرار Zod 4)

هنا يكمن جوهر البرنامج التعليمي. تحتاج صفحة المنتجات إلى استعلام بحث، ورقم صفحة، وترتيب فرز، وقائمة بفلاتر الوسوم، وتبديل لحالة التوفر في المخزن. نريد التحقق من كل منها، وتعيين قيم افتراضية، وجعلها مرنة تجاه المدخلات السيئة التي يتم لصقها في شريط URL.

**هل يعمل محول Zod الخاص بـ TanStack Router مع Zod 4؟** لا. يصرح `@tanstack/zod-adapter@1.167.0` بـ `peerDependencies: { "zod": "^3.23.8" }`، لذا فإن تثبيته بجانب `zod@4.x` يفشل مع خطأ تعارض الأقران `ERESOLVE`[^zod-adapter-npm]. لديك خياران نظيفان، وكلاهما من الدرجة الأولى:

- **المسار أ — Zod 3 + المحول.** استخدم `zod@3.25.76` و `zodValidator()`. يفتح هذا ميزة المساعد `fallback()` في المحول، والتي تلتقط أخطاء التحقق لكل حقل وتستبدلها بقيمة آمنة بدلاً من إثارة خطأ. هذا هو المسار الذي يستخدمه هذا الدليل.
- **المسار ب — Zod 4، بدون محول.** يقبل TanStack Router أي مدقق [Standard Schema](https://standardschema.dev) مباشرة. تنفذ مخططات Zod 4 الـ Standard Schema، لذا يمكنك تمرير المخطط مباشرة إلى `validateSearch` بدون محول وبدون تعارض أقران. ستفقد مساعد `fallback()` الخاص بالمحول وتعتمد على `.catch()` الخاص بـ Zod بدلاً من ذلك. تعرض الخطوة 9 هذا البديل بالكامل.

بالنسبة للمسار أ، أنشئ المخطط مع تغليف كل حقل بـ `fallback()` وتوفير القيمة عبر `.default()` عند غياب المعامل:

```ts
// src/routes/products.tsx
import { createFileRoute } from '@tanstack/React-router'
import { zodValidator, fallback } from '@tanstack/zod-adapter'
import { z } from 'zod'

const SORT_KEYS = ['newest', 'price-asc', 'price-desc'] as const

export const productSearchSchema = z.object({
  q: fallback(z.string(), '').default(''),
  page: fallback(z.number().int().min(1), 1).default(1),
  sort: fallback(z.enum(SORT_KEYS), 'newest').default('newest'),
  tags: fallback(z.array(z.string()), []).default([]),
  inStock: fallback(z.boolean(), false).default(false),
})
```

يقوم الثنائي `fallback()` و `.default()` بمهمتين متميزتين. يقوم `.default(value)` بملء القيمة عندما يكون المعامل **مفقوداً** من URL. بينما يلتقط `fallback(schema, value)` الحالة التي يكون فيها المعامل **موجوداً ولكنه غير صالح** — على سبيل المثال `?page=-3` أو `?sort=banana` — ويستبدله بالقيمة الآمنة بدلاً من إظهار خطأ تحقق للمستخدم[^tsr-validate]. معاً، يضمنان أن `useSearch()` تعيد دائماً كائناً كاملاً وصالحاً، بغض النظر عما يكتبه أي شخص في شريط العنوان.

لاحظ أن المخطط (schema) يتعامل مع أنواع حقيقية، وليس مجرد نصوص: `page` هو رقم، و `inStock` هو قيمة منطقية (boolean)، و `tags` هي مصفوفة. يقوم TanStack Router بتسلسل البحث باستخدام `JSON.stringify`/`JSON.parse` افتراضيًا، لذا فإن الأرقام والقيم المنطقية والمصفوفات والكائنات المتداخلة تنتقل عبر الرابط (URL) وتعود دون الحاجة لتحويل يدوي[^tsr-serialize]. الكائن `{ page: 1, tags: ["sale"] }` يصبح `?page=1&tags=["sale"]` — مع ترميز الأقواس وعلامات الاقتباس في شريط العنوان الفعلي (`tags=%5B%22sale%22%5D`) — ويتم تحليله مباشرة إلى قيم ذات أنواع محددة.

## الخطوة 5 — ربط المخطط بالمسار باستخدام validateSearch

قم بتغليف المخطط في `zodValidator()` ومرره إلى خيار `validateSearch` الخاص بالمسار. من هذه النقطة فصاعدًا، يصبح بحث المسار محدد النوع في كل مكان[^tsr-validate].

```ts
// src/routes/products.tsx (continued)
export const Route = createFileRoute('/products')({
  validateSearch: zodValidator(productSearchSchema),
  component: ProductsPage,
})
```

يقوم `zodValidator()` بتكييف مخطط Zod ليناسب شكل المدقق الذي يتوقعه TanStack Router ويمرر النوع المستنتج من Zod إلى المسار. النوع الذي يخرج من الطرف الآخر هو بالضبط `z.infer<typeof productSearchSchema>` — وهو `{ q: string; page: number; sort: 'newest' | 'price-asc' | 'price-desc'; tags: string[]; inStock: boolean }`. لا يوجد استخدام لـ `as` للتحويل القسري في أي مكان، ولا يوجد مكان ثانٍ للتصريح عن النوع.

## الخطوة 6 — قراءة المعاملات محددة النوع في المكون

يعيد `Route.useSearch()` الكائن الذي تم التحقق منه مع استنتاج كامل للأنواع. لا حاجة لـ generics، ولا كتابة يدوية للأنواع — المحرر يعرف أن `search.sort` هو اتحاد ثلاثي (three-way union) وأن `search.page` هو رقم.

```tsx
// src/routes/products.tsx (continued)
function ProductsPage() {
  const search = Route.useSearch()
  const navigate = Route.useNavigate()

  return (
    <div>
      <h1>Products</h1>
      <p>
        Page {search.page} · sorted by {search.sort}
        {search.inStock ? ' · in stock only' : ''}
        {search.tags.length > 0 ? ` · tags: ${search.tags.join(', ')}` : ''}
      </p>

      <button
        onClick={() =>
          navigate({ search: (prev) => ({ ...prev, page: prev.page + 1 }) })
        }
      >
        Next page
      </button>
    </div>
  )
}
```

هناك شيئان يجعلان هذا مريحًا في الاستخدام. أولاً، `Route.useSearch()` مرتبط بهذا المسار، لذا لا يحتاج إلى وسيط `from`. ثانياً، `navigate({ search: (prev) => ... })` يقبل **مُحدِّثًا وظيفيًا (functional updater)**: `prev` هو البحث الحالي محدد النوع، والكائن الذي تعيده يتم التحقق من نوعه مقابل المخطط. إعادة `{ ...prev, page: 'two' }` ستؤدي إلى خطأ في الترجمة (compile error)، يتم اكتشافه قبل النشر.

إذا كنت بحاجة إلى المعاملات من مكون **ليس** هو مكون المسار — شريط أدوات مشترك مثلاً — استخدم الـ hook المستقل مع وسيط `from` صريح، واختياريًا وظيفة `select` للاشتراك في حقل واحد فقط وتجنب إعادة الرندر غير الضرورية:

```tsx
import { useSearch } from '@tanstack/React-router'

function ResultsCount() {
  // Only re-renders when `page` changes, not on every search update.
  const page = useSearch({ from: '/products', select: (s=> s.page })
  return <span>Viewing page {page}</span>
}
```

## الخطوة 7 — بناء عناصر تحكم التصفية باستخدام Link محدد النوع

تُستخدم `navigate()` للتحديثات الأمرية (imperative) من معالجات الأحداث؛ أما بالنسبة للروابط التصريحية (declarative) — أزرار الترتيب، تبديل الفلتر، الترقيم — استخدم `Link` مع نفس المُحدِّث الوظيفي. تستقبل خاصية `search` المعاملات الحالية محددة النوع وتعيد المعاملات التالية، لذا فإن أي مفتاح أو قيمة خاطئة ستؤدي لخطأ ترجمة. تقوم `activeProps` بتنسيق الرابط عندما يتطابق هدفه مع الموقع الحالي.

```tsx
// src/routes/products.tsx — a sort + filter bar
import { Link } from '@tanstack/React-router'

const SORT_OPTIONS = [
  { value: 'newest', label: 'Newest' },
  { value: 'price-asc', label: 'Price ↑' },
  { value: 'price-desc', label: 'Price ↓' },
] as const

function ProductsToolbar() {
  const search = Route.useSearch()
  return (
    <nav>
      {SORT_OPTIONS.map((opt=> (
        <Link
          key={opt.value}
          to="/products"
          // Reset to page 1 whenever the sort changes — a common UX rule.
          search={(prev=> ({ ...prev, sort: opt.value, page: 1 })}
          activeProps={{ className: 'is-active' }}
        >
          {opt.label}
        </Link>
      )}

      <Link
        to="/products"
        search={(prev=> ({ ...prev, inStock: !prev.inStock, page: 1 }}
      >
        {search.inStock ? 'Showing in-stock only' : 'Show in-stock only'}
      </Link>
    </nav>
  )
}
```

كل رابط هو URL حقيقي وقابل للمشاركة — بمجرد أن تقوم برمجيات الخطوة 8 الوسيطة (middlewares) بتجريد القيم الافتراضية، فإن النقر بزر الماوس الأيمن على "Copy link address" في زر "Price ↑" من حالة افتراضية يمنحك رابطًا مرتبًا `/products?sort=price-asc`، جاهزًا للصقه في Slack أو حفظه كإشارة مرجعية. (بدون تلك البرمجيات الوسيطة، يحمل الرابط كائن البحث الكامل الذي تم التحقق منه، بما في ذلك القيم الافتراضية.) ولأن المُحدِّث ينشر `prev` أولاً، يتم الحفاظ على كل فلتر نشط آخر بينما يتغير فقط `sort` (وإعادة تعيين `page`). استخدام `as const` في `SORT_OPTIONS` هو ما يحافظ على `opt.value` محصوراً في الاتحاد الحرفي (literal union) الذي يتوقعه المخطط، بدلاً من تحويله إلى `string` عام.

## الخطوة 8 — الحفاظ على نظافة الروابط باستخدام برمجيات البحث الوسيطة

تظهر مشكلتان بمجرد أن يبدأ المستخدمون الحقيقيون في التنقل. أولاً، القيم الافتراضية تملأ الرابط: التنقل الذي يمرر كائن البحث الكامل ينتج `?q=&page=1&sort=newest&tags=[]&inStock=false` بدلاً من رابط نظيف `/products`. ثانياً، يتم إعادة تعيين المعاملات بصمت: أي تنقل يوفر كائن بحث جديدًا — مثل `navigate({ search: { page: 2 } })` — يعيد التحقق من المعاملات المحذوفة لتعود إلى قيمها الافتراضية، مما يمسح قيم `q` و `sort` الخاصة بالمستخدم في هذه العملية.

يحل TanStack Router كلتا المشكلتين باستخدام **برمجيات البحث الوسيطة (search middlewares)** المصرح عنها في المسار. يقوم `stripSearchParams` بإزالة المعاملات التي تساوي قيمتها الافتراضية، ويقوم `retainSearchParams` بحمل المعاملات المختارة عبر عمليات التنقل في نفس المسار التي تحذفها[^tsr-validate].

```ts
// src/routes/products.tsx — extend the Route definition
import {
  createFileRoute
  retainSearchParams
  stripSearchParams
} from '@tanstack/React-router'

export const Route = createFileRoute('/products')({
  validateSearch: zodValidator(productSearchSchema)
  search: {
    middlewares: [
      // Drop any param that currently equals its default value.
      stripSearchParams({
        q: ''
        page: 1
        sort: 'newest'
        tags: []
        inStock: false
      })
      // Keep q and sort when a same-route navigation omits them.
      retainSearchParams(['q', 'sort'])
    ]
  }
  component: ProductsPage
})
```

الترتيب مهم، وليس بالاتجاه الذي قد تتوقعه: `retainSearchParams` يعيد تطبيق معاملاته بعد تشغيل بقية السلسلة، لذا إذا وضعته **قبل** `stripSearchParams`، فإن المفاتيح المحتفظ بها ستنجو من التجريد وسيحافظ الرابط في الحالة الافتراضية على `?q=&sort=newest` زائدة. مع وضع `stripSearchParams` أولاً — كما هو موضح أعلاه، وتم التحقق منه مقابل `@tanstack/React-router@1.170.11` — فإن الزيارة في الحالة الافتراضية تنتهي عند رابط مجرد `/products`، والبحث النشط مثل `/products?q=boots&page=2` يحافظ بالضبط على المعاملات التي تحمل معنى، و `navigate({ search: { page: 2 } })` يحافظ على قيم `q` و `sort` الحالية بدلاً من إعادة تعيينها. الكائن الذي تمرره إلى `stripSearchParams` يجب أن يطابق القيم الافتراضية للمخطط — وهذا هو ما يميز المعامل بأنه "آمن للحذف".

ملاحظة بخصوص النطاق: هذه البرمجيات الوسيطة تنتمي إلى مسار `/products`، لذا فهي تعمل لعمليات التنقل التي تستهدف `/products`. التنقل إلى مسار مختلف (`/about`) يحذف معاملات المنتجات تمامًا — وهذا مقصود، لأن المسارات الأخرى لا تصرح عنها. النمط الرسمي لحمل معامل عبر **جميع** المسارات هو التصريح عنه في مخطط بحث المسار الجذري (root-route) وربط `retainSearchParams` بالمسار الجذري بدلاً من ذلك[^tsr-search-guide].

## الخطوة 9 — نسخة Zod 4 (بدون محول)

إذا كان تطبيقك يستخدم بالفعل Zod 4، فتجاوز المحول تمامًا. مرر مخطط Z 4 مباشرة إلى `validateSearch` — يستهلكه TanStack Router كمدقق Standard Schema، ويأتي النوع المستنتج بشكل متطابق[^standard-schema]. استخدم `.catch()` المدمج في Zod بدلاً من `fallback()` الخاص بالمحول لاستعادة البيانات من المدخلات غير الصالحة.

```ts
// src/routes/products.tsx — Zod 4, no @tanstack/zod-adapter
import { createFileRoute } from '@tanstack/React-router'
import { z } from 'zod' // zod@4.x

const SORT_KEYS = ['newest' 'price-asc' 'price-desc'] as const

const productSearchSchema = z.object({
  q: z.string().catch('').default('')
  page: z.number(.int(.min(1.catch(1.default(1
  sort: z.enum(SORT_KEYS.catch('newest'.default('newest'
  tags: z.array(z.string(.catch([].default([]
  inStock: z.boolean(.catch(false.default(false
})

export const Route = createFileRoute('/products'({
  validateSearch: productSearchSchema // schema passed directly
  component: ProductsPage
})
```

المقايضة صغيرة وواضحة. إن `fallback()` الخاص بالمحول مصمم خصيصًا لحالة "معامل URL صالح ولكنه خارج النطاق" ويُقرأ بشكل أوضح قليلاً في مخطط الراوتر، بينما يقوم `.catch()` المدمج في Zod بنفس المهمة بدون أي محول — كلا النسختين تحولان `?page=-5` إلى `1` و `?sort=banana` إلى `'newest'`، وتم التحقق من ذلك مقابل نفس المدخلات. كل من `Route.useSearch()`، وبرمجيات البحث الوسيطة، والمُحدِّثات الوظيفية من الخطوات 6 إلى 8 تعمل بشكل متطابق — فقط بناء المخطط هو ما يختلف.

## الخطوة 10 — تمرير المعاملات التي تم التحقق منها إلى loader

تُعد بارامترات البحث التي تم التحقق من صحتها هي المدخل الصحيح لجلب البيانات. قم بالتصريح عن `loaderDeps` لسحب البارامترات التي يهتم بها الـ loader، ثم اقرأها كـ `deps` مكتوبة النوع (typed) داخل الـ `loader`. تصبح هذه الـ deps جزءًا من مفتاح التخزين المؤقت (cache key) للـ loader، لذا فإن التنقل الذي يغير فقط البارامترات غير التابعة (non-dep) لن يؤدي إلى إعادة جلب البيانات[^tsr-search-guide].

```tsx
// src/routes/products.tsx — add a loader to the Route
export const Route = createFileRoute('/products')({
  validateSearch: zodValidator(productSearchSchema),
  loaderDeps: ({ search }) => ({
    q: search.q,
    page: search.page,
    sort: search.sort,
  }),
  loader: async ({ deps }) => {
    // deps.q is string, deps.page is number, deps.sort is the union — all typed.
    const params = new URLSearchParams({
      q: deps.q,
      page: String(deps.page),
      sort: deps.sort,
    })
    const res = await fetch(`/API/products?${params.toString()}`)
    return { items: (await res.json()) as Array<{ id: number; name: string }> }
  },
  component: ProductsPage,
})
```

داخل المكون، اقرأ النتيجة باستخدام `Route.useLoaderData()`، وهي أيضًا مكتوبة النوع بالكامل. نظرًا لأن الـ loader يعتمد على `loaderDeps`، فإن تغيير `inStock` في واجهة المستخدم لن يؤدي إلى إعادة جلب البيانات إلا إذا أضفته إلى الـ deps — أنت من يقرر بالضبط أي تغيير في البارامترات يستحق طلبًا جديدًا للسيرفر.

## التحقق

تأكد من أن كل شيء يعمل بشكل صحيح من حيث الأنواع (type-checks) والسلوك قبل أن تعتمده. أسرع إشارة هي المترجم (compiler): قم بتشغيل فحص الأنواع وتوقع عدم وجود أخطاء.

```bash
npx tsc --noEmit
# (no output, exit code 0)
```

ثم تحقق من سلوك وقت التشغيل (runtime) في المتصفح عن طريق تعديل عنوان URL مباشرة:

| ما تكتبه                          | ما يرجعه `useSearch()`                                    | السبب                                                |
| --------------------------------- | --------------------------------------------------------- | ---------------------------------------------------- |
| `/products`                       | `{ q:'', page:1, sort:'newest', tags:[], inStock:false }` | `.default()` يملأ كل بارامتر مفقود                   |
| `/products?page=2&sort=price-asc` | `{ ..., page:2, sort:'price-asc' }`                       | البارامترات الصالحة يتم تحليلها إلى قيم مكتوبة النوع |
| `/products?page=-5`               | `{ ..., page:1 }`                                         | `fallback()` يلتقط القيمة غير الصالحة                |
| `/products?sort=banana`           | `{ ..., sort:'newest' }`                                  | `fallback()` يرفض القيمة غير الموجودة في الـ enum    |

الصفان الثالث والرابع هما الثمرة: عنوان URL عدائي أو قديم لن يتسبب أبدًا في تعطل الصفحة أو تسريب قيمة غير صالحة إلى طبقة البيانات الخاصة بك. بل يتم حله بهدوء إلى حالة آمنة.

## الأخطاء الشائعة

هذه هي الإخفاقات الحقيقية التي يواجهها الأشخاص، مأخوذة من الوثائق وسجل المشكلات بدلاً من اختراعها.

- **فشل `npm install` مع خطأ `ERESOLVE` في تبعية النظير `zod@"^3.23.8"`.** لقد قمت بتثبيت Zod 4 بجانب `@tanstack/zod-adapter`، والذي يعتمد حاليًا على Zod 3[^zod-adapter-npm]. إما أن تثبت إصدار `zod@3.25.76` (المسار أ) أو تتخلى عن الـ adapter وتمرر مخطط Zod 4 مباشرة (المسار ب، الخطوة 9). لا تتجاهل الأمر باستخدام `--force` — فالـ adapter يتوقع فعليًا المكونات الداخلية لـ Zod 3.
- **`useSearch()` يظهر بنوع `never` أو `unknown`.** توسعة `Register` من الخطوة 3 مفقودة أو لم يتم إنشاء شجرة المسارات (route tree). تأكد من وجود `src/routeTree.gen.ts` (قم بتشغيل `npm run dev`) وأن `declare module '@tanstack/React-router'` موجود في ملف يتضمنه المترجم.
- **البارامترات الافتراضية لا تختفي من عنوان URL.** هناك سببان. إما أن الكائن الممرر إلى `stripSearchParams` لا يطابق تمامًا القيم الافتراضية لمخططك (على سبيل المثال `page: 0` بينما الافتراضي هو `1`)، أو أن `retainSearchParams` مدرج قبل `stripSearchParams` — حيث يتم إعادة تطبيق المفاتيح المحتفظ بها بعد الإزالة، مما يترك `?q=&sort=newest` معلقًا. ضع `stripSearchParams` أولاً (الخطوة 8).
- **الأرقام أو القيم المنطقية تصل كسلاسل نصية (strings) في الـ loader.** لقد قمت بتجاوز التسلسل (serialization)، أو أنك تقرأ من `window.location` بدلاً من `useSearch()`/`loaderDeps`. مع محول JSON الافتراضي، يمنحك `validateSearch` أرقامًا وقيمًا منطقية حقيقية[^tsr-serialize] — اقرأ دائمًا من خلال الـ router، وليس من سلسلة الاستعلام الخام.
- **البارامتر المحتفظ به لا ينتقل إلى مسار مختلف.** نطاق `retainSearchParams` يقتصر على المسار الذي تم التصريح عنه فيه — فهو يحفظ البارامترات للتنقلات التي تستهدف ذلك المسار، وليس للتنقلات بعيدًا عنه. التنقل من `/products?q=boots` إلى `/about` سينتج عنه `/about` مجردًا. لنقل بارامتر عبر جميع المسارات، قم بالتصريح عنه في مخطط بحث المسار الجذر (root-route) وأرفق الوسيط (middleware) بالمسار الجذر بدلاً من ذلك.

## الخطوات التالية ومزيد من القراءة

لديك الآن بارامترات بحث URL مكتوبة النوع، ومتحقق من صحتها، وبقيم افتراضية، ومنظفة ذاتيًا — نفس النمط يتوسع ليشمل لوحات التحكم، وجداول البيانات، وأي عرض قابل للتصفية. من هنا:

- انقل حالة التصفية لشبكة بيانات بالكامل إلى بارامترات البحث بحيث يكون كل عرض قابلاً للمشاركة وللحفظ في الإشارات المرجعية.
- اجمع بين هذا وبين طبقة بيانات مكتوبة النوع. يوضح [دليل Astro Actions للنماذج آمنة النوع](https://nerdleveltech.com/astro-actions-type-safe-forms-tutorial) نفس فكرة "المخطط هو المصدر الوحيد للحقيقة" على جانب السيرفر، ويطبقها [دليل Next.js 16 Server Actions وواجهة المستخدم المتفائلة](https://nerdleveltech.com/nextjs-16-server-actions-React-19-optimistic-ui-tutorial) على العمليات (mutations).
- لتخزين البيانات التي تحركها تلك البارامترات مؤقتًا، يغطي [دليل Next.js 16 Cache Components لتعدد المستأجرين](https://nerdleveltech.com/nextjs-16-cache-components-multi-tenant-cache-keys-tutorial) كيفية جعل مفاتيح التخزين المؤقت تعتمد على القيم المستمدة من الطلب.

تتعمق الوثائق الرسمية أكثر في أنماط التسلسل والتحقق المخصصة[^tsr-validate][^tsr-search-guide].

[^vite-node]: Vite — الهجرة / المتطلبات. يتطلب Vite 8 إصدار Node.js 20.19+ أو 22.12+. <https://vite.dev/guide/migration>

[^node-lts]: إصدارات Node.js (جدول LTS). <https://nodejs.org/en/about/previous-releases>

[^tsr-types]: TanStack Router — دليل أمان النوع (إعداد TypeScript الموصى به). <https://tanstack.com/router/latest/docs/framework/React/guide/type-safety>

[^tsr-vite]: TanStack Router — التثبيت مع Vite (القيم الافتراضية لـ router-plugin: routesDirectory `./src/routes`، generatedRouteTree `./src/routeTree.gen.ts`). <https://tanstack.com/router/latest/docs/installation/with-vite>

[^tsr-validate]: TanStack Router — التحقق من معايير البحث باستخدام المخططات (Schemas). <https://tanstack.com/router/latest/docs/framework/React/how-to/validate-search-params>

[^zod-adapter-npm]: @tanstack/zod-adapter على npm — `peerDependencies: { zod: "^3.23.8" }`. <https://www.npmjs.com/package/@tanstack/zod-adapter>

[^tsr-serialize]: TanStack Router — تسلسل معايير البحث المخصص (الافتراضي يستخدم `JSON.stringify`/`JSON.parse`). <https://tanstack.com/router/latest/docs/framework/React/guide/custom-search-param-serialization>

[^tsr-search-guide]: TanStack Router — دليل معايير البحث (Search Params). <https://tanstack.com/router/latest/docs/guide/search-params>

[^standard-schema]: Standard Schema — واجهة مشتركة يتم تنفيذها بواسطة Zod و Valibot و ArkType، ويتم استهلاكها مباشرة بواسطة `validateSearch` في TanStack Router. <https://standardschema.dev>
