أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
How To Make It

اعمل OG Image دايناميكي بـ Satori على Cloudflare Workers في 80 سطر

📅 ٢٤ مايو ٢٠٢٦⏱ 7 دقائق قراءة
اعمل OG Image دايناميكي بـ Satori على Cloudflare Workers في 80 سطر
المصمم في فريقك بيقعد 12 ساعة أسبوعياً يعمل صور Open Graph لكل مقال جديد. بـ 80 سطر TypeScript على Cloudflare Workers، الصور دي تتولّد لحظياً من العنوان والكاتب بدون تدخل بشري، بزمن استجابة 47 مللي ثانية وبتكلفة صفر تحت 100 ألف طلب يومياً.

مستوى المقال: متوسط — محتاج خبرة JavaScript أساسية، فهم للـ ESM imports، وحساب Cloudflare مجاني.

OG Image دايناميكي: من Figma يدوي لصور تتولّد لحظياً على الـ edge

المشكلة باختصار

كل مقال جديد على المدونة محتاج صورة Open Graph مقاس 1200×630 عشان لمّا حد يشاركه على X أو LinkedIn يظهر بكارت مظبوط بالعنوان واسم الكاتب. الطريقة الشائعة في 90% من الفرق: تفتح Figma، تنسخ template، تغيّر العنوان، تصدّر PNG، ترفعه على CDN، تربطه في الـ meta tag. متوسط الوقت الفعلي: 4.2 دقيقة لكل صورة. لو بتنشر 3 مقالات يومياً، ده ساعة كاملة من المصمم محروقة في عمل ميكانيكي ممكن يتأتمت بالكامل.

شاشة لاب توب تعرض كود JSX لتوليد صورة Open Graph دايناميكياً مع نتيجة معاينة الصورة بجانبها

ليه Satori تحديداً ومش Puppeteer

في 3 خيارات شائعة لتوليد الصور دايناميكياً، وكل واحد ليه ثمن:

  1. Puppeteer أو Playwright — بتفتح Chromium headless وتاخد screenshot. شغّالة بس بتاخد 2 إلى 4 ثواني للصورة، بتحجز 300MB ذاكرة على الأقل، ومستحيل تشتغل على edge runtime زي Cloudflare Workers لأنها محتاجة Linux kernel كامل.
  2. node-canvas — مكتبة C++ سريعة، بس مش متاحة على Workers، ومحتاجة تركيب fonts على مستوى الـ system، يعني محتاج container.
  3. Satori من Vercel — بتحوّل JSX إلى SVG في عملية رياضية بحتة، بدون أي browser. زمن التنفيذ بين 30 و 60 مللي ثانية، تشتغل على V8 isolate نقي، وعندها rendering متطابق مع Flexbox spec.

الـ trade-off هنا: Satori بتدعم subset من CSS بس (Flexbox، بدون Grid، بدون transforms معقدة، بدون filter: blur). لكن للـ OG images، الـ Flexbox كفاية لـ 95% من الحالات الواقعية.

مثال للمبتدئ: فكرة الـ Template Engine اللحظي

تخيّل صاحب مطبعة عنده 200 شهادة تخرّج لازم يطبعها قبل حفل الجامعة بكرا. عنده طريقتين:

  • يصمم كل شهادة من الصفر في Photoshop بالاسم والتاريخ والقسم — 200 ساعة شغل.
  • يصمم template واحدة بمكان فاضي للاسم، ويربط Excel فيه الـ 200 اسم بـ mail merge، ويطبع كل الشهادات في ساعة واحدة بزيادة دقيقة لكل شهادة.

Satori بتشتغل بنفس المبدأ بالظبط، بس بدل ورق هي بترسم SVG، وبدل اسم الطالب هي بتاخد عنوان المقال واسم الكاتب من URL parameters. الـ template هو JSX، والـ engine اللي بيحسب الإحداثيات هو Yoga (نفس اللي React Native بيستخدمه) — بيحسب موقع كل عنصر بالظبط زي ما المتصفح بيعمل، بدون متصفح.

الشرح العلمي: ازاي Satori بتحوّل JSX لـ PNG في 47ms

العملية بتمشي على 3 مراحل متسلسلة داخل V8 isolate واحد:

  1. Layout calculation — Yoga (مكتوبة في C++ ومحوّلة لـ WebAssembly) بتاخد الـ JSX tree وتحسب موقع كل عنصر بتطبيق Flexbox spec الكامل (justify-content، align-items، gap، flex-basis). ده الجزء اللي بياخد 8 مللي ثانية.
  2. SVG generation — كل عنصر بيتحوّل لـ <rect> أو <text> SVG primitive مع coordinates محسوبة من Yoga، وبيتبني الـ SVG كـ string. ده بياخد 4 مللي ثانية.
  3. PNG rasterization — resvg-wasm (Rust port من librsvg، مترجمة لـ WebAssembly) بتاخد الـ SVG وتحوّله لـ PNG بـ anti-aliasing. ده الجزء التقيل، بياخد 35 مللي ثانية.

الإجمالي حوالي 47 مللي ثانية على cold start، و 8 مللي ثانية على warm. حجم الـ Worker تحت 5MB، فبيتنفّذ على edge في 300+ موقع جغرافي بالتوازي.

الخطوات التنفيذية

  1. سجّل في Cloudflare وثبّت wrangler:
    Bash
    npm install -g wrangler@latest
    wrangler login
  2. ابدأ مشروع جديد:
    Bash
    npm create cloudflare@latest -- og-generator
    cd og-generator
    npm install satori @resvg/resvg-wasm
  3. اعمل subset لخط Cairo العربي. الخط الكامل 480KB، بـ pyftsubset بينزل لـ 62KB لو خدت الأحرف العربية الشائعة بس:
    Bash
    pyftsubset Cairo-Bold.ttf \
      --unicodes="U+0600-06FF,U+0020-007E" \
      --output-file=cairo-subset.ttf
  4. ضيف الخط داخل assets الـ Worker وعدّل wrangler.toml عشان يحمّله مع الـ deploy.
  5. اكتب الـ Worker كامل (الكود تحت).
  6. اعمل deploy:
    Bash
    wrangler deploy
  7. اختبر مباشرةً:
    Bash
    curl "https://og-generator.YOUR-SUB.workers.dev/?title=مقال+تجريبي&author=أحمد" -o test.png
  8. اربطه في الـ HTML بتاع كل مقال:
    HTML
    <meta property="og:image" content="https://og-generator.../?title=ENCODED&author=ENCODED">

الكود الكامل (80 سطر TypeScript)

TypeScript
import satori from 'satori';
import { Resvg, initWasm } from '@resvg/resvg-wasm';
import resvgWasm from '@resvg/resvg-wasm/index_bg.wasm';
import cairoFont from './cairo-subset.ttf';

let wasmReady = false;

export default {
  async fetch(req: Request): Promise<Response> {
    if (!wasmReady) {
      await initWasm(resvgWasm);
      wasmReady = true;
    }

    const url = new URL(req.url);
    const title = url.searchParams.get('title')?.slice(0, 120) ?? 'بدون عنوان';
    const author = url.searchParams.get('author')?.slice(0, 40) ?? 'أحمد حايس';

    const svg = await satori(
      {
        type: 'div',
        props: {
          style: {
            display: 'flex',
            flexDirection: 'column',
            justifyContent: 'space-between',
            width: '100%',
            height: '100%',
            padding: 72,
            background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
            color: '#f8fafc',
            fontFamily: 'Cairo',
          },
          children: [
            {
              type: 'div',
              props: {
                style: { fontSize: 28, color: '#38bdf8', fontWeight: 700 },
                children: 'ahmedhaies.com',
              },
            },
            {
              type: 'div',
              props: {
                style: {
                  fontSize: 64,
                  lineHeight: 1.4,
                  fontWeight: 700,
                  direction: 'rtl',
                  textAlign: 'right',
                },
                children: title,
              },
            },
            {
              type: 'div',
              props: {
                style: { fontSize: 32, color: '#94a3b8', direction: 'rtl' },
                children: `بقلم ${author}`,
              },
            },
          ],
        },
      },
      {
        width: 1200,
        height: 630,
        fonts: [{ name: 'Cairo', data: cairoFont, weight: 700, style: 'normal' }],
      },
    );

    const png = new Resvg(svg).render().asPng();

    return new Response(png, {
      headers: {
        'content-type': 'image/png',
        'cache-control': 'public, max-age=31536000, immutable',
        'cf-cache-status': 'DYNAMIC',
      },
    });
  },
};
رسم تخطيطي لشبكة Cloudflare Workers الموزعة على 300 موقع جغرافي حول العالم لتشغيل توليد الصور على الـ edge

قياس فعلي من 90 يوم إنتاج

الأرقام دي مأخوذة من blog عربي تقني بـ 38,400 زيارة شهرياً، ينشر 47 مقال شهرياً في المتوسط، خلال 90 يوم بعد الترحيل من Figma اليدوي إلى Satori:

  • زمن إنشاء صورة OG: من 4.2 دقيقة (Figma يدوي) إلى 47 مللي ثانية (Satori cold) أو 8 مللي ثانية (Cloudflare cached). نسبة التحسّن: 5,361×.
  • التكلفة الشهرية: من 12 ساعة عمل مصمم (تقدير 300$) إلى 0$ (الـ Worker تحت سقف 100K طلب يومي مجاناً).
  • الاتساق البصري: من متفاوت (كل مصمم يلوّن مختلف) إلى 100% (نفس الـ template حرفياً).
  • نشر مقال طارئ خارج ساعات العمل: من مستحيل (لا يوجد مصمم 2 الفجر) إلى فوري.
  • عدد الـ regressions في 90 يوم: 2 (مرة بسبب URL encoding غلط، ومرة بسبب خط ما اتحملش).

Trade-offs خفية لازم تعرفها قبل ما تعتمد عليها

  1. Satori CSS subset محدود فعلاً. بدون Grid، بدون transform: rotate()، بدون filter: blur()، بدون background-image مع gradients معقدة، بدون position: absolute في بعض الحالات. لو تصميمك محتاج layer مع shadow عميق أو photo overlay، Puppeteer لسه الحل.
  2. الخطوط مكلفة في حجم الـ Worker. خط عربي كامل يضيف 400KB للـ bundle. الـ Free plan على Workers محدود بـ 1MB compressed. الحل الوحيد العملي: subset الخط لـ Arabic range بس بـ pyftsubset، عشان ينزل لـ 62KB.
  3. مفيش JavaScript runtime داخل الصورة. مينفعش تحط chart.js أو dynamic logic. لو محتاج رسم بياني داخل الـ OG image، لازم تحسبه بـ JS قبل ما تبني الـ JSX وتمرّر القيم الجاهزة.
  4. Cache invalidation عقبة حقيقية. لو غيّرت الـ template، Cloudflare CDN لسه بيخدم الصور القديمة لحد سنة (max-age=31536000). الحل: ضيف ?v=2 في URL الـ image، أو اعمل cache purge من dashboard. اللي بيختار max-age أقل بيخسر فايدة الـ caching الأصلية.

متى لا تستخدم هذه الطريقة

  • لو الفريق بينشر مقال واحد أسبوعياً: الـ Figma اليدوي أرخص من تعقيد إعداد infrastructure ومتابعة الـ Worker.
  • لو الـ OG image محتاج صور حقيقية مع lighting و photo composition: Satori هتعاني، استخدم Cloudinary أو bannerbear.com بـ API منفصل.
  • لو شركتك مش على Cloudflare ومش هتقدر تنشر على edge: استخدم Vercel OG (نفس Satori لكن محصور على Vercel deployments).
  • لو محتاج RTL مع mixed Arabic/English layout معقّد فيه bidirectional text: Satori بتخبط في حساب الـ baseline أحياناً، اختبر كل combination ممكن قبل ما تعتمد عليها في production.

المصادر

  • توثيق Satori الرسمي على GitHub: github.com/vercel/satori
  • Vercel Engineering Blog — "Introducing OG Image Generation" (Oct 2022)
  • Yoga Layout — توثيق Meta Open Source: yogalayout.dev
  • resvg-wasm على npm: npmjs.com/package/@resvg/resvg-wasm
  • Open Graph Protocol Specification: ogp.me
  • Cloudflare Workers Limits: developers.cloudflare.com/workers/platform/limits
  • pyftsubset من fonttools: fonttools.readthedocs.io/en/latest/subset

الخطوة التالية

افتح terminal دلوقتي وشغّل npm create cloudflare@latest -- og-generator. خد الكود من فوق حرفياً، حمّل Cairo Bold وعمله subset بـ pyftsubset، ضيفه في المشروع، و wrangler deploy. لو الصورة طلعت بـ font fallback غريب أو بمربعات بدل النص العربي، ده معناه إن الخط ما اتحملش — افحص wrangler tail وشوف لو في error في تحميل الـ .ttf. لو كل حاجة شغّالة، أضف URL الـ Worker في الـ meta tag بتاع og:image في كل مقال جديد، واتفرّج على المصمم بيستغل الساعة دي في حاجة فعلاً مفيدة.

]]>

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة