WebSockets على Vercel Functions: تواصل مباشر بلا خادم منفصل
جدول المحتويات
عن الكاتب

Ahmed Mahmoud
كاتب ومطوّر
مهندس برمجيات شغوف بتطوير الويب وتصميم تجربة المستخدم.
مؤسّس ديفيا · eng-ahmed.com ↗أهم النقاط
دوال Vercel Functions تدعم WebSockets بشكل أصلي عبر experimental_upgradeWebSocket() من حزمة @vercel/functions، وتعمل على Fluid Compute — دون الحاجة لخادم WebSocket منفصل أو خدمة realtime خارجية.
Fluid Compute هو نظام تشغيل الدوال في Vercel الذي يعيد استخدام نسخة (instance) دافئة عبر عدة طلبات متزامنة بدلاً من تشغيل نسخة جديدة لكل طلب؛ إعادة استخدام النسخة هذه هي ما يجعل إبقاء اتصال socket طويل الأمد مفتوحاً أمراً عملياً على منصة اعتادت تاريخياً على دورات طلب/استجابة قصيرة.
مكتبات Node.js القياسية مثل ws تعمل داخل دالة Vercel دون تعديل — فريقنا لم يُعِد كتابة منطق الـ socket في العميل أو الخادم للانتقال من خادم مخصص.
WebSockets تظل الخيار الأفضل مقارنة بـ Server-Sent Events في أي ميزة ثنائية الاتجاه: إدخال محادثة، مؤشرات تعاونية، حالة لعبة متعددة اللاعبين. أما SSE فيبقى الخيار الأبسط للبث أحادي الاتجاه.
خدمات مُدارة مثل Pusher أو Ably لا تزال تستحق مكانها عند نطاق توزيع كبير جداً أو عند الحاجة لبدائل حضور (presence) وغرف جاهزة فوراً؛ أما لميزة realtime واحدة داخل تطبيق واحد، فاستضافة الحل ذاتياً على Vercel Functions أصبحت خياراً افتراضياً واقعياً وليس حلاً بديلاً مؤقتاً.
هل يمكن لدالة Vercel أن تبقي اتصال WebSocket مفتوحاً فعلياً؟
نعم، طالما تعمل على Fluid Compute، وهو نظام التشغيل الافتراضي لدوال Vercel. النموذج الذهني القديم للـ serverless — حاوية معزولة لكل استدعاء، تُغلق فور انتهاء الاستجابة — لم يكن لديه مكان لاتصال يبقى مفتوحاً لدقائق أو ساعات.
Fluid Compute يغيّر وحدة العمل: يُعاد استخدام النسخ عبر الطلبات المتزامنة، وتستطيع الدالة إبقاء socket أو stream أو مؤقت خلفي حياً عبر تلك النسخة المُعاد استخدامها بدلاً من أن يموت فور إرجاع الاستجابة. لهذا السبب أيضاً يدعم النظام الإغلاق التدريجي (graceful shutdown) وإلغاء الطلبات على نفس نظام التشغيل — فالدالة التي تكون في منتصف البث أثناء نشر جديد تحصل على فرصة لإغلاق الاتصالات بشكل نظيف بدلاً من إيقافها قسراً.
بالنسبة لميزة محادثة أو لوحة تحكم مباشرة نبنيها لعميل، هذا هو الفرق بين socket ينقطع بصمت مع كل نشر وآخر يعيد الاتصال بشكل متوقع.
كيف نرقّي دالة Vercel لاتصال WebSocket؟
نستدعي experimental_upgradeWebSocket() من @vercel/functions داخل معالج المسار (route handler)، بنفس طريقة ترقية اتصال HTTP على أي خادم Node.js. يعرض Next.js هذا عبر معالجات المسارات في App Router، فتبقى الترقية بجانب كود API العادي بدلاً من خدمة منفصلة.
في جانب العميل، الأمر مجرد كائن WebSocket عادي — لا شيء خاص بـ Vercel يحتاج تثبيتاً. إذا كان مشروع ما يستخدم بالفعل ws لتجميع الاتصالات أو الغرف أو نبضات القلب (heartbeat)، فذلك الكود ينتقل دون تغيير؛ نقطة الترقية هي الجزء الوحيد الخاص بـ Vercel.
متى يجب اللجوء إلى WebSockets بدلاً من SSE أو الاستطلاع (polling)؟
السؤال الحاسم هو الاتجاه: هل يحتاج العميل لإرسال رسائل متكررة للخلف، وليس فقط الاستقبال؟ إن كانت الإجابة نعم، فـ WebSockets هي الأداة الصحيحة. إن كان العميل يستمع فقط، فـ Server-Sent Events أبسط في التشغيل والتصحيح.
مقارنة: WebSocket مقابل SSE مقابل polling
WebSocket — ثنائي الاتجاه، له ترقية بروتوكول خاصة به (ws:// أو wss://)، إعادة الاتصال يدوية، الأنسب للمحادثات والتحرير التعاوني ومؤشرات الألعاب متعددة اللاعبين، يحتاج experimental_upgradeWebSocket() مع Fluid Compute على Vercel.
Server-Sent Events (SSE) — من الخادم إلى العميل فقط، HTTP عادي مع text/event-stream، إعادة الاتصال مدمجة في واجهة EventSource بالمتصفح، الأنسب لبث الرموز (tokens) والإشعارات والسجلات المباشرة، يعمل على أي نظام تشغيل Node.js دون واجهة برمجية خاصة.
Polling — طلبات متكررة يبدأها العميل، HTTP عادي، لا يوجد اتصال دائم على الإطلاق، الأنسب للفحوصات منخفضة التكرار، لا يحتاج بنية تحتية خاصة.
كنا قد تناولنا SSE لبث رموز الذكاء الاصطناعي في تدوينة سابقة — وهذا لا يزال الخيار الصحيح لمخرجات النموذج أحادية الاتجاه. أما WebSockets فهي للميزات التي يتحدث فيها المتصفح للخلف: موضع مؤشر، ضغطة مفتاح، مؤشر كتابة.
هل لا نزال بحاجة لـ Pusher أو Ably أو خادم WebSocket مستقل؟
أحياناً، لكن ليس كخيار افتراضي بعد الآن. منصات realtime المُدارة مثل Pusher وAbly تتعامل مع أمرين صعبين حقاً بناؤهما بنفسك: التوزيع عبر آلاف الاتصالات المتزامنة، وبدائل الحضور أو الغرف — من متصل الآن، من في هذا المستند — كواجهة برمجية جاهزة. إذا كانت الميزة بهذا الحجم، أو تحتاج لدلالات حضور جاهزة دون بنائها، تبقى الخدمة المُدارة الخيار العملي.
ما تغيّر هو خط الأساس. سابقاً، أي WebSocket على Vercel كان يعني تشغيل خادم منفصل دائم التشغيل — عملية Node صغيرة في مكان آخر فقط لإبقاء الـ socket، تتواصل مع التطبيق المستضاف على Vercel عبر HTTP. الآن، حاجة realtime لميزة واحدة — مؤشر مباشر في أداة تعاونية صغيرة، قناة حالة لمهمة خلفية، عنصر محادثة ثنائي الاتجاه — يمكن أن تعيش بالكامل داخل نفس دالة Vercel التي تخدم باقي الـ API. هدف نشر واحد، نموذج مصادقة واحد، مجموعة واحدة من متغيرات البيئة.
ما حدود الاتصال وأنماط إعادة الاتصال التي يجب علينا التعامل معها بأنفسنا؟
WebSockets لا تحصل على إعادة اتصال تلقائية كما يفعل EventSource مع SSE — هذا المنطق مسؤوليتنا. بعض الأنماط التي نبنيها في كل عميل WebSocket الآن.
التراجع الأسي عند إعادة الاتصال: عند الإغلاق، أعد المحاولة بتأخير متزايد — ثانية واحدة، ثانيتان، أربع ثوانٍ، بحد أقصى حوالي 30 ثانية — بدلاً من قصف نقطة النهاية فوراً.
نبضات القلب (heartbeat): أرسل رسالة ping صغيرة على فترات وتوقع رد pong؛ إذا لم يصل شيء خلال مهلة محددة، عامل الاتصال كأنه ميت وأعد الاتصال بدلاً من انتظار نظام التشغيل ليلاحظ socket نصف مفتوح.
مهلات الخمول حقيقية: اتصال بلا حركة لفترة طويلة قد يُسقط بواسطة وسطاء (proxies) أو المنصة نفسها — نبضة القلب أعلاه تعمل أيضاً كإبقاء حي (keep-alive).
الحالة عند إعادة الاتصال، وليس فقط الـ socket: عندما يعيد العميل الاتصال، يحتاج لمزامنة أي حالة فاتته — آخر معرف رسالة، إصدار المستند الحالي. صمم البروتوكول بحيث يستطيع الخادم الإجابة عن 'لحّقني من X' بدلاً من افتراض أن العميل رأى كل رسالة.
لا شيء من هذا خاص بـ Vercel — إنه نفس الانضباط الذي يحتاجه أي عميل WebSocket — لكنه يهم أكثر هنا لأنه لا توجد خدمة مُدارة تخفيه عنك.
الأسئلة الشائعة
س: هل نحتاج نظام تشغيل Edge لاستخدام WebSockets على Vercel؟
ج: لا. دعم WebSocket يعمل على Fluid Compute مع نظام تشغيل Node.js القياسي — لا حاجة لـ runtime = 'edge'، وEdge Functions لم تعد الخيار الافتراضي الموصى به على Vercel بشكل عام.
س: هل يمكننا استخدام حزمة ws مباشرة بدلاً من experimental_upgradeWebSocket()؟
ج: experimental_upgradeWebSocket() هي نقطة الدخول التي تنفذ ترقية HTTP إلى WebSocket داخل دالة Vercel؛ بمجرد فتح الاتصال، كود التعامل مع الـ socket الموجود — بما فيه المنطق المبني حول ws — ينتقل دون تغيير يُذكر.
س: هل هذا نفس الشيء مثل Server-Sent Events لبث الذكاء الاصطناعي؟
ج: لا. SSE أحادي الاتجاه، من الخادم إلى العميل، ويبقى الخيار الصحيح لبث رموز الذكاء الاصطناعي. أما WebSockets فثنائية الاتجاه وتناسب الميزات التي يرسل فيها العميل أيضاً رسائل متكررة للخلف.
س: هل تنجو اتصالات WebSocket من عملية نشر جديدة؟
ج: يدعم Fluid Compute الإغلاق التدريجي وإلغاء الطلبات، ما يمنح الاتصالات الجارية فرصة للإغلاق بشكل نظيف أثناء النشر بدلاً من إيقافها قسراً — لكن كود العميل يجب أن يطبق منطق إعادة اتصال للنافذة القصيرة أثناء التبديل.
س: هل لا نزال بحاجة لقاعدة بيانات أو Redis لتوزيع متعدد النسخ؟
ج: إذا كان التطبيق يعمل على نسخة Fluid Compute واحدة مُعاد استخدامها، فالبث داخل الذاكرة كافٍ. عندما تمتد حركة المرور عبر عدة نسخ، لا تزال هناك حاجة لطبقة مشتركة — Redis pub/sub أو خدمة realtime مُدارة — لتوزيع الرسائل عبرها.
قراءات إضافية
هندسة الواجهات
الوضع الداكن دون وميض في Next.js 16: كيف نستخدم سكربت سمة حاجبًا وcolor-scheme وزر تبديل آمنًا مع الـ Hydration
وميض الوضع الداكن عند إعادة تحميل الصفحة مشكلة توقيت: الخادم لا يستطيع قراءة localStorage. نشرح سكربت السمة الحاجب الذي نستخدمه في Next.js 16، ولماذا لا نضع السمة في cookie حفاظًا على الرسم الثابت، وكيف نبني زر تبديل يمر بالـ hydration دون أي عدم تطابق.
هندسة الواجهات
صور Open Graph الديناميكية في Next.js 16: كيف نبني opengraph-image.tsx ونتحقق من إصدار Satori الذي تضمّنه next/og ونحافظ على صحة معاينات الروابط
واجهت بطاقات المشاركة المولّدة لدينا ثلاث مفاجآت: خط WOFF2 لم يستطع المحرّك قراءته، وملف README لـ Satori يصف محرّكًا أحدث من المضمّن في Next.js، وصفحة ظلّت تعرض صورة الـ layout. هذه ملاحظاتنا الميدانية حول opengraph-image.tsx في Next.js 16 وأولوية البيانات الوصفية وفحص og:image قبل مشاركة أي رابط.