كيفية إنشاء مكوّن معتمد على البيانات باستخدام ووردبريس وNext.js
مقدمة: لماذا نحتاج إلى مكوّنات معتمدة على البيانات؟
عند تطوير موقع يعتمد على المحتوى لعميل أو جهة عمل، تظهر عادةً معادلة صعبة: كيف نحافظ على تجربة استخدام متناسقة، وفي الوقت نفسه نتيح مساحة كافية للمرونة والإبداع؟ هنا يبرز دور المكوّنات المعتمدة على البيانات، إذ تسمح بفصل المحتوى عن الواجهة، وجعل إدارة العناصر الديناميكية أكثر سهولة وقابلية للتوسع.
في هذا الدليل، سنتناول طريقة بناء مكوّن ديناميكي يبدأ من لوحة تحكم WordPress وينتهي في واجهة Next.js، بالاعتماد على إضافة Advanced Custom Fields واستخدام GraphQL لجلب البيانات بدقة وكفاءة.

ما الذي سنقوم ببنائه؟
الفكرة هنا هي إنشاء مكوّن واجهة يعرض محتوى منظماً يمكن التحكم فيه من خلال لوحة إدارة WordPress. يتكون هذا المكوّن من العناصر التالية:
- عنوان رئيسي.
- وصف مختصر أو نص تعريفي.
- أربعة روابط لمقالات أو منشورات ذات صلة.
- زران يقودان المستخدم إلى محتوى إضافي.
هذا النوع من المكوّنات مناسب جداً للصفحات الرئيسية وصفحات الهبوط والمناطق الترويجية داخل المواقع التي تعتمد على إدارة محتوى مرنة.

تخطيط البيانات قبل التنفيذ
قبل إنشاء أي حقول مخصصة، من المهم تحليل المكوّن إلى وحدات بيانات واضحة. هذه الخطوة توفر عليك كثيراً من التعقيد لاحقاً، خاصة عند ربط الواجهة الخلفية مع الواجهة الأمامية.
في هذا المثال، نحتاج إلى تخزين:
- حقل للعنوان.
- حقل للنص الوصفي.
- مجموعة تضم أربعة منشورات مرتبطة.
- مجموعة للزر الأول تحتوي على النص والرابط.
- مجموعة للزر الثاني تحتوي على النص والرابط.
بعد هذا التقسيم، تصبح عملية تصميم الحقول في ACF أكثر وضوحاً.
إنشاء الحقول المخصصة باستخدام ACF
بعد تثبيت وتفعيل إضافة Advanced Custom Fields، ستظهر لك صفحة جديدة في الشريط الجانبي داخل لوحة تحكم WordPress. من هناك يمكنك إنشاء مجموعة حقول جديدة وربطها بالصفحات التي تريدها.

إضافة حقول العنوان والوصف
ابدأ بإضافة أول حقلين للمكوّن:
- حقل عنوان من نوع
Text. - حقل وصف من نوع
TextareaأوTextبحسب طول المحتوى المتوقع.
في أغلب الحالات، يكفي تحديد:
- الاسم الظاهر
Label. - الاسم البرمجي
Name. - نوع الحقل
Type.

إضافة مجموعة المنشورات المرتبطة
نحتاج هنا إلى أربعة منشورات ذات صلة. في النسخة المدفوعة ACF Pro يمكن استخدام حقل Repeater لتغيير العدد بسهولة، لكن بما أن الشرح يعتمد على النسخة المجانية، فسنستخدم حقلاً من نوع Group يحتوي على أربعة حقول داخلية.
كل حقل داخلي سيكون من نوع Post Object، بحيث يسمح للمحرر باختيار منشور موجود مسبقاً من الموقع.

إضافة حقول الأزرار
بعد ذلك، أنشئ مجموعتين منفصلتين:
- مجموعة للزر الأول.
- مجموعة للزر الثاني.
وكل مجموعة تحتوي على:
- حقل نصي لاسم الزر
label. - حقل رابط للوجهة
link.

وبذلك يصبح نموذج الحقول النهائي مكتملاً وجاهزاً للاستخدام.

تحديد أماكن ظهور الحقول داخل لوحة التحكم
من مزايا ACF أنك تتحكم بالكامل في مكان ظهور هذه الحقول. في هذا المثال، الهدف هو إتاحة هذه الحقول داخل أي صفحة، لذلك نضبط قاعدة الظهور Location Rule بحيث تظهر عند تحرير الصفحات.

من الناحية العملية، يفضل عدم إظهار الحقول في كل مكان إلا عند الحاجة. الإفراط في الحقول داخل لوحة التحرير قد يربك فريق المحتوى، ويؤثر سلباً على تجربة التحرير.
كما أن خيارات التخطيط Layout داخل ACF تساعدك على ترتيب الحقول بشكل مريح، سواء من حيث العرض أو التموضع، وهو أمر مهم جداً في المشاريع متعددة المحررين.
إنشاء صفحة تحتوي على المكوّن
في هذه المرحلة، سيتم استخدام الحقول المخصصة داخل نوع محتوى موجود مسبقاً، مثل الصفحات. هذا يعني أن المحرر سيرى محرر Gutenberg بالإضافة إلى حقول المكوّن.
صحيح أنه يمكن تغليف هذه الحقول داخل كتلة Gutenberg Block، لكن هذا الخيار ليس محور هذا الشرح، لسببين رئيسيين:
- يتطلب استخدام
ACF Pro. - دعم كتل
Gutenbergفي سياقGraphQLليس ناضجاً بما يكفي في كثير من الحالات.
بعد ذلك، يمكنك ملء الصفحة بالمحتوى وإدخال بيانات المكوّن يدوياً.

عند اختيار الحقول من نوع Post Object، ستظهر قائمة بالمنشورات المتاحة داخل الموقع. اختر منها المقالات المناسبة، ثم أدخل تفاصيل الزرين.

تجهيز GraphQL لقراءة الحقول المخصصة
لكي تصبح بيانات ACF متاحة من خلال GraphQL، ستحتاج إلى تثبيت إضافتين:
WPGraphQLلإتاحة واجهةGraphQLداخلWordPress.WPGraphQL for Advanced Custom Fieldsلربط حقولACFمع مخططGraphQL.
بعد التثبيت، عد إلى مجموعة الحقول، وستجد خياراً جديداً يسمح بإظهار هذه الحقول في GraphQL مع منحها اسماً خاصاً لاستخدامه في الاستعلامات.

بعدها، يمكنك فتح واجهة GraphiQL وبناء الاستعلام المناسب.

مثال على استعلام GraphQL
الميزة الأهم في GraphQL هي أنك تطلب البيانات التي تحتاج إليها فقط، دون جلب حقول إضافية غير مستخدمة.
query MyQuery {
pages {
nodes {
frontPanelFields {
fieldGroupName
text
title
button1 {
label
link
}
button2 {
label
link
}
posts {
post1 {
... on Post {
id
slug
title
}
}
post2 {
... on Post {
id
slug
title
}
}
post3 {
... on Post {
id
slug
title
}
}
post4 {
... on Post {
id
slug
title
}
}
}
}
}
}
}
جلب البيانات إلى Next.js
بعد تجهيز الاستعلام، تأتي مرحلة إدخاله إلى مشروع Next.js. إذا كنت تستخدم بنية جاهزة تربط WordPress مع Next.js أو لديك Apollo Client مفعّل مسبقاً، فبإمكانك توظيف الاستعلام مباشرة.
إنشاء دالة الاستعلام في ملف /data/pages.js
export function getQueryFrontPanelById ( id ) {
return gql`
query {
page(id: "${id}") {
frontPanelFields {
fieldGroupName
text
title
button1 {
label
link
}
button2 {
label
link
}
posts {
post1 {
... on Post {
id
slug
title
}
}
post2 {
... on Post {
id
slug
title
}
}
post3 {
... on Post {
id
slug
title
}
}
post4 {
... on Post {
id
slug
title
}
}
}
}
}
}
`
}
الاختلاف هنا أن الاستعلام بات يعتمد على id الخاص بالصفحة، وليس على slug. والسبب أن الصفحات في WordPress قد تكون هرمية، لذلك لا يكون البحث عبر slug دائماً الخيار الأدق.
استخراج الصفحة المناسبة ثم جلب بيانات الحقول
export async function getContentAndFields ( slug ) {
// Get all of the WordPress pages
const { pages } = await getAllPages()
// Find one that has a matching slug
const filteredPages = pages.filter( page => page.slug === slug)
// If we don't find one, return null
if (filteredPages.length === 0 ) {
return null
}
// Otherwise, get the first one
const page = filteredPages[ 0 ]
// Get the Front Panel info
const { frontPanelFields } = await getFrontPanelById(page.id)
// and return all the data.
return {
...page,
frontPanelFields
}
}
هذه الدالة تؤدي ثلاث مهام أساسية:
- الحصول على جميع الصفحات.
- تحديد الصفحة المطابقة لقيمة
slug. - استخدام
idالخاص بها لجلب بيانات المكوّن.
إنشاء صفحة ديناميكية في Next.js
الخطوة التالية هي إنشاء ملف [slug].js داخل مجلد الصفحات، حتى نستعرض الصفحة بناءً على الرابط الديناميكي.
import Layout from "components/Layout"
import FrontPanelComponent from "components/FrontPanelComponent"
import { getContentAndFields } from "lib/pages"
import Error from "next/error"
export default function PageWithAcfComponents ( { pageData } ) {
// If there is no pageData, then the page didn't exist so serve a 404 error.
if (!pageData) {
return <Error statusCode={404} />
}
// Destructure the content and panel data from the page data.
const { content, frontPanelFields } = pageData;
return (
<Layout>
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8 blog-post max-w-2xl">
{/* If there is frontPanelFields, render the component. */}
{frontPanelFields && <FrontPanelComponent data={frontPanelFields} />}
{/* Render the other content to the DOM. */}
<div
className=""
dangerouslySetInnerHTML={{
__html: content,
}}
/>
</div>
</Layout>
)
}
export async function getServerSideProps ( { params = {} } = {} ) {
// Get the slug from the request route.
const slug = params?.slug;
// Get the fields and content using the function we defined earlier
const pageData = await getContentAndFields(slug)
// Return the pageData to the component above.
return {
props: {
pageData
}
}
}
يعتمد هذا الملف على getServerSideProps لجلب البيانات أثناء الطلب، ثم تمريرها إلى المكوّن المسؤول عن العرض. وفي حال عدم العثور على الصفحة، يتم إرجاع خطأ 404.
بناء مكوّن FrontPanelComponent في الواجهة الأمامية
الآن وصلنا إلى الجزء العملي الأهم: عرض البيانات القادمة من WordPress داخل مكوّن واجهة في Next.js.
import Link from "next/link";
import Image from "next/image";
import { getMetaImage } from "lib/image";
export default function FrontPanelComponent ( { data: { title, text, button1, button2, posts, ...rest } } ) {
return (
<div className="bg-white p-4 py-20">
<h2 className="text-center font-bold text-dlblue text-3xl">{title}</h2>
<div className="flex flex-col md:flex-row justify-between">
<div>{mapPosts(posts)}</div>
<div className="text-xl text-black leading-none px-8 xl:text-2xl lg:text-xl -pt-4">
<div>{text}</div>
</div>
</div>
<div className="flex md:flex-row flex-wrap md:justify-end mt-8">
<div className="text-normal lg:text-xl mx-2">
<a
className="text-white rounded-xl p-4 text-primary hover:bg-primary hover:text-white border-2 border-primary bg-white"
href={button1.link}
target="_blank"
>
{button1.label}
</a>
</div>
<div className="text-normal md:text-xl mx-2">
<a
className="hover:text-lightPrimary hover:bg-white border-2 hover:border-primary text-white bg-lightPrimary rounded-xl p-4"
href={button2.link}
target="_blank"
>
{button2.label}
</a>
</div>
</div>
</div>
)
}
function mapPosts ( posts ) {
const { post1, post2, post3, post4 } = posts;
return (
<div className="pt-8 grid grid-rows-2 grid-cols-2">
<div className="mx-2">
<Link href={`/posts/${post1.slug}`}>
<a>
<Image src={getMetaImage(post1.title)} width="380" height="200" />
</a>
</Link>
</div>
<div className="mx-2">
<Link href={`/posts/${post2.slug}`}>
<a>
<Image src={getMetaImage(post2.title)} width="380" height="200" />
</a>
</Link>
</div>
<div className="mx-2">
<Link href={`/posts/${post3.slug}`}>
<a>
<Image src={getMetaImage(post3.title)} width="380" height="200" />
</a>
</Link>
</div>
<div className="mx-2">
<Link href={`/posts/${post4.slug}`}>
<a>
<Image src={getMetaImage(post4.title)} width="380" height="200" />
</a>
</Link>
</div>
</div>
)
}
ما الذي يحدث داخل هذا المكوّن؟
رغم أن الكود يبدو طويلاً، إلا أن وظيفته الأساسية بسيطة: أخذ البيانات المرسلة من الخادم ثم عرضها بصيغة أنيقة داخل الواجهة.
هناك ثلاث نقاط تقنية مهمة تستحق الانتباه:
- استخدام مكوّن
LinkمنNext.jsللروابط الداخلية، ما يساعد على التنقل السريع وتحسين تجربة المستخدم. - استخدام مكوّن
Imageلتحسين الصور ودعم التحميل الكسولlazy loading. - استخدام دالة
getMetaImageلتوليد رابط صورة ديناميكي، على سبيل المثال عبرCloudinary، مع إضافة عنوان المقال فوق صورة أساسية.
لماذا هذا الأسلوب مفيد في المشاريع الحقيقية؟
هذه البنية ليست مجرد تمرين تقني، بل تقدم فائدة مباشرة في مواقع الإنتاج. عند الاعتماد على WordPress كنظام إدارة محتوى، وNext.js كواجهة حديثة، تحصل على مزايا مهمة:
- مرونة عالية للمحررين دون العبث بالكود.
- فصل واضح بين إدارة المحتوى والعرض البرمجي.
- تحكم دقيق في البيانات المطلوبة عبر
GraphQL. - أداء أفضل في الواجهة الأمامية.
- قابلية تطوير المكوّنات وإعادة استخدامها مستقبلاً.
كما أن هذا النموذج يصلح للتوسع لاحقاً بإضافة مكوّنات أكثر تعقيداً مثل الحقول المتكررة Repeater Fields أو المحتوى المرن Flexible Content عند استخدام ACF Pro.
نصائح عملية لتحسين الجودة وتهيئة محركات البحث
- احرص على تسمية الحقول داخل
ACFبشكل واضح ومنهجي لتسهيل صيانتها لاحقاً. - قلّل عدد الحقول الظاهرة للمحرر حتى لا تتحول لوحة الإدارة إلى واجهة معقدة.
- استخدم عناوين وصفية وبيانات غنية داخل المكوّن، لأن ذلك ينعكس إيجابياً على
SEO. - إن كنت تعرض صوراً ديناميكية، فتأكد من دعم النص البديل
altكلما أمكن. - اختبر استعلامات
GraphQLفيGraphiQLقبل نقلها إلى التطبيق لتقليل الأخطاء.
الخلاصة التقنية
يُعد الجمع بين WordPress وACF وWPGraphQL وNext.js خياراً عملياً لبناء مكوّنات ديناميكية قوية وسهلة الإدارة. تقنياً، أفضل ما في هذا النهج هو أنه يمنح فريق المحتوى استقلالية كبيرة، وفي الوقت نفسه يحافظ على مرونة المطور في بناء واجهات حديثة وسريعة. إذا كنت تبحث عن بنية قابلة للتوسع وتخدم الأداء وتجربة التحرير معاً، فهذا الأسلوب من أنجح الخيارات المتاحة حالياً.