App Router مقابل Pages Router
فهم نظامي التوجيه
يدعم Next.js حاليًا نظامي توجيه: Pages Router التقليدي وApp Router الجديد المقدم في Next.js 13. فهم كلا النظامين أمر حاسم لأنك ستواجه مشاريع قديمة تستخدم Pages Router، بينما يجب أن تستخدم المشاريع الجديدة عادةً App Router لميزاته المتقدمة والأداء الأفضل.
نظرة عامة على Pages Router
كان Pages Router نظام التوجيه في Next.js منذ نشأته. إنه مستقر وموثق جيدًا ومستخدم على نطاق واسع في تطبيقات الإنتاج.
خصائص Pages Router
- الموقع: المسارات موجودة في مجلد
pages/ - قائم على الملفات: كل ملف يمثل مسارًا مباشرة
- العرض: العرض من جانب العميل افتراضيًا، مع خيار SSR وSSG
- جلب البيانات: يستخدم
getServerSidePropsوgetStaticPropsوgetStaticPaths - التخطيطات: تُنفذ يدويًا من خلال ملف
_app.js
مثال على بنية Pages Router
pages/
├── _app.js # مكون التطبيق المخصص (تخطيط عام)
├── _document.js # مستند مخصص (بنية HTML)
├── index.js # الصفحة الرئيسية (/)
├── about.js # صفحة من نحن (/about)
└── blog/
├── index.js # الصفحة الرئيسية للمدونة (/blog)
└── [slug].js # مقالة مدونة ديناميكية (/blog/:slug)
مثال على Pages Router
// pages/blog/[slug].js
import { useRouter } from 'next/router'
export default function BlogPost({ post }) {
const router = useRouter()
if (router.isFallback) {
return <div>جاري التحميل...</div>
}
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
// العرض من جانب الخادم
export async function getServerSideProps({ params }) {
const post = await fetchPost(params.slug)
return { props: { post } }
}
// أو التوليد الثابت
export async function getStaticProps({ params }) {
const post = await fetchPost(params.slug)
return { props: { post } }
}
export async function getStaticPaths() {
const posts = await fetchAllPosts()
return {
paths: posts.map(post => ({ params: { slug: post.slug } })),
fallback: false
}
}
نظرة عامة على App Router
App Router هو مستقبل التوجيه في Next.js. يقدم مفاهيم ثورية مثل مكونات خادم React والبث والتحكم الأكثر دقة في حالات التحميل والأخطاء.
خصائص App Router
- الموقع: المسارات موجودة في مجلد
app/ - قائم على المجلدات: كل مجلد يمكن أن يحتوي على ملفات خاصة متعددة (page.js، layout.js، loading.js، إلخ.)
- العرض: مكونات الخادم افتراضيًا، مع خيار مكونات العميل
- جلب البيانات: يستخدم async/await الأصلي مباشرة في المكونات
- التخطيطات: دعم من الدرجة الأولى مع تخطيطات متداخلة
- البث: دعم مدمج للعرض التدريجي
مثال على بنية App Router
app/
├── layout.js # التخطيط الجذري (يلف جميع الصفحات)
├── page.js # الصفحة الرئيسية (/)
├── loading.js # واجهة التحميل للصفحة الرئيسية
├── error.js # حد الأخطاء للصفحة الرئيسية
├── about/
│ └── page.js # صفحة من نحن (/about)
└── blog/
├── layout.js # تخطيط خاص بالمدونة
├── page.js # الصفحة الرئيسية للمدونة (/blog)
├── loading.js # واجهة التحميل للمدونة
└── [slug]/
├── page.js # مقالة المدونة (/blog/:slug)
└── loading.js # واجهة التحميل للمقالة
مثال على App Router
// app/blog/[slug]/page.js
// هذا مكون خادم افتراضيًا
async function getPost(slug) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
cache: 'force-cache' // يعادل SSG
// أو cache: 'no-store' لـ SSR
})
return res.json()
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
// توليد المعاملات الثابتة (مثل getStaticPaths)
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return posts.map(post => ({ slug: post.slug }))
}
الاختلافات المعمارية الرئيسية
1. مكونات الخادم مقابل العميل
Pages Router:
- جميع المكونات هي مكونات عميل افتراضيًا
- يتم إرسال JavaScript لكل مكون إلى المتصفح
- منطق جانب الخادم فقط في وظائف جلب البيانات
App Router:
- جميع المكونات هي مكونات خادم افتراضيًا
- لا يتم إرسال JavaScript إلى العميل إلا إذا لزم الأمر
- مكونات العميل تتطلب توجيه
'use client'
// App Router - مكون خادم (افتراضي)
export default function ServerComponent() {
// هذا يعمل فقط على الخادم
// لا يُرسل JavaScript إلى العميل
return <div>أنا مكون خادم</div>
}
// App Router - مكون عميل
'use client'
import { useState } from 'react'
export default function ClientComponent() {
const [count, setCount] = useState(0)
// هذا يعمل في المتصفح
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
2. جلب البيانات
Pages Router: يستخدم وظائف خاصة تعمل في وقت البناء أو وقت الطلب
// Pages Router - جلب البيانات
export default function Page({ data }) {
return <div>{data}</div>
}
// يعمل على كل طلب (SSR)
export async function getServerSideProps() {
const data = await fetchData()
return { props: { data } }
}
// يعمل في وقت البناء (SSG)
export async function getStaticProps() {
const data = await fetchData()
return { props: { data } }
}
App Router: يستخدم مكونات async وfetch الأصلي مع خيارات التخزين المؤقت
// App Router - جلب البيانات
export default async function Page() {
// الجلب مباشرة في المكون
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache' // سلوك SSG
// cache: 'no-store' // سلوك SSR
// next: { revalidate: 3600 } // سلوك ISR
}).then(r => r.json())
return <div>{data}</div>
}
3. التخطيطات
Pages Router: تخطيطات مخصصة من خلال _app.js أو وظائف getLayout لكل صفحة
// pages/_app.js - تخطيط عام
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
// تخطيط لكل صفحة
Page.getLayout = function getLayout(page) {
return (
<DashboardLayout>
{page}
</DashboardLayout>
)
}
App Router: دعم تخطيط من الدرجة الأولى مع التداخل
// app/layout.js - التخطيط الجذري (مطلوب)
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header />
{children}
<Footer />
</body>
</html>
)
}
// app/dashboard/layout.js - تخطيط متداخل
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
<Sidebar />
<main>{children}</main>
</div>
)
}
4. حالات التحميل
Pages Router: تنفيذ يدوي
// Pages Router - حالة تحميل يدوية
import { useRouter } from 'next/router'
import { useState, useEffect } from 'react'
export default function Page() {
const router = useRouter()
const [loading, setLoading] = useState(false)
useEffect(() => {
const handleStart = () => setLoading(true)
const handleComplete = () => setLoading(false)
router.events.on('routeChangeStart', handleStart)
router.events.on('routeChangeComplete', handleComplete)
return () => {
router.events.off('routeChangeStart', handleStart)
router.events.off('routeChangeComplete', handleComplete)
}
}, [router])
if (loading) return <div>جاري التحميل...</div>
return <div>المحتوى</div>
}
App Router: ملف loading.js مدمج
// app/blog/loading.js - واجهة تحميل تلقائية
export default function Loading() {
return <div>جاري تحميل مقالات المدونة...</div>
}
// يظهر هذا تلقائيًا أثناء تحميل page.js
5. معالجة الأخطاء
Pages Router: صفحات أخطاء مخصصة (_error.js) أو try/catch في المكونات
// pages/_error.js
function Error({ statusCode }) {
return (
<p>
{statusCode
? `حدث خطأ ${statusCode} على الخادم`
: 'حدث خطأ على العميل'}
</p>
)
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
export default Error
App Router: حد أخطاء error.js مدمج
// app/blog/error.js - حد الأخطاء
'use client' // يجب أن تكون مكونات الأخطاء مكونات عميل
export default function Error({ error, reset }) {
return (
<div>
<h2>حدث خطأ ما!</h2>
<p>{error.message}</p>
<button onClick={reset}>حاول مرة أخرى</button>
</div>
)
}
التأثيرات على الأداء
حجم الحزمة
Pages Router: يتم تجميع كل مكون وتبعياته وإرساله إلى العميل، حتى لو كانوا يعرضون محتوى ثابتًا فقط.
App Router: مكونات الخادم لا ترسل أبدًا JavaScript إلى العميل. فقط المكونات التفاعلية الموسومة بـ 'use client' يتم تجميعها.
البث وSuspense
يدعم App Router بث HTML من الخادم، مما يتيح وقتًا أسرع للبايت الأول والعرض التدريجي:
// App Router - البث مع Suspense
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<h1>مدونتي</h1>
{/* يعرض هذا فورًا */}
<Suspense fallback={<div>جاري تحميل المقالات...</div>}>
{/* يبث هذا عندما يكون جاهزًا */}
<Posts />
</Suspense>
</div>
)
}
async function Posts() {
const posts = await fetchPosts() // عملية بطيئة
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
اعتبارات الهجرة
متى تستخدم Pages Router
- صيانة التطبيقات الحالية المبنية بـ Pages Router
- المشاريع ذات المتطلبات المحددة التي لم يتم دعمها بعد في App Router
- الفرق غير المستعدة لتعلم أنماط جديدة
متى تستخدم App Router
- جميع المشاريع الجديدة (موصى به من قبل Vercel وفريق Next.js)
- التطبيقات التي تحتاج إلى أقصى أداء
- المشاريع التي تستفيد من مكونات الخادم (JavaScript أقل)
- التطبيقات التي تتطلب ميزات متقدمة مثل البث وSuspense
استراتيجية الهجرة التدريجية
يمكنك الهجرة تدريجيًا بوجود كلا الموجهين في نفس المشروع:
project/
├── app/ # مسارات جديدة باستخدام App Router
│ └── dashboard/
│ └── page.js # /dashboard
└── pages/ # مسارات قديمة باستخدام Pages Router
└── blog/
└── [slug].js # /blog/:slug
app/about/page.js وpages/about.js، فقط إصدار App Router سيكون قابلاً للوصول.
جدول المقارنة
الميزة | Pages Router | App Router ---------------------|---------------------|-------------------- المجلد | pages/ | app/ المكون الافتراضي | مكون عميل | مكون خادم جلب البيانات | وظائف خاصة | async/await في المكون التخطيطات | يدوي عبر _app | تخطيطات متداخلة مدمجة حالات التحميل | يدوي | ملف loading.js معالجة الأخطاء | _error.js | حد أخطاء error.js البث | غير مدعوم | دعم مدمج ملفات المسار | ملفات مسماة | ملفات خاصة في مجلدات الأداء | جيد | أفضل (JS أقل) منحنى التعلم | أسهل | أشد انحدارًا في البداية الاستقرار | مستقر جدًا | مستقر (13.4+)
- أنشئ نفس الصفحة باستخدام كل من Pages Router وApp Router لفهم الاختلافات
- ابنِ صفحة مقالة مدونة مع حالات التحميل والأخطاء في App Router
- قارن أحجام الحزم: أنشئ صفحات متطابقة بكلا الموجهين وتحقق من حجم حزمة JavaScript في علامة تبويب Network
- نفذ تخطيطات متداخلة في App Router (مثل التخطيط الجذري → تخطيط لوحة التحكم → صفحة محددة)
- حول مكون Pages Router إلى App Router، محددًا أي الأجزاء يجب أن تكون مكونات خادم مقابل مكونات عميل
- أنشئ مثال بث باستخدام Suspense في App Router
- جرب استراتيجيات تخزين مؤقت مختلفة (force-cache، no-store، revalidate) في جلب بيانات App Router
الخلاصة
يمثل App Router تطورًا كبيرًا في معمارية Next.js، حيث يجلب ميزات جديدة قوية مع الحفاظ على البساطة التي جعلت Next.js شائعًا. بينما يظل Pages Router مستقرًا ومدعومًا، فإن App Router هو الخيار الموصى به للمشاريع الجديدة بسبب أدائه المتفوق وتجربة المطور الأفضل مع ميزات مثل حالات التحميل والأخطاء المدمجة والبنية المستقبلية مع مكونات خادم React.
النقاط الرئيسية:
- يستخدم App Router مكونات الخادم افتراضيًا، مما يقلل JavaScript المرسل إلى العميل
- جلب البيانات أكثر سهولة مع async/await مباشرة في المكونات
- التخطيطات وحالات التحميل والأخطاء لها دعم من الدرجة الأولى
- يمكن لكلا الموجهين التعايش أثناء الهجرة
- اختر App Router للمشاريع الجديدة؛ حافظ على Pages Router للتطبيقات الموجودة
في الدرس القادم، سنتعمق في التخطيطات والقوالب في App Router، واستكشاف كيفية إنشاء بنى واجهة مستخدم قابلة لإعادة الاستخدام وتحسين معمارية تطبيقك.