2026年5月22日

2026年5月22日

WordPressのGutenbergブロックエラーを解決する方法

はじめに

WordPressのGutenbergブロックエディターで「このブロックにはエラーが発生しました」と表示される・register_block_type()でカスタムブロックを登録したのにエディターに表示されない・block.jsonのassets(JS/CSS)が読み込まれない・ブロックを保存すると「保存されたマークアップが無効です」エラーになるといった問題は、ブロックAPIの仕様とedit/save関数の整合性を正しく理解することで解決できます。

症状・原因

  • register_block_type()initフック外で呼び出しているか、block.jsonのパスが間違っている
  • edit関数とsave関数の出力するHTMLが一致しないため「無効なマークアップ」エラーが発生している
  • JavaScriptのregisterBlockTypeでblock nameの名前空間(namespace/block-name形式)が正しくない
  • ブロックのスクリプトにwp-blockswp-elementなどの依存関係がwp_enqueue_scriptの依存配列に含まれていない

解決手順

ステップ1:ブロックの状態を診断する

# 登録済みブロックを確認
wp eval "print_r(array_keys(WP_Block_Type_Registry::get_instance()->get_all_registered()));" | grep myplugin

# ブロックのアセットが正しくエンキューされているか確認
wp eval "
global \$wp_scripts;
foreach (\$wp_scripts->queue as \$handle) {
    if (strpos(\$handle, 'myplugin') !== false) {
        echo \$handle . ': ' . \$wp_scripts->registered[\$handle]->src . PHP_EOL;
    }
}
"

# block.json の存在確認
ls -la wp-content/plugins/my-plugin/build/block.json

# ブラウザコンソールでエラーを確認(開発者ツール)
# Uncaught Error: Block "myplugin/my-block" is not registered.

ステップ2:block.jsonとPHP登録を正しく設定する

// block.json(ブロック定義ファイル)
{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "myplugin/my-card",
    "version": "1.0.0",
    "title": "マイカードブロック",
    "category": "common",
    "icon": "index-card",
    "description": "カード形式でコンテンツを表示するブロック",
    "supports": {
        "html": false,
        "align": ["wide", "full"],
        "color": {
            "background": true,
            "text": true
        }
    },
    "attributes": {
        "title": {
            "type": "string",
            "default": ""
        },
        "content": {
            "type": "string",
            "source": "html",
            "selector": ".card-content"
        },
        "imageId": {
            "type": "integer",
            "default": 0
        }
    },
    "editorScript": "file:./index.js",
    "editorStyle":  "file:./index.css",
    "style":        "file:./style-index.css"
}
// PHP側でblock.jsonを使って登録
add_action('init', function(): void {
    // ✅ block.json があるディレクトリを渡す(ファイルパスではない)
    register_block_type(__DIR__ . '/build');

    // または複数ブロックをまとめて登録
    $blocks = ['my-card', 'my-slider', 'my-accordion'];
    foreach ($blocks as $block) {
        register_block_type(__DIR__ . "/build/{$block}");
    }
});

// サーバーサイドレンダリングが必要なブロック
add_action('init', function(): void {
    register_block_type(__DIR__ . '/build', [
        'render_callback' => function(array $attributes, string $content): string {
            $title = esc_html($attributes['title'] ?? '');
            $posts = get_posts([
                'numberposts' => (int) ($attributes['count'] ?? 3),
                'post_status' => 'publish',
            ]);
            $output = "<div class='dynamic-block'><h2>{$title}</h2><ul>";
            foreach ($posts as $post) {
                $output .= '<li><a href="' . esc_url(get_permalink($post)) . '">'
                    . esc_html($post->post_title) . '</a></li>';
            }
            return $output . '</ul></div>';
        },
    ]);
});

ステップ3:JavaScriptのedit/save関数を正しく実装する

// src/index.js
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, RichText, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
import metadata from './block.json';

registerBlockType(metadata.name, {
    edit: function Edit({ attributes, setAttributes }) {
        // ✅ useBlockProps() は必ず呼ぶ(ブロックのクラス・属性を付与)
        const blockProps = useBlockProps({
            className: 'my-card-editor',
        });

        return (
            <>
                <InspectorControls>
                    <PanelBody title="カード設定">
                        <TextControl
                            label="タイトル"
                            value={attributes.title}
                            onChange={(title) => setAttributes({ title })}
                        />
                    </PanelBody>
                </InspectorControls>
                <div {...blockProps}>
                    <RichText
                        tagName="h3"
                        value={attributes.title}
                        onChange={(title) => setAttributes({ title })}
                        placeholder="タイトルを入力..."
                    />
                    <RichText
                        tagName="div"
                        className="card-content"
                        value={attributes.content}
                        onChange={(content) => setAttributes({ content })}
                        placeholder="コンテンツを入力..."
                    />
                </div>
            </>
        );
    },

    // ✅ save の出力は edit と完全に一致させる
    save: function Save({ attributes }) {
        const blockProps = useBlockProps.save({
            className: 'my-card',
        });
        return (
            <div {...blockProps}>
                <h3>{attributes.title}</h3>
                <RichText.Content
                    tagName="div"
                    className="card-content"
                    value={attributes.content}
                />
            </div>
        );
    },
});

ステップ4:ブロックの「無効なマークアップ」エラーを修正する

// deprecated(非推奨版)でマークアップ変更の後方互換性を保つ
registerBlockType('myplugin/my-card', {
    // ... edit function ...

    save: function Save({ attributes }) {
        // 現在の save 関数(v2)
        const blockProps = useBlockProps.save();
        return (
            <div {...blockProps}>
                <h3 className="card-title">{attributes.title}</h3>
            </div>
        );
    },

    deprecated: [
        {
            // v1 の旧 save 関数(マークアップ変更前)
            attributes: {
                title: { type: 'string', default: '' },
            },
            save: function SaveV1({ attributes }) {
                return (
                    <div className="my-card">
                        <h3>{attributes.title}</h3>
                    </div>
                );
            },
            migrate: function migrate(attributes) {
                // 旧属性を新属性に変換
                return attributes;
            },
        },
    ],
});
# 無効になったブロックを一括で再保存(WP-CLI)
wp post list --post_type=post --format=ids | xargs -I{} wp post update {} --post_content="$(wp post get {} --field=post_content)"

ステップ5:ブロックのビルド環境を整える

# @wordpress/scripts でビルド環境を構築
npm install --save-dev @wordpress/scripts

# package.json に scripts を追加
# {
#   "scripts": {
#     "build": "wp-scripts build",
#     "start": "wp-scripts start",
#     "lint:js": "wp-scripts lint-js"
#   }
# }

# 開発時(ファイル監視)
npm run start

# 本番ビルド(最小化)
npm run build
// ブロックのスクリプトをデバッグする
add_action('enqueue_block_editor_assets', function(): void {
    // ブロックエディター専用スクリプトを手動エンキュー(block.json 未使用時)
    wp_enqueue_script(
        'myplugin-blocks',
        plugins_url('build/index.js', __FILE__),
        ['wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n'],
        filemtime(plugin_dir_path(__FILE__) . 'build/index.js')
    );
    wp_enqueue_style(
        'myplugin-blocks-editor',
        plugins_url('build/index.css', __FILE__),
        ['wp-edit-blocks'],
        filemtime(plugin_dir_path(__FILE__) . 'build/index.css')
    );
});

注意事項

  • save関数の出力を変更すると既存のブロックが「無効なマークアップ」エラーになります。deprecated配列に旧バージョンのsave関数を追加して後方互換性を維持してください
  • register_block_type()に渡すパスはblock.jsonが置かれているディレクトリパスです。ファイルパス(/build/block.json)ではなくディレクトリパス(/build)を渡してください

まとめ

WordPressGutenbergブロックエラーの解決は①WP_Block_Type_Registryで登録済みブロックを確認・ブラウザコンソールでJSエラーを確認・ブロックアセットのエンキュー状態を調査、②block.jsonname/apiVersion/editorScript/styleを正しく定義・register_block_type(__DIR__.'/build')でディレクトリパスを渡す・SSR必要時はrender_callbackを追加、③useBlockProps()edit/save双方で呼ぶ・RichText.Contentsaveの出力を固定・InspectorControlsでサイドバーUIを実装、④deprecated配列に旧save関数を追加して後方互換を確保・migrate関数で旧属性を新属性に変換・WP-CLIで既存投稿を一括再保存、⑤@wordpress/scriptsでビルド環境を構築・npm run startで開発時ファイル監視・wp-blocks/wp-element/wp-editorを依存配列に追加の手順で解決します。

お気軽にご相談ください

お見積りへ お問い合わせへ