بعض التفاصيل محجوبة. إن كان لديك مفتاح وصول، فبإمكانك مطالعة النسخة الكاملة.
شركة لتأجير معدّات الطاقة الجديدة ومعدّات البناء
إعادة بناء كاملة لموقع ونظام إدارة محتوى شركة لتأجير معدّات البناء والطاقة المتجدّدة
أعدتُ بناء موقع Wix مقيَّد بالقوالب ليصير نظام إدارة محتوى مكتوبًا من الصفر وواجهةً مُصيَّرة مسبقًا. تولّيتُ العمل وحدي، من تصميم قاعدة البيانات إلى النشر.

نظرة عامة
العميل شركة يابانية تعمل في تأجير وبيع معدّات الطاقة الشمسية وبطاريات التخزين وأجهزة السلامة. كان الموقع القديم مبنيًّا على Wix، فكانت صفحات تفاصيل المنتجات محكومةً بقيود القالب، ولم يكن ممكنًا تركيبها بحرّية بما يوافق طبيعة العمل.
في الموقع الجديد لم أستعمل أيّ نظام إدارة محتوى جاهز، بل بنيتُ لوحة الإدارة والواجهة من الصفر بـ Nuxt وAstro. والهدف أن أسلّم فريق التشغيل القدرة على تركيب صفحات تفاصيل المنتجات، مع الإبقاء على سرعة الموقع الساكن في جانب الزائر. وقد تولّيتُ كل شيء وحدي، من تصميم قاعدة البيانات إلى إعدادات النشر.
البنية
يتشارك Worker اثنان على Cloudflare قاعدة D1 نفسها: جانب نظام إدارة المحتوى يكتب، وجانب الواجهة للقراءة فقط. وتُخزَّن الوسائط في R2، ويرفعها المتصفّح مباشرةً عبر روابط موقَّعة يُصدرها نظام إدارة المحتوى. ولأنها لا تمرّ عبر الـ Worker، فهي تتفادى حدّي حجم الطلب وزمن المعالج.
كانت حزمة AWS SDK الرسمية تسقط على بيئة تشغيل Cloudflare عند تحميل الوحدة نفسها، ورسالة الخطأ لم تكن تشير إلى السبب الحقيقي البتّة. حللتُ ذلك باستبدالها بمكتبة توقيع مبنيّة على fetch وحده، وتركتُ خلاصة التقصّي في تعليق داخل الكود، حتى لا يقع من يأتي بعدي في الحفرة نفسها.
تُصيَّر صفحات الواجهة الـ19 مسبقًا وقت البناء، وتُكتب مكوّنات أقسام تفاصيل المنتج بـ Vue تصييرًا من الخادم بصفر JavaScript. ولم أكتب بـ Solid إلا جزيرة التصفية وحدها لأنها تحتاج تفاعلًا، وفصلتُ حدّ الترجمة بين إطارَي العمل باتفاقٍ على ترتيب المجلّدات.
المواضع الصعبة تقنيًّا
أداة تركيب أقسام صفحة تفاصيل المنتج
كان على صفحة تفاصيل المنتج أن تعيد إنتاج دليل بصري محدَّد كما هو: 15 نوعًا من الأقسام و41 صيغة. والصعوبة لم تكن في العدد، بل في أن ثلاثة مواضع — نموذج المحرّر، ومعاينة المحرّر، والتصيير في الإنتاج — كان كلٌّ منها ينحرف عن الآخرَين على هواه. فكلّما أُضيف حقل إلى صيغة واحدة، وجب إصلاح المواضع الثلاثة جميعًا.
كان الحلّ أن أفصل الأقسام في حزمة workspace مستقلّة، وأن أجعل مخطّط zod واحدًا هو العقد الوحيد. فتقرأ عمليةُ التحقّق في لوحة الإدارة والتصييرُ في الواجهة النوعَ نفسه. ولأن مجموعة مكوّنات Vue ذاتها تتولّى المعاينة اللحظية في نظام إدارة المحتوى والتصيير بصفر JavaScript في الواجهة، فلا مجال أصلًا لأن يزدوج التنفيذ.
الصيغ التي تختلف أشكال بياناتها لا أمثّلها بجعل الحقول كلّها اختيارية، بل باتحادٍ قابل للتمييز. وبهذا يُردّ النقص في الإدخال عند التحقّق وقت الحفظ، فلا يظهر خطأً في وقت التشغيل بعد أن يكون الزائر قد فتح الصفحة.

التصيير المسبق والتغييرات غير المنشورة
للتصيير المسبق لكلّ الصفحات أثرٌ جانبيّ لا مفرّ منه: لو عدّل المحرِّر المحتوى في لوحة الإدارة، فلن يتغيّر في الموقع شيء حتى تُنفَّذ عملية البناء التالية. وفي مكانٍ يديره شخص واحد، لا أحد يقوى على أن يتذكّر الذهاب إلى لوحة التحكّم ليطلق البناء بيده.
كان الحلّ أن أعامل «غير المنشور» بوصفه حالةً من الدرجة الأولى في النظام. فمتى نجحت كتابةٌ تؤثّر في ما يراه الزائر، تُرفع رايةٌ في جدول حالة من سطر واحد. واستعملتُ COALESCE لأسجّل وقت أوّل تغيير لا آخره، حتى أستطيع أن أعرض في أعلى لوحة الإدارة مقدار ما تراكم.
زرّ النشر يمرّر Deploy Hook عبر الخادم، فلا يصل الرابط الحامل للسرّ إلى المتصفّح إطلاقًا. ولم أختر أن يُطلَق البناء تلقائيًّا مع كلّ حفظ، لأن تعديل عشرة حقول تباعًا يعني تشغيل البناء عشر مرّات؛ فجعلتُه نشرًا مجمّعًا بعد أن يراجعه إنسان.

مسح المراجع في الجداول كلّها قبل حذف صورة
يُشار إلى جدول الوسائط من أربعة مواضع، غير أن المفاتيح الأجنبية الحقيقية بعضُها فقط. أمّا المعرّفات الخام داخل أعمدة JSON، ومفاتيح الكائنات التي تحملها الأقسام، فلا تحميها قيود قاعدة البيانات. وعند حذف صورة واحدة لا تقوى قاعدة البيانات على منع إلا نوعٍ واحد؛ وما يفوتها يجعل صورةً منشورة تُعيد 404، ودون رجعة.
- أعمدة المفاتيح الأجنبية تُطابَق بالمعرّف كما هو
- مصفوفات الأرقام تُفكَّك بـ json_each وتُطابَق واحدةً واحدة
- مصفوفات الكائنات يُستخرج منها معرّف الوسيط بـ json_extract
- أعمدة الإعدادات غير محدّدة الشكل يُتحقَّق أوّلًا من سلامتها بوصفها JSON ثم تُفكَّك
- متن الأقسام يحتفظ بمفتاح الكائن، فيُنتقل فيه إلى مطابقة جزئية تشمل صيغته المهرَّبة
- إن وُجد ما يطابق، يُعاد 409 مع تعداد مواضع الاستعمال، ليعرف فريق التشغيل أيُّ محتوى يستعمل تلك الصورة
المطابقة الجزئية ليست دقيقة، لكنها مفاضلة مقصودة: النتيجة الإيجابية الخاطئة تكلّف رفضًا واحدًا يمكن التراجع عنه، أمّا ما يفوت فيعني ملفًا محذوفًا لا يعود. وكان التنفيذ السابق يبحث بدقّة عن اسم سمة مقرَّرة سلفًا، غير أن تلك السمة لم تُكتب قطّ في أيّ موضع من الكود، فتعطّل المسح مرّةً، في صمت.
لم أسلك طريق تطبيع هذه المراجع في جدول وسيط. هو الصواب في الكتب. غير أن التطبيع يجرّ معه مزامنةً مع كلّ حفظ للمحتوى. وحذف الوسائط عملية قليلة التكرار، فمن الأجدى أن أشتري بمسحٍ شامل واحد عبءَ المزامنة الدائم.
الموقع المنشور