دليل Expo Router v7 الكامل: التوجيه بالملفات والروابط العميقة (2026)
دليل عملي لإعداد Expo Router v7 في React Native: التوجيه القائم على الملفات، التخطيطات، المسارات الديناميكية، المصادقة المحمية، والروابط العميقة على iOS و Android.
Expo Router v7 هو إطار توجيه قائم على الملفات لتطبيقات React Native يحوّل بنية مجلد app/ إلى شجرة تنقّل تلقائية، مع دعم كامل لـ TypeScript والروابط العميقة (Deep Linking) والـ Universal Links على iOS و App Links على Android. في هذا الدليل سأشرح خطوة بخطوة كيفية إعداد Expo Router v7 ضمن مشروع Expo SDK 55، وكيفية بناء التخطيطات (Layouts) والمسارات الديناميكية والمجموعات المحمية بالمصادقة، مع أمثلة كود قابلة للتشغيل مباشرة.
Expo Router v7 أصبح مبنياً على React Navigation v7 ويفعّل البنية الجديدة (New Architecture) افتراضياً منذ SDK 55.
كل ملف داخل مجلد app/ يصبح شاشة، وكل _layout.tsx يصبح تخطيطاً يحيط بالشاشات داخل مجلده.
المسارات الديناميكية تُكتب على شكل [id].tsx أو [...slug].tsx ويتم الوصول إلى المعاملات عبر useLocalSearchParams.
الروابط العميقة تعمل تلقائياً عبر scheme في app.json، أما Universal Links فتحتاج ملف apple-app-site-association.
الـ Typed Routes تتيح التحقق من المسارات في وقت الترجمة وتمنع الأخطاء الإملائية في router.push.
المجموعات المحمية (Protected Routes) تُبنى عبر تخطيطات شرطية تستخدم Redirect عند غياب الجلسة.
ما هو Expo Router v7 ولماذا تستخدمه؟
Expo Router هو طبقة توجيه فوق React Navigation تستلهم فلسفتها من Next.js: بدلاً من تسجيل الشاشات يدوياً في كائن Stack.Navigator، تقوم بإنشاء ملفات داخل مجلد app/ فيتعرّف عليها الموجّه تلقائياً ويبني شجرة التنقّل. في الإصدار v7 الصادر مع دليل ترقية Expo SDK 55، تمت إعادة كتابة طبقة الربط بـ React Navigation v7، وأصبح دعم الـ Typed Routes افتراضياً عند تفعيل experiments.typedRoutes.
الميزة الكبرى هي أن الروابط العميقة تعمل خارج الصندوق. إذا كان لديك مسار app/post/[id].tsx، فإن الرابط myapp://post/42 يفتح الشاشة الصحيحة بدون أي إعداد إضافي على Android، وبإعداد بسيط على iOS. صراحةً، في مشروع شحنته العام الماضي، أدّى الانتقال إلى Expo Router إلى تقليل كود التنقّل بنحو 40% وإزالة كل ملفات navigation/types.ts اليدوية. وبما أن الموجّه يدعم العرض على الويب (web) أيضاً، يمكنك مشاركة نفس شجرة التنقّل بين iOS و Android و web دون تعديل.
أخيراً، أنشئ مجلد app/ في جذر المشروع وأضف الملف app/_layout.tsx الذي يُمثّل التخطيط الجذري. هذا الملف إلزامي. بدونه لن يقوم Expo Router بتسجيل أي شاشة، وستحصل على شاشة بيضاء عند بدء التشغيل دون أي رسالة خطأ واضحة في وحدة التحكم (وهذا فخ شائع جداً عند بدء أول مشروع).
التوجيه القائم على الملفات: من الملف إلى الشاشة
القاعدة الأساسية بسيطة: كل ملف .tsx داخل app/ يصبح مساراً، واسم الملف يصبح جزءاً من المسار. مثال:
المكوّن Link يولّد رابطاً قابلاً للضغط ويتولّى التنقّل عند النقر. أما برمجياً فاستخدم useRouter:
import { useRouter } from 'expo-router';
const router = useRouter();
router.push('/post/42'); // إضافة شاشة فوق المكدّس
router.replace('/login'); // استبدال الشاشة الحالية
router.back(); // العودة للخلف
كيف تعمل التخطيطات (_layout.tsx)؟
كل ملف _layout.tsx يحدّد كيف تُغلَّف الشاشات الموجودة في مجلده. هذا هو المكان الذي تختار فيه نوع المُلاح: Stack أو Tabs أو Drawer. التخطيط الجذري النموذجي يبدو كالتالي:
لاحظ أن أسماء الشاشات في <Stack.Screen name="..."> تُطابق مسارات الملفات نسبةً إلى مجلد التخطيط (بدون امتداد). ولبناء واجهة تبويبات، استخدم Tabs داخل مجلد فرعي:
الأقواس في اسم المجلد (tabs) تجعله "مجموعة" (أي أنه لا يظهر في عنوان الـ URL لكنه يجمع التخطيط). سنتعمّق في المجموعات بعد قليل. ميزة أخرى مفيدة في التخطيطات هي إمكانية تمرير initialRouteName لتحديد الشاشة الافتراضية عند فتح التطبيق من رابط عميق غير معروف.
المسارات الديناميكية وتمرير المعاملات
الأقواس المربعة في اسم الملف تجعل المسار ديناميكياً. الملف app/post/[id].tsx يلتقط أي قيمة في موضع الـ id، والملف app/blog/[...slug].tsx يلتقط مسارات متعدّدة المستويات (Catch-All). داخل الشاشة، اقرأ المعاملات عبر الـ Hook المخصّص:
import { useLocalSearchParams, Stack } from 'expo-router';
import { Text, View } from 'react-native';
export default function PostScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
return (
<View>
<Stack.Screen options={{ title: `Post #${id}` }} />
<Text>Showing post with id: {id}</Text>
</View>
);
}
يمكنك أيضاً تمرير معاملات استعلام (query params) دون الحاجة إلى مسار ديناميكي:
للمسارات الاختيارية، استخدم بنية مزدوجة الأقواس مثل [[...slug]].tsx. هذه البنية مفيدة لشاشات مثل توثيق متعدد الصفحات حيث قد يكون المسار فارغاً (يعرض الصفحة الرئيسية) أو طويلاً. وانتبه إلى الفرق بين useLocalSearchParams الذي يعطيك معاملات الشاشة الحالية فقط، و useGlobalSearchParams الذي يعطيك معاملات أعلى شاشة في المكدّس، فاستخدم الأول في 95% من الحالات.
مجموعات المسارات والمسارات المحمية بالمصادقة
المجموعات (Groups) هي مجلدات داخل app/ اسمها محاط بأقواس مستديرة مثل (auth) أو (tabs). لا تظهر في الـ URL، لكنها تتيح لك تطبيق تخطيط مختلف على مجموعة من الشاشات. الاستخدام الأكثر شيوعاً هو فصل تدفّق المصادقة عن التطبيق الرئيسي:
لحماية مجموعة (app) من الوصول غير المصادَق، استخدم مكوّن Redirect ضمن تخطيطها:
import { Redirect, Stack } from 'expo-router';
import { useAuth } from '../../hooks/useAuth';
export default function AppLayout() {
const { session, isLoading } = useAuth();
if (isLoading) return null;
if (!session) return <Redirect href="/login" />;
return <Stack />;
}
هذا النمط يضمن أن أي محاولة وصول إلى مسار داخل (app) دون جلسة نشطة ستُوجَّه فوراً إلى شاشة تسجيل الدخول. يمكن دمجها مع بناء تطبيقات React Native بدون إنترنت للحفاظ على المصادقة عند العمل بدون إنترنت.
كيف تُعدّ الروابط العميقة و Universal Links؟
الروابط العميقة المخصّصة (Custom URL Scheme) تعمل تلقائياً متى أضفت "scheme": "myapp" في app.json. بعد ذلك يفتح الرابط myapp://post/42 الشاشة app/post/[id].tsx بقيمة id=42. لاختبار ذلك أثناء التطوير:
npx uri-scheme open "myapp://post/42" --ios
npx uri-scheme open "myapp://post/42" --android
أما Universal Links على iOS و App Links على Android فتسمح للروابط الحقيقية مثل https://yoursite.com/post/42 بفتح التطبيق مباشرة. هذا يتطلّب:
إضافة associatedDomains في ios داخل app.json.
استضافة ملف apple-app-site-association على https://yoursite.com/.well-known/.
لـ Android، إضافة intentFilters مع autoVerify: true وإنشاء ملف assetlinks.json.
للاستماع للروابط داخل التطبيق (مثلاً لتسجيل تحليلات)، استخدم useURL من expo-linking الذي يعرض الرابط الذي فتح التطبيق. ولمزيد من التفاصيل راجع دليل Expo الرسمي للروابط.
المسارات المُكتَّبة (Typed Routes) في TypeScript
عند تفعيل experiments.typedRoutes: true، يقوم Expo CLI بإنشاء ملف .expo/types/router.d.ts يحتوي على اتحاد سلاسل لكل المسارات الممكنة. هذا يعني أن TypeScript سيرفض أي مسار غير موجود:
router.push('/post/42'); // OK
router.push('/psot/42'); // خطأ ترجمة: المسار غير موجود
router.push({ pathname: '/post/[id]', params: { id: '42' } }); // OK
في تطبيق بـ 60+ شاشة، أنقذتني هذه الميزة من ثلاث حالات إنتاج خلال أسبوع واحد بعد تفعيلها (كلها أخطاء إملائية في مسارات لم تكن مغطّاة باختبارات). شغّل npx expo start --clear بعد إضافة مسار جديد كي يُعاد توليد الأنواع.
الانتقال من Expo Router v3 إلى v7
إذا كنت تنتقل من إصدار سابق، فإن أهم التغييرات الكاسرة في v7 هي:
React Navigation v7: تم استبدال طبقة الربط الداخلية. APIs مثل useNavigation() ما زالت تعمل لكن بعض الأنواع تغيّرت، فراجع دليل الترقية الرسمي.
إزالة href ذو الكائن البسيط: الآن يجب استخدام { pathname, params } صراحةً للمسارات الديناميكية.
المسارات المحمية بمكوّن Protected: بدلاً من Redirect داخل التخطيط، يدعم v7 الآن مكوّن Stack.Protected الذي يخفي الشاشة من شجرة التنقّل بالكامل عند عدم الإذن.
تحسينات الأداء: عند تفعيل البنية الجديدة (New Architecture) يصبح التنقّل بين الشاشات أسرع بنحو 30% بفضل Fabric.
الترتيب الموصى به للترقية:
رقّ Expo SDK أولاً إلى 55: npx expo install expo@^55.
حدّث expo-router: npx expo install expo-router.
شغّل npx expo-doctor لاكتشاف عدم التوافق.
عدّل أي استدعاءات router.push('/post/' + id) لتستخدم البنية الكائنية.
اختبر الروابط العميقة على جهاز حقيقي، فالمحاكي لا يكشف دائماً مشاكل Universal Links.
إذا كان مشروعك يعتمد على EAS Build فلا تنسَ تحديث ملفات eas.json لاستخدام "image": "latest" على الأقل، فالصور الأقدم لا تدعم New Architecture بشكل كامل.
الأسئلة الشائعة
ما الفرق بين Expo Router و React Navigation؟
React Navigation هو المكتبة الأساسية للتنقّل في React Native وتتطلّب تسجيل كل شاشة يدوياً. Expo Router هو طبقة فوقها تستخدم نظام ملفات لتسجيل الشاشات تلقائياً وتُضيف دعم الروابط العميقة وميزات الويب. كل تطبيق Expo Router يستخدم React Navigation داخلياً، لذا يمكنك مزج الأسلوبين عند الحاجة.
هل يمكنني استخدام Expo Router v7 بدون TypeScript؟
نعم، Expo Router يعمل مع JavaScript عادي، لكنك ستفقد ميزة Typed Routes التي تتحقّق من صحة المسارات في وقت الترجمة. الأنماط البرمجية الأخرى (التخطيطات، المسارات الديناميكية، الروابط العميقة) متطابقة بين JS و TS.
كيف أعرض شاشة Modal باستخدام Expo Router؟
أضف الخيار presentation: 'modal' ضمن Stack.Screen الخاص بالشاشة في التخطيط، أو ضع الشاشة داخل مجموعة (modal) بتخطيط Stack مخصّص. ثم انتقل إليها بـ router.push('/modal-screen') وستظهر بتأثير صعود من الأسفل تلقائياً.
لماذا لا تعمل الروابط العميقة على Android في وضع الإنتاج؟
السبب الأكثر شيوعاً هو غياب ملف assetlinks.json على نطاقك أو وجود توقيع تطبيق غير صحيح فيه. تحقّق من البصمة عبر keytool -list -v -keystore وتأكّد أن autoVerify: true مضبوط في intentFilters. كذلك يجب أن يكون الملف متاحاً على HTTPS وبنوع MIME application/json.
هل Expo Router مناسب للتطبيقات الكبيرة؟
نعم، استخدمته في تطبيقات تتجاوز 100 شاشة دون مشاكل في الأداء. التحدّي الوحيد هو تنظيم بنية المجلدات. يُنصح بتقسيم الميزات إلى مجموعات (Groups) واستخدام _layout.tsx لكل ميزة لتجميع منطق التخطيط والحماية في مكان واحد.
دليل عملي لبناء تطبيقات React Native بنية Offline-First باستخدام expo-sqlite و Drizzle ORM. يغطّي الإعداد الكامل، عمليات CRUD مع أمان النوع، المزامنة التلقائية مع الخادم، تحسين الأداء، والتشفير — مع أمثلة كود تطبيقية.
رقِّ مشروعك إلى Expo SDK 55 مع React Native 0.83. يغطي هذا الدليل البنية الجديدة الإجبارية، Expo Router v7، Hermes v1، إزالة expo-av، وخطوات الترقية العملية مع أمثلة كود.