Next.js
App Router في Next.js — دليل شامل 2026
📅 2026-11-13⏱ 8 دقائق قراءة
في المقال السابق، بنيت أول تطبيق Next.js وتعلمت الأساسيات. الآن سنتعمق في **App Router** — وهو **نظام التوجيه الحديث** في Next.js 15.
في هذا الدليل العملي، سنأخذك خطوة بخطوة لفهم App Router بالكامل، مع تمارين وحلول.
## ما هو App Router؟
**App Router** هو نظام التوجيه **الجديد** في Next.js (منذ الإصدار 13).
**يعتمد على:** **نظام الملفات هو الراوتر**.
<table>
<thead>
<tr>
<th>الملف</th>
<th>الرابط</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>app/page.tsx</code></td>
<td><code>/</code></td>
</tr>
<tr>
<td><code>app/about/page.tsx</code></td>
<td><code>/about</code></td>
</tr>
<tr>
<td><code>app/blog/page.tsx</code></td>
<td><code>/blog</code></td>
</tr>
<tr>
<td><code>app/blog/[slug]/page.tsx</code></td>
<td><code>/blog/any-post</code></td>
</tr>
<tr>
<td><code>app/(shop)/cart/page.tsx</code></td>
<td><code>/cart</code></td>
</tr>
<tr>
<td><code>app/dashboard/settings/page.tsx</code></td>
<td><code>/dashboard/settings</code></td>
</tr>
</tbody>
</table>
## أنواع الملفات في App Router
<table>
<thead>
<tr>
<th>الملف</th>
<th>الوظيفة</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>page.tsx</code></td>
<td>صفحة (URL)</td>
</tr>
<tr>
<td><code>layout.tsx</code></td>
<td>تخطيط مشترك</td>
</tr>
<tr>
<td><code>loading.tsx</code></td>
<td>شاشة التحميل</td>
</tr>
<tr>
<td><code>error.tsx</code></td>
<td>معالج الأخطاء</td>
</tr>
<tr>
<td><code>not-found.tsx</code></td>
<td>صفحة 404</td>
</tr>
<tr>
<td><code>route.ts</code></td>
<td>API Endpoint</td>
</tr>
<tr>
<td><code>template.tsx</code></td>
<td>قالب (يعاد عند التنقل)</td>
</tr>
<tr>
<td><code>default.tsx</code></td>
<td>للمسارات المتوازية</td>
</tr>
</tbody>
</table>
## المسارات الأساسية
### 1. الصفحة الرئيسية
**`app/page.tsx`** → `/`
### 2. مسار عادي
**`app/about/page.tsx`** → `/about`
### 3. مسار متداخل
**`app/blog/tech/page.tsx`** → `/blog/tech`
### 4. مسار ديناميكي
**`app/blog/[slug]/page.tsx`** → `/blog/أي-شيء`
**`app/blog/[slug]/page.tsx`:**
```tsx
export default function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
return <h1>المقال: {params.slug}</h1>;
}
```
**⚠️ لاحظ:** في Next.js 15، `params` هو **Promise** — يجب استخدام `async/await`.
### 5. مسار ديناميكي شامل
**`app/docs/[...slug]/page.tsx`** → `/docs/a/b/c`
```tsx
export default async function Docs({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
return <h1>المسار: {slug.join("/")}</h1>;
}
```
### 6. مسار اختياري
**`app/shop/[[...slug]]/page.tsx`** → `/shop` أو `/shop/a/b`
## مجموعات المسارات (Route Groups)
استخدم `( )` لتنظيم المسارات **بدون التأثير على URL**.
```
app/
├── (marketing)/
│ ├── about/
│ │ └── page.tsx → /about
│ └── contact/
│ └── page.tsx → /contact
├── (shop)/
│ ├── products/
│ │ └── page.tsx → /products
│ └── cart/
│ └── page.tsx → /cart
└── layout.tsx
```
**الفائدة:** تخطيط مختلف لكل مجموعة:
```
app/
├── (marketing)/
│ ├── layout.tsx # تخطيط التسويق
│ └── about/page.tsx
└── (shop)/
├── layout.tsx # تخطيط المتجر
└── cart/page.tsx
```
## Layouts المتداخلة
```
app/
├── layout.tsx # التخطيط الجذري
├── page.tsx
└── dashboard/
├── layout.tsx # تخطيط لوحة التحكم
├── page.tsx
└── settings/
└── page.tsx
```
**`app/dashboard/layout.tsx`:**
```tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<aside className="w-64 bg-gray-900 text-white p-4">
<nav>
<a href="/dashboard">لوحة التحكم</a>
<a href="/dashboard/settings">الإعدادات</a>
</nav>
</aside>
<main className="flex-1 p-8">{children}</main>
</div>
);
}
```
**النتيجة:** كل صفحات `/dashboard/*` ستحتوي على الشريط الجانبي.
## Loading — شاشة التحميل
**`app/dashboard/loading.tsx`:**
```tsx
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-4 border-blue-600 border-t-transparent" />
</div>
);
}
```
**النتيجة:** عند تحميل أي صفحة في `/dashboard`، تظهر شاشة التحميل.
## Error — معالج الأخطاء
**`app/dashboard/error.tsx`:**
```tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="text-center p-8">
<h1 className="text-4xl font-bold text-red-600 mb-4">
❌ حدث خطأ
</h1>
<p className="text-gray-600 mb-6">{error.message}</p>
<button
onClick={reset}
className="bg-blue-600 text-white px-6 py-3 rounded-lg"
>
حاول مرة أخرى
</button>
</div>
);
}
```
**⚠️ مهم:** `error.tsx` يجب أن يكون **Client Component** (`"use client"`).
## Not Found — صفحة 404
**`app/not-found.tsx`:**
```tsx
import Link from "next/link";
export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center text-center">
<div>
<h1 className="text-9xl font-bold text-gray-900">404</h1>
<p className="text-2xl text-gray-600 mb-8">الصفحة غير موجودة</p>
<Link
href="/"
className="bg-blue-600 text-white px-8 py-3 rounded-lg"
>
العودة للرئيسية
</Link>
</div>
</div>
);
}
```
## التنقل بين الصفحات
### 1. `<Link>` — الطريقة الأساسية
```tsx
import Link from "next/link";
<Link href="/about">من نحن</Link>
```
### 2. `useRouter` — التنقل برمجياً
```tsx
"use client";
import { useRouter } from "next/navigation";
export default function LoginButton() {
const router = useRouter();
const handleLogin = () => {
// ... منطق تسجيل الدخول
router.push("/dashboard");
};
return <button onClick={handleLogin}>تسجيل الدخول</button>;
}
```
### 3. `redirect` — إعادة التوجيه
```tsx
import { redirect } from "next/navigation";
export default async function Page() {
const isLoggedIn = false;
if (!isLoggedIn) {
redirect("/login");
}
return <h1>مرحباً</h1>;
}
```
## useRouter و usePathname
**`app/components/NavLink.tsx`:**
```tsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
interface NavLinkProps {
href: string;
children: React.ReactNode;
}
export default function NavLink({ href, children }: NavLinkProps) {
const pathname = usePathname();
const isActive = pathname === href;
return (
<Link
href={href}
className={`px-4 py-2 rounded-lg transition ${
isActive
? "bg-blue-600 text-white"
: "text-gray-700 hover:bg-gray-100"
}`}
>
{children}
</Link>
);
}
```
## generateStaticParams
**للمسارات الديناميكية في التصدير الثابت:**
```tsx
export function generateStaticParams() {
const posts = [
{ slug: "post-1" },
{ slug: "post-2" },
];
return posts.map((post) => ({
slug: post.slug,
}));
}
```
## مثال عملي: مدونة
### 1. الصفحة الرئيسية `app/page.tsx`
```tsx
import Link from "next/link";
export default function HomePage() {
return (
<main className="p-8">
<h1 className="text-4xl font-bold mb-6">📝 مدونتي</h1>
<Link href="/blog" className="text-blue-600 hover:underline">
تصفح المقالات →
</Link>
</main>
);
}
```
### 2. قائمة المقالات `app/blog/page.tsx`
```tsx
import Link from "next/link";
const posts = [
{ slug: "learn-nextjs", title: "تعلم Next.js" },
{ slug: "react-hooks", title: "React Hooks" },
{ slug: "typescript-tips", title: "نصائح TypeScript" },
];
export default function BlogPage() {
return (
<main className="p-8 max-w-4xl mx-auto">
<h1 className="text-4xl font-bold mb-8">المقالات</h1>
<ul className="space-y-4">
{posts.map((post) => (
<li key={post.slug}>
<Link
href={`/blog/${post.slug}`}
className="text-xl text-blue-600 hover:underline"
>
{post.title}
</Link>
</li>
))}
</ul>
</main>
);
}
```
### 3. المقال الفردي `app/blog/[slug]/page.tsx`
```tsx
import Link from "next/link";
import { notFound } from "next/navigation";
const posts = {
"learn-nextjs": { title: "تعلم Next.js", content: "..." },
"react-hooks": { title: "React Hooks", content: "..." },
"typescript-tips": { title: "نصائح TypeScript", content: "..." },
};
export function generateStaticParams() {
return Object.keys(posts).map((slug) => ({ slug }));
}
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = posts[slug as keyof typeof posts];
if (!post) notFound();
return (
<main className="p-8 max-w-4xl mx-auto">
<Link href="/blog" className="text-blue-600 hover:underline mb-8 inline-block">
← العودة
</Link>
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<p>{post.content}</p>
</main>
);
}
```
## تمارين عملية
### تمرين 1: 3 صفحات متداخلة
أنشئ `/dashboard/profile` و `/dashboard/settings`.
**الحل:**
```
app/dashboard/profile/page.tsx
app/dashboard/settings/page.tsx
```
### تمرين 2: مسار ديناميكي
أنشئ `/users/[id]` يعرض ID المستخدم.
**الحل:**
```tsx
export default async function UserPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <h1>المستخدم: {id}</h1>;
}
```
### تمرين 3: Route Group
نظّم الصفحات في مجموعتين.
**الحل:**
```
app/(marketing)/about/page.tsx
app/(shop)/products/page.tsx
```
### تمرين 4: Layout مخصص
أنشئ Layout للوحة التحكم.
**الحل:**
```tsx
// app/dashboard/layout.tsx
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}
```
### تمرين 5: Loading
أضف شاشة تحميل.
**الحل:**
```tsx
// app/loading.tsx
export default function Loading() {
return <div>جاري التحميل...</div>;
}
```
### تمرين 6: Error
أضف معالج أخطاء.
**الحل:**
```tsx
"use client";
export default function Error({ reset }: { reset: () => void }) {
return (
<div>
<h1>خطأ!</h1>
<button onClick={reset}>حاول مرة أخرى</button>
</div>
);
}
```
### تمرين 7: NavLink نشط
أنشئ NavLink يظهر الرابط النشط.
**الحل:** (راجع `NavLink.tsx` أعلاه)
### تمرين 8: مدونة كاملة
ابنِ مدونة بكل ما تعلمته.
**الحل:** (راجع المثال العملي أعلاه)
## حل المشاكل الشائعة
### 🔴 المشكلة 1: `params` غير متاح مباشرة
**السبب:** في Next.js 15، `params` هو Promise.
**الحل:**
```tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
}
```
### 🔴 المشكلة 2: Link لا يعمل
**السبب:** نسيت الاستيراد.
**الحل:**
```tsx
import Link from "next/link";
```
### 🔴 المشكلة 3: useRouter لا يعمل
**السبب:** لم تُضف `"use client"`.
**الحل:** أضف في أعلى الملف.
### 🔴 المشكلة 4: generateStaticParams مفقود
**السبب:** مع `output: 'export'`، تحتاج تعريف كل المسارات.
**الحل:**
```tsx
export function generateStaticParams() {
return [{ slug: "post-1" }, { slug: "post-2" }];
}
```
### 🔴 المشكلة 5: 404 في الإنتاج
**السبب:** السيرفر لا يعرف المسارات.
**الحل:** أضف `rewrites` في `firebase.json`.
## جدول الملفات الخاصة
<table>
<thead>
<tr>
<th>الملف</th>
<th>الوظيفة</th>
<th>ملاحظة</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>page.tsx</code></td>
<td>صفحة</td>
<td>مطلوب للعرض</td>
</tr>
<tr>
<td><code>layout.tsx</code></td>
<td>تخطيط</td>
<td>لا يُعاد عند التنقل</td>
</tr>
<tr>
<td><code>template.tsx</code></td>
<td>قالب</td>
<td>يُعاد عند التنقل</td>
</tr>
<tr>
<td><code>loading.tsx</code></td>
<td>تحميل</td>
<td>يظهر أثناء التحميل</td>
</tr>
<tr>
<td><code>error.tsx</code></td>
<td>خطأ</td>
<td>Client Component</td>
</tr>
<tr>
<td><code>not-found.tsx</code></td>
<td>404</td>
<td>عند عدم وجود الصفحة</td>
</tr>
<tr>
<td><code>route.ts</code></td>
<td>API</td>
<td>لا يعرض UI</td>
</tr>
</tbody>
</table>
## قائمة تحقق نهائية
<table>
<thead>
<tr>
<th>المهمة</th>
<th>الحالة</th>
</tr>
</thead>
<tbody>
<tr>
<td>فهم نظام الملفات هو الراوتر</td>
<td>⬜</td>
</tr>
<tr>
<td>إنشاء مسارات متداخلة</td>
<td>⬜</td>
</tr>
<tr>
<td>المسارات الديناميكية</td>
<td>⬜</td>
</tr>
<tr>
<td>Route Groups</td>
<td>⬜</td>
</tr>
<tr>
<td>Layouts المتداخلة</td>
<td>⬜</td>
</tr>
<tr>
<td>Loading و Error</td>
<td>⬜</td>
</tr>
<tr>
<td>حل التمارين الثمانية</td>
<td>⬜</td>
</tr>
</tbody>
</table>
## ماذا بعد هذا المقال؟
الآن بعد أن أتقنت App Router، أنت جاهز للمقال التالي:
1. **Server Components** — تعمق.
2. **Data Fetching** — جلب البيانات.
3. **API Routes** — بناء APIs.
## الخلاصة
في هذا المقال، تعلمت:
- ✅ ما هو App Router.
- ✅ نظام الملفات هو الراوتر.
- ✅ المسارات الديناميكية.
- ✅ Route Groups.
- ✅ Layouts المتداخلة.
- ✅ Loading و Error.
- ✅ التنقل بين الصفحات.
- ✅ حل 8 تمارين عملية.
**تذكر:** App Router هو **المستقبل** — أتقنه جيداً.