<header class="flex items-center">

<NuxtImg src="logo.png" class="w-18" alt="Dywan Dev - Digital tools & templates" format="webp" quality="80" loading="lazy" />

<Item><nuxt-link href="/">Home</nuxt-link></Item>

<Item><nuxt-link href="/about">About</nuxt-link></Item>

<Item><nuxt-link href="/works"> Portfolio</nuxt-link></Item>

<Item><nuxt-link href="/contact_me"> Contact</nuxt-link></Item>

<a href="/contact-me" title="Get in touch with Dywan Dev"> Contact </a>

<themeswitch class="moon">Dark/Light</themeswitch>

<langswitch class="en">en/fa</langswitch>

</header>

<initilizecontent class="content">Hello World - Dywan Dev</initilizecontent>

<myname is="Dywan Dev" />

<jobtitle is="Digital tools · Templates · Case studies" />

<portfolios are="ready" number="8" />

<experience years="more than 6" />

<birthdate year="1998" month="july" day="11" />

<skills :list="['NUXT', 'Vue', 'React', 'Next', 'Javascript','Responsive Design', 'i18n', 'TypeScript]" />

<contactDetails :list="['contact@dywandev.com', 'linkedin.com/in/dywan-dev', 'github.com/dywan-dev']" />

Dywan Dev
المدونة
كيف بنيت قالب موقع مطعم بثلاث Variants من قاعدة كود واحدة
11د

كيف بنيت قالب موقع مطعم بثلاث Variants من قاعدة كود واحدة

مقدمة

بدأ الأمر مع عميل واحد.

صاحب مطعم كان يحتاج موقعًا. لا شيء معقد. ولا مكلف. فقط شيء يشبه نشاطه — دافئ، مرحِّب، وحقيقي. بنيته. كان سعيدًا. وانتقلت لغيره.

ثم تواصل معي مطعم آخر. أسلوب مختلف. جمهور مختلف. وهوية علامة تجارية مختلفة تمامًا. بدأت البناء من الصفر، وفي حوالي الساعة الثالثة توقفت وطرحت على نفسي سؤالًا غيّر طريقة تفكيري في القوالب:

لماذا أعيد بناء نفس البنية للمرة الثالثة؟

كل موقع مطعم يشارك نفس «الهيكل العظمي». Hero يجعل الطعام يبدو لا يُقاوَم. قائمة طعام سهلة التصفح. قصة تبني الثقة. قسم تواصل يحوّل الزائرين إلى حجوزات. البنية لا تتغير. الذي يتغير هو الشخصية فقط.

هذه الملاحظة أصبحت Savoura Dywan — قاعدة كود React واحدة تنتج ثلاث مواقع مطاعم مختلفة فعلاً. فاخر راقٍ. مطعم حيّ/محلي غير رسمي. بيتزا سريع وجريء. Build واحد. ثلاث هويات. بلا أي تنازل عن الجودة.

هذه هي القصة الكاملة لكيف بنيته ولماذا اتخذت كل قرار.

1. مشكلة قوالب المطاعم الموجودة

قبل أن أبني أي شيء، قضيت وقتًا في دراسة ما هو موجود بالفعل في السوق.

معظم قوالب المطاعم تقع في واحدة من فئتين.

الفئة الأولى جميلة لكنها جامدة. تصميم مذهل، محتوى «مُشفّر» داخل المكونات، ولا تخصيص حقيقي سوى تبديل الصور وتغيير النص مباشرة داخل ملفات المكونات. المطوّر يستطيع استخدامها. صاحب المطعم لا يستطيع. والمطوّر الذي يريد تسليمها لثلاثة عملاء مختلفين سيعيد البناء ثلاث مرات.

الفئة الثانية مرنة لكنها عامة. Page builders وأدوات سحب وإفلات وقوالب WordPress بخيارات لا تنتهي. مرونة قصوى، شخصية شبه معدومة. في النهاية كل المطاعم تبدو متشابهة.

ولا واحدة من الفئتين تحل المشكلة الحقيقية: صاحب المطعم يحتاج موقعًا «يشبه مطعمه» تحديدًا، يتم تسليمه بسرعة، وبسعر منطقي لعمل محلي.

تم تصميم Savoura Dywan لتكون في المسافة بين الفئتين. لها شخصية واضحة (opinionated) بما يكفي لتبدو مميزة. ومرنة بما يكفي لخدمة أنواع أعمال مختلفة. وبسيطة بما يكفي لأن التخصيص يعني تعديل ملف واحد، لا إعادة بناء المشروع.

2. تحديد الثلاثة Variants

الـ Variants الثلاث لم تُختر عشوائيًا. إنها تمثل ثلاثة جماهير مختلفة فعلاً، بتوقعات بصرية مختلفة ونفسية عميل مختلفة.

Luxury — Fine Dining

نسخة الـ Luxury تخدم المطاعم التي تكون التجربة فيها هي المنتج. مطعم تذوق. بيت مأكولات بحرية فاخر. وجهة gastronomic.

الزائر الذي يصل إلى هذا الموقع قرر بالفعل أنه سيدفع. لا يقارن الأسعار. هو يقيّم الهيبة والأجواء والحصرية. يجب أن ينقل الموقع هذه الثلاثة قبل قراءة أي كلمة.

اللغة البصرية: خلفيات داكنة، لمسات ذهبية، خطوط serif مع تباعد حروف واسع، حركات بطيئة وأنيقة، hero بحجم كامل الشاشة بصور سينمائية، ومساحات بيضاء واسعة تعكس تموضعًا Premium.

Casual — Neighbourhood Restaurant

نسخة الـ Casual تخدم المطاعم التي تكون فيها الدفء والمجتمع هما المنتج. trattoria عائلية. bistro حيّ. مكان brunch له جمهور محلي وفيّ.

الزائر هنا يريد أن يشعر بالراحة قبل أن يصل. يريد صور طعام «بابتسامة»، قوائم مقروءة، وشخصية إنسانية وليست corporate.

اللغة البصرية: ألوان ترابية دافئة، زوايا مستديرة، خطوط sans-serif ودودة، حركات مرنة بفيزياء spring، hero قريب بإضاءة طبيعية، وأقسام تشبه محادثة لا عرضًا رسميًا.

Pizzeria — Fast and Bold

نسخة الـ Pizzeria تخدم المطاعم التي تكون فيها السرعة والشهية والطاقة هي المنتج. بيتزا ليلي. عمل يعتمد على التوصيل. concept خدمة سريعة.

الزائر جائع. يريد أن يرى المنتج فورًا، يجد القائمة بسرعة، ويطلب أو يتصل بدون أي احتكاك.

اللغة البصرية: خلفية داكنة بتباين عالي، لمسات حمراء وصفراء قوية، خطوط condensed ثقيلة، حركات سريعة وحادة، hero تهيمن عليه صور المنتج، وCTA واضح فوق الـ fold.

ثلاث Variants. ثلاث نفسيات. ثلاث رحلات عميل مختلفة بالكامل — من قاعدة كود واحدة.

3. قرار المعمارية الذي جعل ذلك ممكنًا

الإغراء عند بناء شيء كهذا هو استخدام conditional rendering في كل مكان. if luxury هنا. switch للـ variant هناك. يبدأ الأمر نظيفًا ثم يصبح غير قابل للإدارة خلال أسابيع.

اتخذت قرارًا تأسيسيًا منع هذا: قاعدة الكود لا تعرف أي مطعم تخدمه. الإعدادات هي التي تعرف.

كل ما هو خاص بالـ variant موجود في ملفين. siteConfig.js يحتوي المحتوى وبيانات التواصل وميزات التفعيل والـ variant identifier. وthemes.js يحتوي «الشخصية البصرية» الكاملة لكل variant — الألوان، الخطوط، المسافات، سرعات الحركة، قيم border radius، وأنماط الظلال.

كل مكوّن يقرأ من هذين الملفين عبر Theme Context مركزي. لا يوجد منطق خاص بالـ variant داخل المكونات إلا عندما تختلف الـ layout فعلاً بين variants. وعندما تختلف، يقوم resolver بسيط بحلها بشكل نظيف.

// The only place variant logic lives in components
const HeroComponent = {
  luxury: LuxuryHero,
  casual: CasualHero,
  pizzeria: PizzeriaHero
}[variant];

return <HeroComponent />;

هذه المعمارية تنتج نتيجة مهمة جدًا لبيع القالب: المشتري يغيّر variant: 'luxury' إلى variant: 'casual' في ملف واحد فتتحول كامل المنصة. الألوان، الخطوط، الحركات، المسافات، الشخصية — كل شيء يتغير فورًا.

هذا ليس مجرد هندسة جيدة. هذه ميزة تغلق الصفقات.

4. بناء نظام القائمة

القائمة هي أهم قسم في أي موقع مطعم. هنا يتخذ الزائر القرار: يأتي أو يذهب. الخطأ هنا يخسر البيع قبل أن يحصل المطعم على فرصة.

نظام القائمة في Savoura Dywan مبني بالكامل على الإعدادات. كل محتوى القائمة موجود في siteConfig.js:

menu: {
  categories: [
    {
      id: 'starters',
      name: 'Starters',
      nameAr: 'المقبلات',
      nameFr: 'Entrées',
      items: [
        {
          id: 1,
          name: 'Burrata & Heirloom Tomato',
          nameAr: 'بوراتا وطماطم',
          nameFr: 'Burrata et Tomate',
          description: 'Fresh burrata with seasonal heirloom tomatoes, aged balsamic, and micro basil',
          price: 14,
          currency: 'USD',
          image: '/menu/burrata.jpg',
          tags: ['vegetarian', 'gluten-free'],
          featured: true,
          available: true
        }
      ]
    }
  ],
  displayStyle: 'grid', // 'grid' | 'list' | 'magazine'
  showImages: true,
  showCalories: false,
  enableFilters: true
}

مكوّن القائمة يقرأ هذه الإعدادات ويعرض بناءً عليها. تغيير displayStyle من grid إلى list يغير تخطيط القائمة بالكامل. تعطيل showImages يزيل الصور للمطاعم التي تفضّل قائمة نصية. تفعيل الفلاتر يضيف شريط فلترة تلقائيًا.

كل عنصر في القائمة يدعم ثلاث لغات بشكل طبيعي. عند التحويل إلى العربية، تظهر القائمة بالعربية مع تخطيط RTL. الزائر الفرنسي يرى الفرنسية. والإنجليزية هي fallback. بدون أي عمل مطوّر بعد الإعداد الأولي.

5. الدعم متعدد اللغات وRTL — نقطة التفوق

هذه الميزة وحدها تميّز Savoura Dywan عن أغلب قوالب المطاعم المنافسة.

سوق MENA — المغرب، الجزائر، تونس، مصر، ودول الخليج — فيه ملايين المطاعم بدون حضور ويب احترافي. أغلب القوالب لا تدعم العربية. والعديد ممن يدّعي دعمها يطبقها بشكل سيئ — تخطيطات مكسورة، اتجاهات نص مختلطة، وخطوط لا تعرض العربية بشكل صحيح.

Savoura Dywan تتعامل مع العربية كلغة أساسية، لا كإضافة متأخرة.

التنفيذ يستخدم i18next للترجمة مع hook اتجاه يقوم بتحديث HTML dir وخصائص CSS المنطقية واتجاهات الحركة معًا عند تغيير اللغة.

export const useDirection = () => {
  const { i18n } = useTranslation();

  useEffect(() => {
    const isRTL = ['ar'].includes(i18n.language);
    document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
    document.documentElement.lang = i18n.language;
    document.documentElement.setAttribute('data-direction', isRTL ? 'rtl' : 'ltr');
  }, [i18n.language]);
};

خاصية data-direction تسمح للـ CSS وFramer Motion بالاستجابة لتغير الاتجاه. حركات الـ slide-in تنعكس بشكل صحيح. الصور ذات الدلالة الاتجاهية تُعكس في RTL عند الحاجة. وكل التخطيط ينقلب بدون أخطاء.

لمطعم مغربي يخدم الزبائن بالفرنسية والعربية، هذا ليس «ميزة إضافية». هذا مطلب تجاري. Savoura Dywan تقدمه جاهزًا.

6. PWA — الميزة التي يراها العملاء كالسحر

كل عميل عرضت عليه ميزة PWA كانت ردة فعله نفسها. يفتح هاتفه، يدخل للـ demo، يرى install prompt، يضغط… ثم يشاهد الموقع يظهر على الشاشة الرئيسية كأنه تطبيق Native. تلاحظ اتساع العينين قليلًا.

تلك اللحظة قيمتها في البيع أكثر من أي شرح تقني.

التنفيذ يستخدم Vite PWA Plugin مع إعداد Workbox مضبوط بعناية. القرارات الأساسية:

دعم العمل دون اتصال للقائمة. إذا كان الزبون يتصفح القائمة في منطقة ضعيفة الشبكة — وهذا يحدث كثيرًا في مدن مغربية — تستمر القائمة في العمل من cache. المطعم لا يخسر زبونًا بسبب spinner.

إشعارات التحديث. عندما يحدّث صاحب المطعم قائمته أو يضيف عرضًا، يرى الزائرون الذين ثبتوا الـ PWA إشعارًا يطلب تحديث الصفحة. دائمًا يرون معلومات حديثة.

توقيت ظهور install prompt. تظهر بعد أن يقضي الزائر 30 ثانية في الموقع — وقت كافٍ لإظهار اهتمام حقيقي بدون قطع الانطباع الأول.

export const usePWAInstall = () => {
  const [installPrompt, setInstallPrompt] = useState(null);
  const [showPrompt, setShowPrompt] = useState(false);

  useEffect(() => {
    const handler = (e) => {
      e.preventDefault();
      setInstallPrompt(e);

      // Wait 30 seconds before showing install UI
      setTimeout(() => setShowPrompt(true), 30000);
    };

    window.addEventListener('beforeinstallprompt', handler);
    return () => window.removeEventListener('beforeinstallprompt', handler);
  }, []);

  const install = async () => {
    if (!installPrompt) return;
    await installPrompt.prompt();
    setShowPrompt(false);
    setInstallPrompt(null);
  };

  return { showPrompt, install };
};

7. تدفق الحجز وواتساب

معظم مواقع المطاعم في المغرب ومنطقة MENA لا تستخدم أنظمة حجز أونلاين. قناة التواصل الأساسية هي WhatsApp. أي قالب يتجاهل ذلك غير مبني لهذا السوق.

Savoura Dywan تتضمن زر WhatsApp عائم يملأ رسالة تلقائيًا باسم المطعم وطلب حجز:

export const WhatsAppButton = () => {
  const { config } = useTheme();

  if (!config.features.enableWhatsApp) return null;

  const message = encodeURIComponent(
    `Hello, I'd like to make a reservation at ${config.branding.name}.`
  );

  const url = `https://wa.me/${config.contact.whatsapp.replace(/\\D/g, '')}?text=${message}`;

  return (
    <motion.a
      href={url}
      target="_blank"
      rel="noopener noreferrer"
      className="whatsapp-float"
      whileHover={{ scale: 1.1 }}
      whileTap={{ scale: 0.95 }}
      aria-label="Contact us on WhatsApp"
    >
      <WhatsAppIcon />
    </motion.a>
  );
};

فعِّله عبر enableWhatsApp: true في الإعدادات. ضع الرقم مرة واحدة. كل طلب حجز يصل مباشرة لهاتف صاحب المطعم. بلا رسوم منصات. بلا تبعيات خارجية. فقط القناة التي تعمل فعلاً في هذا السوق.

8. الأداء — لأن موقع مطعم بطيء يخسر زبائن

الزائر الذي ينتظر أكثر من 3 ثوانٍ لتحميل موقع مطعم سيغادر. لن ينتظر. سيبحث عن مطعم آخر.

الأداء لم يكن «تحسينًا في النهاية» لدى Savoura Dywan. كان قيدًا تصميميًا منذ البداية.

القرارات الأساسية:

تحميل كسول لكل الصور. صور الـ hero تُحمّل فورًا. باقي الصور تُحمّل عند التمرير. التحميل الأولي يحمل فقط ما يظهر فوق الـ fold.

تحسين الخطوط. Google Fonts تُحمّل مع display=swap وpreconnect. النص يظهر فورًا بخط fallback ثم يتبدّل — بدون نص مخفي.

تقسيم الكود حسب الصفحات. React Router يقوم بتحميل كل صفحة بشكل lazy. من يدخل الصفحة الرئيسية لا يحمل كود صفحة القائمة إلا عند فتحها.

تحسين build عبر Vite. تقسيم chunks يدويًا للفصل بين vendors وكود التطبيق. Framer Motion وi18next وReact في chunks مستقلة تُخزّن كل واحدة على حدة. تحديث محتوى القائمة لا يُبطل cache الخاص بـ Framer Motion.

النتيجة عبر الثلاثة variants هي Lighthouse Performance غالبًا فوق 90 على الموبايل — الجهاز الذي يستخدمه أغلب الزبائن لاتخاذ قرار أين يأكلون.

9. مشكلة 90% وكيف حللتها

سأكون صريحًا. Savoura Dywan وصلت 90% من الاكتمال ثلاث مرات قبل أن تنتهي فعلاً.

أول 90% كانت التخطيط الأساسي وتصاميم الـ variants الثلاث. ثم أضفت الدعم متعدد اللغات — 90% مرة أخرى. ثم PWA والحركات ومعمارية الإعدادات — 90% مرة أخرى.

في كل مرة كنت أشعر أنها «انتهت»، كان شيء جديد يبدو ضروريًا. ميزة جديدة. تحسين. طريقة أفضل لتنظيم ملف الإعدادات. وكل إضافة تعيد شعور «الانتهاء» إلى الصفر بدون تقدم حقيقي في قيمة المنتج.

السؤال الذي كسر الحلقة كان بسيطًا: هل العميل الذي سيدفع سيلاحظ فرقًا بين ما لدي الآن وبين ما سأضيفه الآن؟

عندما كانت الإجابة لا — توقفت وشحنت.

هذا أهم درس تعلمته من بناء Savoura Dywan. الكمالية والجودة ليستا الشيء نفسه. منتج مشحون يحل المشكلة قيمته أكبر من منتج غير مشحون يحلها أفضل بقليل.

هناك سبب لوجود النسخة الثانية.

10. ماذا تقدم Savoura Dywan جاهزًا

لصاحب مطعم أو مطوّر يسلّم مشروع مطعم، Savoura Dywan تقدم:

ثلاث Variants كاملة بشخصيات مختلفة تغطي أغلب أنواع المطاعم. دعم كامل للغات الإنجليزية والفرنسية والعربية مع RTL صحيح — ميزة غير متوفرة في معظم القوالب المنافسة. PWA مع وصول للقائمة دون اتصال وتثبيت على الشاشة الرئيسية. معمارية config-driven حيث كل المحتوى والهوية وتفعيل الميزات في ملف واحد. حركات Framer Motion مضبوطة حسب شخصية كل variant. تكامل WhatsApp مبني لواقع سوق MENA. إعداد SEO يشمل meta tags وOpen Graph وTwitter cards وبيانات منظمة للأعمال. تصميم mobile-first متجاوب ومُحسّن للأجهزة التي يستخدمها الزبائن فعلاً.

المطوّر الذي يستخدم Savoura Dywan لتسليم مشروع مطعم يوفر بين يومين وأربعة أيام مقارنة بالبناء من الصفر. وبأي سعر فريلانس منطقي، هذا التوفير يساوي سعر القالب عدة مرات من أول مشروع.

الخاتمة

بدأت Savoura Dywan بسؤال: لماذا أعيد بناء نفس الهيكل مرارًا عندما الشيء الوحيد الذي يتغير هو الشخصية؟

الإجابة أصبحت منتجًا. منتجًا ليس فقط للمطوّرين الذين يريدون اختصارًا، بل أيضًا لأصحاب المطاعم الذين يحتاجون حضورًا احترافيًا بدون تكاليف تطوير مخصص، ولواقع السوق المغربي وMENA حيث الدعم متعدد اللغات وWhatsApp ميزات غير اختيارية.

كل قرار معماري — نظام الإعدادات، theme context، resolver pattern، وتنفيذ RTL — تم اتخاذه لتحقيق نتيجة واحدة: قالب يبدو مخصصًا بالكامل لكنه يحتاج أقل قدر من الإعداد.

وهذا ما يفرق بين «قالب» و«محرك قوالب».

Savoura Dywan متاحة الآن على Dywan Dev. ثلاث variants كاملة، دعم RTL متعدد اللغات، PWA، وإعداد كامل من ملف واحد. مبنية لمطوّرين يسلّمون بسرعة ولأصحاب مطاعم يريدون نتائج احترافية بدون تكاليف مخصصة. احصل على Savoura Dywan.

تابع القراءة

عرض الكلعرض الكل
واتساب