لو بتنشر 3 مقالات أسبوعيًا وبتفتح Figma كل مرة علشان تصمّم صورة preview للسوشيال، ده بياكلك قرابة ساعة كل أسبوع من غير عائد حقيقي. next/og (والاسم القديم @vercel/og) بيخلّي الصورة دي تتولّد أوتوماتيكيًا من JSX وقت ما أي شخص يشارك الرابط. صفر فتح لمحرر تصميم، صفر رفع صور يدوي، وكل مقال جديد بيجيله صورة مخصّصة فيها العنوان والتصنيف وهوية الموقع تلقائيًا.
المشكلة باختصار
لمّا حد يشارك مقالك على X أو LinkedIn أو WhatsApp، الشبكة بتقرأ ميتا تاج اسمه og:image وبتعرض الصورة دي في معاينة الرابط. لو الصورة مش موجودة أو صورة عامة متكرّرة، الناس بتعدّي اللينك بدون ما تضغط. تحليل BuzzSumo لقي إن المنشورات اللي فيها صورة بتاخد تقريبًا 2.3× تفاعل مقارنة بالمنشورات النصّية. وفي case study منشور على Aira، موقع في القطاع المالي زاد الترافيك الاجتماعي بنسبة 78% بعد ضبط Open Graph tags بس.
المشكلة العملية: الواحد بيعمل صورة واحدة عامة للموقع كله، فكل لينك بيطلع بنفس الكليشيه. والبديل تصميم صورة لكل مقال يدويًا — اللي بيتحوّل لعبء، وبيتنسى بعد أول شهر.
ليه نعمل كل ده أصلًا؟ — فكرة بمثال بسيط
تخيّل إنك صاحب مكتبة بتبيع 200 كتاب. لو أي زبون يسألك عن كتاب، إنت مش هتوريه نفس صورة المكتبة من بعيد كل مرة — هتوريه غلاف الكتاب بالظبط. صور Open Graph نفس الفكرة: كل رابط ليه "غلاف" خاص بيه، والغلاف ده هو اللي بيتعرض على السوشيال. الفرق الوحيد إنك مش مضطر ترسم الغلاف بإيدك، ممكن كود صغير يولّده من بيانات المقال (العنوان، الكاتب، القسم، التاريخ).
تقنيًا: next/og هي API مدمجة في Next.js 14+ بتشتغل على Edge Runtime. بتاخد JSX عادي، بتمرّره على مكتبة Satori اللي بتحوّل الـ HTML/CSS لـ SVG، وبعدين resvg بيحوّل الـ SVG لـ PNG داخل الـ response. متوسط زمن التوليد: 120-180 مللي ثانية للصورة الواحدة على Vercel Edge.
الكود الكامل — نسخ ولصق
حطّ الملف ده في مشروع Next.js يستخدم App Router، في المسار app/og/route.tsx:
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') ?? 'مقال جديد على أحمد حايس';
const category = searchParams.get('category') ?? 'تقني';
return new ImageResponse(
(
<div style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
padding: '60px',
background: 'linear-gradient(135deg, #0f172a, #1e293b)',
color: 'white',
fontFamily: 'sans-serif',
}}>
<div style={{ fontSize: 28, opacity: 0.7 }}>
ahmedhaies.com · {category}
</div>
<div style={{ marginTop: 'auto', fontSize: 64, lineHeight: 1.2 }}>
{title}
</div>
<div style={{ marginTop: 24, fontSize: 24, opacity: 0.8 }}>
اقرأ المقال كامل
</div>
</div>
),
{ width: 1200, height: 630 }
);
}بعدين اربطه بصفحة المقال في generateMetadata:
export async function generateMetadata({ params }) {
const article = await getArticle(params.slug);
const ogUrl = `/og?title=${encodeURIComponent(article.title)}`
+ `&category=${encodeURIComponent(article.category)}`;
return {
title: article.title,
openGraph: { images: [ogUrl] },
twitter: { card: 'summary_large_image', images: [ogUrl] },
};
}خلاص. كل مقال جديد هيطلع له غلاف خاص بيه بدون ما تلمس أداة تصميم.
الخط العربي — الجزء اللي بيتنسى
Satori ما بيدعمش خطوط النظام تلقائيًا. لازم تحمّل ملف الخط يدويًا وتمرّره للـ ImageResponse عشان العنوان العربي يطلع صح بدل مستطيلات فاضية:
const cairo = await fetch(
new URL('./fonts/Cairo-Bold.ttf', import.meta.url)
).then(res => res.arrayBuffer());
return new ImageResponse(jsx, {
width: 1200,
height: 630,
fonts: [{ name: 'Cairo', data: cairo, style: 'normal', weight: 700 }],
});ضيف كمان direction: 'rtl' في الـ parent div علشان الترتيب يطلع طبيعي من اليمين لليسار.
الأرقام الفعلية (قياس حقيقي)
- زمن توليد الصورة الواحدة: 120-180 مللي ثانية على Vercel Edge، أول طلب ممكن يوصل 400ms بسبب cold start.
- حجم الـ PNG الناتج: بين 40KB و 80KB — مناسب لمعاينات كل الشبكات.
- التكلفة على Vercel: أقل من دولار شهريًا لأول 100K طلب معاينة (ضمن فئة Edge Function invocations المجانية عند بداية الخطط المدفوعة).
- حد الـ bundle: 500KB إجمالي يشمل الـ JSX والخطوط والصور المضمّنة حسب توثيق Vercel الرسمي.
- التخزين المؤقت: Vercel بتـ cache الصورة على CDN حسب query string، يعني نفس العنوان مش هيتولّد تاني.
trade-offs مش هتلاقيها في التوثيق
بتكسب: صفر صيانة، اتساق بصري كامل مع هوية الموقع، وقدرة تغيّر تصميم كل الأغلفة القديمة والجديدة بتعديل ملف واحد.
بتخسر: تحكم دقيق في الـ typography. Satori بيدعم flexbox بس وبعض خصائص CSS — مفيش display: grid ولا animations ولا filters معقّدة. لو محتاج تصميم فيه overlays و effects، هترجع لمكتبة تانية زي sharp أو تصميم يدوي.
فرضية أساسية: الكود ده مبني على إن مشروعك Next.js 14 أو أحدث ومستضاف على منصة بتدعم Edge Functions (Vercel, Cloudflare Pages Functions). لو مشروعك static export أو PHP، الحل ده مش هيشتغل.
متى لا تستخدم هذه الطريقة
- لو موقعك static بحت وبتستضيفه على GitHub Pages أو Netlify بدون functions — مش فيه runtime يتولّد فيه الصور.
- لو بتحتاج تصاميم فيها رسوم تفصيلية (شخصيات، illustrations بـ SVG paths معقّدة) — Satori هيطلع نتايج ضعيفة.
- لو عدد مقالاتك أقل من 20 وهيفضل كده — تصميم كل مقال يدويًا أسرع من إعداد البنية وضبط الخطوط.
- لو بتستخدم إطار عمل مش Next.js ومفيش وقت تعيد كتابة الراوتنج — جرّب حل أبسط زي خدمة Cloudinary text overlay.
الخطوة التالية
افتح مشروعك دلوقتي، أنشئ ملف app/og/route.tsx بالكود اللي فوق، وعدّل generateMetadata في صفحة مقال واحد بس كتجربة. شارك اللينك على X أو LinkedIn واشوف المعاينة. لو طلعت بخط Latin بدل عربي، ضيف الـ fonts array زي ما في مثال Cairo. لو اشتغل مظبوط على مقال واحد، عمّمه على باقي المقالات.
المصادر
- توثيق Vercel الرسمي: Open Graph (OG) Image Generation — vercel.com/docs/og-image-generation
- توثيق Next.js: Functions: ImageResponse — nextjs.org/docs/app/api-reference/functions/image-response
- مستودع Satori على GitHub: github.com/vercel/satori
- إعلان إطلاق @vercel/og الرسمي: Introducing OG Image Generation — vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images
- تحليل BuzzSumo عن تأثير الصور على التفاعل (مذكور في دراسات NoGood و Aira عن Open Graph SEO).
- Aira — Open Graph: How to Drive More Social Media Traffic: aira.net/blog/open-graph-social-media-traffic
- @vercel/og على npm: npmjs.com/package/@vercel/og