آموزش ساخت بلوک سفارشی گوتنبرگ
چرا بلوک سفارشی گوتنبرگ در ویرایشگر ظاهر نمیشود یا در فرانتاند بههم میریزد؟ این راهنما ساخت بلوک گوتنبرگ را از صفر با block.json، edit، save، attribute و بلوک پویا آموزش میدهد و اشتباهات رایج را بررسی میکند.
اولین باری که یک بلوک سفارشی نوشتم، چند ساعت طول کشید تا بفهمم چرا بلوک در ویرایشگر ظاهر نمیشود. بعد از چند بار بازبینی، کشف کردم فایل block.json را در جای اشتباهی قرار دادهام و وردپرس آن را پیدا نمیکرد. از آن روز، ساختار بلوک را مثل یک نقشه مشخص در ذهن دارم: هر فایل سر جای خودش، هر هوک در زمان خودش. این مقاله، همان نقشه است که در سالهای گذشته روی دهها پروژه اجرایش کردهام.
بلوک سفارشی چرا و کجا لازم میشود؟
در پروژههای وردپرسی، بلوک سفارشی معمولاً از دو جا سر درمیآورد: یا مشتری میخواهد یک بخش مشخص با ساختار ثابت در نوشتهها تکرار شود (مثل کارت محصول، جعبه اطلاعات، یا فرم تماس سریع) و شما نمیخواهید آن را دستی در هر نوشته بازسازی کنید؛ یا افزونهای در اختیار دارید که میخواهید خروجیاش را مستقیماً در ویرایشگر قابل استفاده کنید. در هر دو حالت، بلوک سفارشی ابزار درستی است.
در نگاه اول ممکن است فکر کنید این کار با یک shortcode هم انجام میشود، ولی در تجربه من بلوک در چند سناریو مزیت جدی دارد: تعامل بصری زنده با کاربر در ویرایشگر، امکان پیشنمایش بدون خروج از صفحه ویرایش، سازگاری با سایت ویرایشگر و ذخیرهسازی داده بهصورت ساختاریافته در HTML. اگر با مفهوم شورتکد آشنا نیستید، ساخت شورتکد با کدنویسی وردپرس تفاوت این دو ابزار را دقیقتر نشان میدهد.
بلوک گوتنبرگ فقط یک قطعه رابط کاربری نیست؛ یک قرارداد است بین ویرایشگر، دیتابیس و فرانتاند. اگر این قرارداد درست بسته نشود، هر سه طرف ضرر میکنند.
آناتومی یک بلوک گوتنبرگ
پیش از کد، ساختار فایلهای یک بلوک سفارشی را بشناسید. در رویکرد مدرن وردپرس، بلوکها با فایل block.json تعریف میشوند و بقیه فایلها حول همین تعریف میچرخند:
| فایل | نقش | اجباری؟ |
|---|---|---|
| block.json | تعریف هویت، attributeها و تنظیمات بلوک | بله (رویکرد مدرن) |
| src/index.js | ثبت بلوک در جاوااسکریپت | بله |
| src/edit.js | رابط ویرایشگر بلوک | بله |
| src/save.js | خروجی ذخیرهشده در دیتابیس | برای بلوک ثابت، بله |
| src/editor.scss | استایل مخصوص ویرایشگر | اختیاری |
| src/style.scss | استایل مشترک ویرایشگر و فرانتاند | اختیاری |
| render.php | خروجی بلوک پویا در سمت سرور | فقط برای بلوک پویا |
اگر با ساختار فایل قالب و افزونه آشنا نیستید، راهنمای توسعه افزونه وردپرس از صفر و راهنمای توسعه قالب وردپرس از صفر نقطه شروع خوبی هستند. برای درک هوکها که در ساخت بلوک زیاد بهکار میآیند، هوکهای وردپرس چیستند و چگونه کار میکنند پیشنیاز ضروری است.
گام اول: تعریف block.json
فایل block.json شناسنامه بلوک شماست. وردپرس با خواندن این فایل، بلوک را میشناسد، attributeهایش را میفهمد و میداند استایلها و اسکریپتهایش را چطور بارگذاری کند. نمونهای ساده از این فایل برای یک بلوک کارت اطلاعات:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "myplugin/info-card",
"version": "1.0.0",
"title": "کارت اطلاعات",
"category": "widgets",
"icon": "info-outline",
"description": "یک کارت برای نمایش اطلاعات کوتاه",
"textdomain": "myplugin",
"attributes": {
"title": {
"type": "string",
"default": ""
},
"content": {
"type": "string",
"default": ""
}
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css"
}
سه نکته مهم در این فایل: اول، name باید با namespace معتبر و یکتا باشد؛ همین نام در دیتابیس و در ویرایشگر بهعنوان شناسه استفاده میشود. دوم، apiVersion روی ۳ یعنی از بلوکهای نسل جدید استفاده میکنید که پیشنهادشده است. سوم، attributes شبیه schema دادهای بلوک است؛ اینجا هر چیزی که میخواهید در دیتابیس ذخیره کنید تعریف میکنید.
گام دوم: ثبت بلوک در PHP
بلوکها معمولاً در افزونه سفارشی ثبت میشوند، نه در قالب؛ چون بلوک بهذات یک قابلیت محتوایی است، نه یک ویژگی ظاهری. الگوی ثبت در فایل اصلی افزونه:
<?php
function myplugin_register_blocks() {
register_block_type( __DIR__ . '/build/info-card' );
}
add_action( 'init', 'myplugin_register_blocks' );
اگر بلوک را برای قالب مینویسید، همین کد را در functions.php قالب چایلد قرار دهید. نکته مهم: register_block_type باید در هوک init فراخوانی شود؛ اگر در after_setup_theme یا در بالای فایل بدون هوک قرار دهید، بلوک در ویرایشگر ظاهر نمیشود. اگر با تفاوت هوکها آشنا نیستید، ساخت اکشن سفارشی در وردپرس ترتیب درست اجرا را توضیح میدهد.
اگر رویکرد بدون build tools را ترجیح میدهید — مثلاً برای پروژههای کوچک یا آموزش — میتوانید فایلهای JS را مستقیم در wp-content/plugins بگذارید و بهجای file:./index.js از editorScript: "myplugin-info-card-editor" استفاده کنید. من در پروژههای جدی همیشه از build tools استفاده میکنم چون نگهداری و نسخهبندی را سادهتر میکند.
گام سوم: ثبت جاوااسکریپتی بلوک
در فایل src/index.js بلوک را در ویرایشگر ثبت میکنید. الگوی ساده:
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import Save from './save';
import metadata from './block.json';
import './editor.scss';
import './style.scss';
registerBlockType( metadata.name, {
edit: Edit,
save: Save,
} );
نکتهای که در سالهای اخیر باعث تغییرات زیادی شده: از apiVersion: 3 به بعد، در Metadata همان block.json میتوان تنظیمات زیادی را تعریف کرد و دیگر نیازی به تکرارشان در registerBlockType نیست. این باعث میشود فایل index.js بسیار کوتاهتر و خواناتر باشد.
گام چهارم: تابع ویرایش (Edit)
تابع Edit، رابط ویرایشگر بلوک است. الگوی آن با کامپوننتهای هسته وردپرس:
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<RichText
tagName="h3"
value={ attributes.title }
onChange={ ( title ) => setAttributes( { title } ) }
placeholder="عنوان کارت"
/>
<RichText
tagName="p"
value={ attributes.content }
onChange={ ( content ) => setAttributes( { content } ) }
placeholder="محتوای کارت"
/>
</div>
);
}
سه نکته مهم در این کد: اول، useBlockProps() را همیشه استفاده کنید؛ این هوک، کلاسها و attributeهای لازم بلوک را اعمال میکند و اگر آن را نگذارید، بلوک در ویرایشگر ظاهر میشود ولی در فرانتاند مشکل خواهد داشت. دوم، RichText ابزار استاندارد وردپرس برای متن قابلویرایش است؛ از آن بهجای textarea یا input ساده استفاده کنید چون هم تجربه کاربری بهتری میدهد و هم در ذخیرهسازی، HTML معتبرتری تولید میکند. سوم، پیش از دستزدن به هر بلوک، توصیه میکنم مقاله گوتنبرگ و آینده ویرایش محتوا در وردپرس را بخوانید تا فلسفه طراحی این لایه را درک کنید.
گام پنجم: تابع ذخیره (Save)
تابع Save، خروجی بلوک را در دیتابیس تولید میکند. مهمترین قاعده در این تابع: خروجی باید دقیقاً با ساختاری که در تابع Edit استفاده کردهاید همخوان باشد، وگرنه وردپرس پیام Block validation failed میدهد. الگوی Save برای بلوک ساده:
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Save( { attributes } ) {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<RichText.Content
tagName="h3"
value={ attributes.title }
/>
<RichText.Content
tagName="p"
value={ attributes.content }
/>
</div>
);
}
نکتهای که در چند پروژه بارها دیدهام: اگر تابع Edit را تغییر دهید ولی تابع Save را متناسب با آن بهروز نکنید، همه بلوکهای موجود در سایت شما پیام خطا میدهند. برای جلوگیری از این مشکل، پیش از انتشار بلوک، یا تابع Save را نهایی کنید و بعد دست به Edit نزنید، یا از بلوک پویا استفاده کنید که در بخش بعدی توضیح دادهام.
تابع Save در واقع تعهد شما به ساختار داده است. اگر این تعهد را بشکنید، حتی خودتان هم نمیتوانید بلوکهای قدیمی سایتتان را ویرایش کنید.
گام ششم: مدیریت attributeها
attributeها در بلوک گوتنبرگ، معادل پایگاه دادهای آن هستند. سه نکته مهم در مدیریت آنها:
- نوع داده را دقیق تعیین کنید. در
block.jsonبرای هر attributetypeمشخص کنید —string،number،boolean،array،object. اگر این را نادرست تنظیم کنید، بلوک در ذخیرهسازی رفتار عجیبی نشان میدهد. - مقدار پیشفرض بدهید. برای هر attribute یک
defaultتعیین کنید تا کاربر در تجربه اول با بلوک خالی مواجه نشود. - برای متنهای غنی از
sourceاستفاده کنید. اگر مقدار attribute را مستقیم از HTML استخراج میکنید، ازsource: "html"وselectorاستفاده کنید:
"title": {
"type": "string",
"source": "html",
"selector": "h3"
}
با این الگو، وردپرس خودش مقدار را از HTML ذخیرهشده استخراج میکند و شما نیازی به ذخیره جداگانه در متادیتا ندارید. این رویکرد باعث میشود داده بلوک، در خروجی HTML قابل جستجو و در صورت مهاجرت، قابل انتقال باشد.
گام هفتم: استایل ویرایشگر و فرانتاند
در بلوکهای گوتنبرگ، سه نوع استایل دارید و تفکیک آنها در تجربه من مهمترین نکته در ساخت بلوکهای حرفهای است:
- style.scss: برای استایل مشترک ویرایشگر و فرانتاند. هر چیزی که ظاهر بلوک را میسازد اینجا قرار میگیرد.
- editor.scss: برای استایلهایی که فقط در ویرایشگر لازم است — مثل حاشیه یا پلیسهولدر. این استایل در فرانتاند بارگذاری نمیشود.
- view.scss: برای استایلهایی که فقط در فرانتاند لازم است، مثل انیمیشنهایی که در ویرایشگر نباید اجرا شوند.
اگر از کلاسها درست استفاده کنید، بلوک شما در ویرایشگر مثل پیشنمایش دقیق فرانتاند دیده میشود. توصیهام این است که هر استایل را در قالببندی مشترک بنویسید تا بین ویرایشگر و فرانتاند اختلاف ظاهری نباشد. مبانی کدنویسی استاندارد در وردپرس در استانداردهای کدنویسی وردپرس آمده است.
گام هشتم: بلوک پویا با ServerSideRender
گاهی بلوک شما باید محتوایش را از دیتابیس بخواند — مثل نمایش آخرین محصولات، آخرین نوشتههای یک دسته، یا نمایش اطلاعات کاربر. در این حالت، تابع Save لازم نیست HTML را ذخیره کند چون خروجی در زمان اجرا محاسبه میشود. الگوی بلوک پویا:
<?php
function myplugin_render_info_card( $attributes ) {
$title = isset( $attributes['title'] ) ? $attributes['title'] : '';
$content = isset( $attributes['content'] ) ? $attributes['content'] : '';
return sprintf(
'<div class="wp-block-myplugin-info-card"><h3>%s</h3><p>%s</p></div>',
esc_html( $title ),
esc_html( $content )
);
}
register_block_type( __DIR__ . '/build/info-card', array(
'render_callback' => 'myplugin_render_info_card',
) );
در تابع Save برای بلوک پویا، معمولاً یک رشته خالی برگردانید چون وردپرس از render_callback استفاده میکند. برای پیشنمایش در ویرایشگر، میتوانید از کامپوننت ServerSideRender استفاده کنید، ولی در تجربه من برای بلوکهای ساده، پیشنمایش سبک در سمت کلاینت هم کافی است. تفاوت بلوک ثابت و پویا در جدول زیر خلاصه شده است:
| معیار | بلوک ثابت | بلوک پویا |
|---|---|---|
| ذخیره HTML در دیتابیس | بله | خیر |
| تغییر ساختار در آینده | نیاز به مهاجرت داده | راحت |
| مناسب برای | محتوای ثابت کاربر | داده پویا از دیتابیس |
| سرعت فرانتاند | سریعتر | بستگی به کوئری دارد |
اشتباهات رایج در ساخت بلوک
در پروندههای زیادی که در ساخت بلوک گوتنبرگ بررسی کردهام، پنج اشتباه بیشتر از بقیه تکرار شده:
- عدم تطابق Edit و Save. اگر ساختار HTML در Edit و Save یکی نباشد، بلوک در ویرایشگر خطای
Block validation failedمیدهد. مهمترین قاعده: همیشه ساختار را یکی نگه دارید. - فراموش کردن useBlockProps. بدون این هوک، کلاسها و attributeهای بلوک به خروجی اضافه نمیشوند و در نتیجه استایلها در فرانتاند اعمال نمیشود.
- ثبت بلوک در هوک اشتباه.
register_block_typeباید در هوکinitباشد. اگر آن را درafter_setup_themeیا مستقیم در بالای فایل قرار دهید، بلوک در ویرایشگر دیده نمیشود. - نبود namespace یکتا در نام بلوک. نام بلوک باید شامل
namespace/block-nameباشد. اگر فقطinfo-cardرا بهعنوان نام بگذارید، با بلوکهای افزونههای دیگر تعارض پیدا میکند. - فراموش کردن تست فرانتاند. بعضی از خطاهای بلوک فقط در فرانتاند ظاهر میشوند. مثلاً اگر خروجی Save شامل تگهای اشتباه باشد، در ویرایشگر کار میکند ولی در فرانتاند بههم میریزد.
اشتباه ششمی هم هست که کمتر گفته میشود ولی در پروژههای واقعی دیدهام: نوشتن کد بلوک در قالب بهجای افزونه. اگر بلوک در قالب قرار بگیرد، روز تغییر قالب، بلوک شما از بین میرود و محتوای نوشتههایی که از آن استفاده کردهاند، به HTML خام تبدیل میشود. همیشه بلوک را در افزونه سفارشی قرار دهید؛ همان منطقی که در کدنویسی اختصاصی برای افزونه وردپرس روی آن تأکید کردهام.
روش تست و دیباگ بلوک
چهار ابزار در تست و دیباگ بلوک گوتنبرگ همیشه در دسترس من هستند:
- کنسول مرورگر: بیشتر خطاهای بلوک در کنسول مرورگر ظاهر میشوند. اگر بلوک ثبت نمیشود، اولین جایی که باید نگاه کنید همینجاست.
- لاگ دیباگ وردپرس: خطاهای سمت سرور، مخصوصاً در بلوکهای پویا، در فایل
wp-content/debug.logثبت میشوند. اگر با مفهوم این لاگ آشنا نیستید، پیدا کردن خطاهای جاوااسکریپت در کنسول مرورگر نقطه شروع خوبی است. - ابزار Block Validation: گوتنبرگ خودش یک پیام دقیق درباره عدم تطابق Edit و Save میدهد. اگر این پیام را در ویرایشگر دیدید، همیشه از بخش Console مرورگر جزئیات را ببینید.
- محیط استجینگ: بلوک را اول در محیط استجینگ تست کنید، بهخصوص اگر روی سایت زنده نصب میشود. مسیر استفاده از افزودن کد سفارشی به وردپرس در قالب چایلد هم برای تست مفید است.
یک عادت شخصی که از تجربههای تلخ شکل گرفته: پیش از هر تغییر در بلوکی که در سایت زنده استفاده شده، یک نسخه پشتیبان از پوشه افزونه بگیرم. اگر تغییر باعث خطای Block validation failed در همه بلوکهای موجود سایت شود، بازگشت به نسخه قبلی در چند دقیقه حل میشود، در حالی که بدون بکاپ میتواند چند ساعت وقت بگیرد.
بلوک سفارشی و بهینهسازی AEO
از دید AEO (Answer Engine Optimization یا بهینهسازی برای موتورهای پاسخده)، بلوک سفارشی میتواند یک لایه مهم در ساختار محتوای شما باشد. موتورهای جستجوی امروز و بهخصوص مدلهای زبان بزرگ، به ساختار HTML و معنای قطعات محتوا نگاه میکنند. اگر بلوک شما بخشی مثل پرسش و پاسخ، جدول مقایسه، یا اطلاعات منظم را تولید میکند، میتوانید در خروجی از تگهای معنایی و حتی دادهساختاریافته (Schema) استفاده کنید تا سایت شما شانس بیشتری برای نمایش در نتایج غنی داشته باشد.
یک مثال عملی که در پروژههای خودم بهکار بردهام: بلوکی برای نمایش پرسشهای متداول که خروجی HTML آن شامل <details><summary> است و بهعلاوه ساختار FAQPage را بهعنوان Schema میسازد. نتیجه این بلوک، هم تجربه کاربری بهتری است و هم احتمال بیشتری برای ظهور در نتایج پرسش و پاسخ گوگل. اگر با مفهوم کلی سئوی تکنیکال و دادهساختاریافته آشنا نیستید، SEO تکنیکال: از خزش تا ایندکس لایههای زیرین را توضیح میدهد.
بلوکها از دید معماری نرمافزار
برای مهندسانی که با معماری نرمافزار سروکار دارند، ارزش دارد بلوک گوتنبرگ را بهعنوان یک کامپوننت خودمختار (Autonomous Component) نگاه کنند که سه مسئولیت مستقل دارد: حالت (State)، نمایش (Render) و ذخیرهسازی (Persist). این تفکیک، همان الگوی کامپوننتهای مدرن در React، Vue یا Svelte است که در آن کامپوننتها جدا و قابل بازاستفاده طراحی میشوند.
سه مشاهده دقیقتر از تجربههای میدانی: اول، در پروژههای بزرگ با چند توسعهدهنده، مسئله نسخهبندی بلوک به یک چالش جدی تبدیل میشود. اگر تابع Save در نسخه دوم تغییر کند، بلوکهایی که با نسخه اول ساخته شدهاند، در ویرایشگر خطا میدهند. راهحل در معماری مدرن، استفاده از مفهوم deprecated است که به وردپرس میگوید نسخههای قبلی چطور به نسخه جدید تبدیل شوند. اگر با مفهوم نسخهبندی API آشنا نیستید، استانداردهای کدنویسی وردپرس چارچوب کلی را میدهد.
دوم، در معماری Headless که فرانتاند جدا از وردپرس سرو میشود، بلوک گوتنبرگ به یک لایه میانی تبدیل میشود بین داده ذخیرهشده و نمایش نهایی. اگر فرانتاند شما React یا Vue است، باید تصمیم بگیرید که آیا خروجی بلوک را در فرانتاند بازسازی میکنید یا آن را بهصورت HTML آماده از REST API میگیرید. هر دو رویکرد مزایایی دارند، ولی استفاده از HTML آماده در تجربه من سریعتر و سادهتر است؛ به شرطی که بلوک شما در ساختار HTML خودش دقیق باشد.
سوم، در سیستمهای چند-مستأجری (Multi-Tenant) که چند سایت روی یک نصب وردپرس سرو میشوند، بلوکها میتوانند یک منبع بزرگ برای اشتراکگذاری کد باشند. اگر یک بلوک را در یک سایت شبکه ثبت کنید، بقیه سایتها میتوانند همان بلوک را در محتوای خود استفاده کنند. این یعنی معماری درست، هزینه ساخت بلوک را بین چند سایت توزیع میکند و سرعت توسعه را بالا میبرد. با این حال، بههمین دلیل هم نیاز به مدیریت نسخه دقیقتر است، چون تغییری در بلوک، روی همه سایتها اثر میگذارد.
چهارم، در CI/CD (Continuous Integration / Continuous Deployment یا یکپارچهسازی و استقرار پیوسته)، بلوکها باید بهعنوان بخشی از کدبیس مدیریت شوند. یعنی نه در پیشخوان ویرایش دستی، نه از طریق مدیریت فایل. بلوک در مخزن Git، نسخهبندیشده، با تست خودکار — این همان انضباطی است که پروژههای نرمافزاری مدرن دارند و در وردپرس کمتر دیده میشود. نتایج این رویکرد در پروژههای بزرگ، تفاوت بین پایداری و بحران ماهانه است.
پنجم، در معماری بلوکمحور نسل جدید وردپرس (Block Themes)، خود قالب هم مجموعهای از بلوکهاست. یعنی در آیندهای نهچندان دور، بلوکهای سفارشی شما بخشی از هویت قالب خواهند بود، نه افزونهای اضافی. این تغییر پارادایم، ساختار مدیریت و نگهداری بلوکها را هم تغییر میدهد. اگر پروژه شما در افق چندساله دیده میشود، از امروز بلوکهایتان را با معیار قالب بلوکی طراحی کنید — یعنی ساختاری معنایی، قابلبازاستفاده و وابسته به هسته وردپرس، نه به صفحات ثابت.
خط بستهشدن یک بلوک
اگر بخواهم این مقاله را در سه نکته فشرده کنم: اول، ساخت بلوک گوتنبرگ یک پروژه کوچک است با ساختار مشخص — block.json، index، edit، save و استایلها. هر فایل سر جای خودش، خطا کاهش پیدا میکند. دوم، همیشه بلوک را در افزونه سفارشی قرار دهید، نه در قالب؛ این کار بلوک شما را از تغییر قالب محفوظ میدارد. سوم، پیش از انتشار بلوک، تابع Save را نهایی کنید و از آن پس به آن دست نزنید؛ اگر نیاز به تغییر ساختار داشتید، از deprecated استفاده کنید تا بلوکهای قبلی نشکنند.
پیشنهاد عملی من برای همین هفته: اگر تا حالا بلوک سفارشی نساختهاید، با npx @wordpress/create-block یک بلوک ساده بسازید و در محیط لوکال تست کنید. مسیر نصب و راهاندازی با توسعه وردپرس با محیط لوکال شروع میشود. اگر با ساختار افزونه آشنا نیستید، راهنمای توسعه افزونه از صفر پیش از بلوکنویسی توصیه میشود. همچنین برای درک بهتر تفاوت بلوک با ویجت، ساخت ویجت سفارشی با کدنویسی وردپرس تصویر کاملتری میدهد. اگر در پروژهای با خطای بلوک گوتنبرگ مواجه شدهاید که در این فهرست نبوده — بهخصوص اگر در قالبهای بلوکی یا معماری Headless بوده — برایم بنویسید کدام علت ریشهای بود و چطور به جواب رسیدید. تجربههای واقعی شما همان چیزی است که این راهنما را برای نفر بعدی دقیقتر میکند. 🧱