لو بتنشر 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 }
);
}