السلسلة: هذا الدرس جزء من سلسلة TanStack. اقرأ المقال الرئيسي.
TanStack Router يقرأ محتوى src/routes لتوليد شجرة routes typée بلا كتابة إعداد يدوي. هذا النهج الإعلاني ينقل كل عمل التوجيه إلى شجرة الملفات: ملف يصير URL، مجلد يصير بادئة، بادئة خاصة تغيّر الدلالة. الملحق Vite يُعيد توليد routeTree.gen.ts عند كل حفظ.
المتطلبات
- Node.js 22 LTS أدنى
- مشروع Vite 6 أو 7 مع React 19
- الحزم
@tanstack/react-router،@tanstack/react-router-devtools،@tanstack/router-plugin zodللتحقق من search params- TypeScript 5.4+،
strict: true
npm install @tanstack/react-router @tanstack/react-router-devtools zod
npm install -D @tanstack/router-plugin
الخطوة 1 — توصيل الملحق في vite.config.ts
الملحق يجب تنفيذه قبل react()، لأنه يُولّد ملف الشجرة الذي يقرأه المُصرّف لاحقاً. عكس الترتيب يُنتج خطأ routeTree.gen.ts not found.
// 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(),
],
})
الخطوة 2 — route الجذر __root.tsx
جذر الشجرة يُسمى إلزامياً __root.tsx (شرطتان سفليتان). يستخدم createRootRoute لا createFileRoute.
// src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
export const Route = createRootRoute({
component: () => (
<>
<header>
<nav>Navigation globale</nav>
</header>
<main>
<Outlet />
</main>
<TanStackRouterDevtools />
</>
),
})
الخطوة 3 — route index وroute فرعي كلاسيكي
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Accueil</h1>
}
الخطوة 4 — معامل ديناميكي مع $
مقطع URL متغيّر يُحدَّد بالبادئة $ في اسم الملف. لمطابقة /posts/123، أنشئ src/routes/posts/$postId.tsx.
// src/routes/posts/$postId.tsx
import { createFileRoute, useParams } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
component: PostPage,
})
function PostPage() {
const { postId } = useParams({ from: '/posts/$postId' })
return <article>Article {postId}</article>
}
زر /posts/42: الصفحة تعرض Article 42. المعاملات ليست محوَّلة لأرقام؛ إن انتظرت معرّفاً عددياً، تحقّق منه في parseParams.
الخطوة 5 — Layout pathless مع _layout
ملف أو مجلد مسبوق بـ underscore هو layout pathless: يغلّف أبناءه دون إضافة مقطع URL.
// src/routes/_layout.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_layout')({
component: AppLayout,
})
function AppLayout() {
return (
<div className="shell">
<aside>Sidebar partagee</aside>
<section>
<Outlet />
</section>
</div>
)
}
أي route تحت src/routes/_layout/ يرث هذا الإطار. URL يبقى نظيفاً: /dashboard، لا /_layout/dashboard.
الخطوة 6 — حماية منطقة بـ admin/_authenticated
// src/routes/admin/_authenticated/route.tsx
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/admin/_authenticated')({
beforeLoad: ({ location }) => {
const token = localStorage.getItem('token')
if (!token) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
},
component: () => <Outlet />,
})
الـ hook beforeLoad يُنفَّذ قبل العرض. إن رفع redirect، المستخدم يذهب إلى /login مع الهدف محفوظاً.
الخطوة 7 — التقاط الباقي بـ $.tsx
// src/routes/$.tsx
import { createFileRoute, useParams } from '@tanstack/react-router'
export const Route = createFileRoute('/$')({
component: NotFound,
})
function NotFound() {
const { _splat } = useParams({ from: '/$' })
return <p>Page introuvable : /{_splat}</p>
}
اكتب URL غير موجود مثل /foo/bar/baz: الصفحة تعرض Page introuvable : /foo/bar/baz. الـ splat يجب إعلانه في أعلى مستوى لمنطقته لئلا يحجب route أدقّ.
الخطوة 8 — التجميع بلا بادئة مع (أقواس)
الأقواس حول اسم مجلد تُنشئ مجموعة. المجلد يختفي من URL لكنه يجمع routes للتنظيم.
src/routes/
(marketing)/
pricing.tsx // => /pricing
contact.tsx // => /contact
(auth)/
login.tsx // => /login
register.tsx // => /register
المجموعات لا تُنشئ layout مشترك؛ للمشاركة، اجمع المجموعة مع layout pathless: (auth)/_layout/login.tsx.
الخطوة 9 — تنميط search params مع Zod
query strings مدخلات غير موثوقة. TanStack Router يفرض التحقق عند الإعلان عبر validateSearch. مع schema موضوع، useSearch يُرجع كائناً typé، وuseNavigate يرفض عند التصريف أي قيمة خارج schema.
// src/routes/search.tsx
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
import { z } from 'zod'
const searchSchema = z.object({
q: z.string().optional(),
page: z.number().int().min(1).default(1),
})
export const Route = createFileRoute('/search')({
validateSearch: searchSchema,
component: SearchPage,
})
function SearchPage() {
const { q, page } = useSearch({ from: '/search' })
const navigate = useNavigate({ from: '/search' })
return (
<div>
<input
defaultValue={q ?? ''}
onChange={(e) =>
navigate({ search: (prev) => ({ ...prev, q: e.target.value }) })
}
/>
<p>Page {page}</p>
</div>
)
}
إن زار شخص /search?page=abc، Zod يرفض القيمة والـ router يطبّق افتراض 1.
اصطلاحات التسمية
| اسم الملف | السلوك |
|---|---|
__root.tsx |
جذر الشجرة، يحوي Outlet العالمي وproviders |
index.tsx |
route تماماً للمجلد الأب |
about.tsx |
route ثابت /about |
$postId.tsx |
معامل ديناميكي، متاح عبر useParams |
$.tsx |
Splat catch-all، يلتقط الباقي في _splat |
_layout.tsx |
Layout pathless، يغلّف بلا مقطع URL |
route.tsx |
يمثّل route للمجلد الحاوي |
(group)/ |
مجموعة تنظيم، لا تظهر في URL |
posts.index.tsx |
تدوين مسطّح مكافئ لـ posts/index.tsx |
أخطاء شائعة
| العرَض | السبب | التصحيح |
|---|---|---|
routeTree.gen.ts not found |
الملحق Vite بعد react() |
ضع tanstackRouter أولاً في plugins |
useParams يُرجع undefined |
اسم ملف بلا $ |
أعد التسمية postId.tsx إلى $postId.tsx |
| Layout pathless مُتجاهَل | ملف layout.tsx بدل _layout.tsx |
أضف underscore بادئة |
| Search param يبقى string بدل number | لا validateSearch |
وصّل Zod مع z.number() |
| 404 splat يحجب route حقيقي | Splat في مجلد خاطئ | انقل $.tsx إلى الجذر |
| Devtools غير مرئية | build إنتاج | أطلق npm run dev |
أسئلة شائعة
هل ينبغي إصدار routeTree.gen.ts؟ artifact يُعاد توليده عند كل build. الممارسة الشائعة إضافته إلى .gitignore وتوليده في CI.
كيف نحصل على URL الحالي؟ استخدم useLocation. لرابط typé، فضّل مكوّن Link مع prop to الذي يقترح autocompletion.
هل يمكن خلط file-based وcode-based؟ نعم. routeTree.gen.ts كائن قابل للمعالجة. تستطيع حقن routes إضافية عبر API code-based للحالات الديناميكية.
الفرق بين _layout.tsx و(group)؟ Layout pathless يضيف مكوّن يغلّف. المجموعة حيلة تنظيم ملفات، بلا أثر على URL ولا على العرض.
كيف نمرّر بيانات من loader إلى المكوّن؟ أعلن loader في route، ثم استدع Route.useLoaderData() في المكوّن. التنميط يُستدلّ آلياً.