wordpress-block-editor-fse について
WordPress のテーマやブロック開発者向けに、フルサイト編集を解説するガイドです。ブロックテーマの構成、サイトエディターでのカスタマイズ、独自ブロック、移行やアクセシビリティの実践を扱います。
主な機能
- 01ブロックテーマのファイルと構成
- 02theme.json によるデザイン設定
- 03サイトエディターでのテンプレート編集
- 04独自ブロックの開発
- 05クラシックテーマからの移行
- 06パフォーマンスとアクセシビリティの実践
活用シーン
- 01新しいブロックテーマの構築
- 02サイトエディターでのテンプレート調整
- 03サーバーサイドまたは React ブロックの開発
- 04クラシックテーマの FSE への移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill wordpress-block-editor-fseインストール後、Claude Code で/wordpress-block-editor-fseを入力して読み込まれたか確認します。
同梱ファイルmetadata.json
詳細
WordPress ブロックエディターとフルサイト編集
概要
フルサイト編集(FSE)は本番環境で使用できる状態にあり(WP 6.2 以降)、コンテンツだけでなく、ヘッダー、フッター、テンプレートなど、すべてをブロックとして扱います。ブロックテーマでは、PHP ファイルと style.css の代わりに、HTML テンプレートと theme.json を使用します。
主なコンポーネント:
- theme.json:色、タイポグラフィ、余白、レイアウトを一元管理します
- HTML テンプレート:ブロックベースのファイル(index.html、single.html)
- テンプレートパーツ:再利用可能なコンポーネント(header.html、footer.html)
- ブロックパターン:あらかじめデザインされたブロックレイアウト
- サイトエディター:テンプレートを視覚的にカスタマイズします
使用する場面: ✅ 新しいテーマ、一貫したデザインシステム、技術に詳しくないユーザーによるカスタマイズ ❌ 複雑なサーバーロジック、ブロックに不慣れなチーム、多数の PHP 依存関係
フルサイト編集のアーキテクチャ
ブロックテーマとクラシックテーマの比較
| ブロックテーマ | クラシックテーマ |
|---|---|
| ブロックを含む HTML ファイル | テンプレートタグを含む PHP ファイル |
| theme.json + CSS | functions.php + style.css |
| サイトエディター(ビジュアル) | カスタマイザー(設定) |
| ユーザーがテンプレートを編集 | カスタマイズは限定的 |
サイトエディターの機能
- テンプレート編集(固定ページ、投稿、アーカイブ)
- テンプレートパーツ(ヘッダー/フッターのバリエーション)
- グローバルスタイル(サイト全体の色、タイポグラフィ)
- パターンライブラリ(ブロックの構成を保存/再利用)
- ナビゲーションメニュー(ブロックベース)
- スタイルバリエーション(別のデザインプリセット)
theme.json の設定
theme.json v3(WP 6.7)では、デザインを一元的に制御できます。WordPress は CSS カスタムプロパティを自動生成します。
本番環境向けの例
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"appearanceTools": true,
"useRootPaddingAwareAlignments": true,
"layout": {
"contentSize": "800px",
"wideSize": "1200px"
},
"color": {
"palette": [
{ "slug": "primary", "color": "#0073aa", "name": "Primary" },
{ "slug": "secondary", "color": "#005177", "name": "Secondary" },
{ "slug": "base", "color": "#ffffff", "name": "Base" },
{ "slug": "contrast", "color": "#000000", "name": "Contrast" }
],
"defaultPalette": false,
"defaultGradients": false
},
"typography": {
"fontFamilies": [
{
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
"slug": "system",
"name": "System Font"
}
],
"fontSizes": [
{ "slug": "small", "size": "0.875rem", "name": "Small" },
{ "slug": "medium", "size": "1rem", "name": "Medium" },
{
"slug": "large",
"size": "1.5rem",
"name": "Large",
"fluid": { "min": "1.25rem", "max": "1.5rem" }
}
],
"fontWeight": true,
"lineHeight": true
},
"spacing": {
"units": ["px", "em", "rem", "vh", "vw", "%"],
"padding": true,
"margin": true,
"spacingSizes": [
{ "slug": "30", "size": "0.5rem", "name": "XS" },
{ "slug": "40", "size": "1rem", "name": "S" },
{ "slug": "50", "size": "1.5rem", "name": "M" },
{ "slug": "60", "size": "2rem", "name": "L" }
]
},
"border": { "radius": true, "color": true, "width": true }
},
"styles": {
"color": {
"background": "var(--wp--preset--color--base)",
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontSize": "var(--wp--preset--font-size--medium)",
"lineHeight": "1.6"
},
"elements": {
"link": {
"color": { "text": "var(--wp--preset--color--primary)" },
":hover": {
"color": { "text": "var(--wp--preset--color--secondary)" }
}
},
"h1": {
"typography": {
"fontSize": "var(--wp--preset--font-size--large)",
"fontWeight": "700"
}
},
"button": {
"color": {
"background": "var(--wp--preset--color--primary)",
"text": "var(--wp--preset--color--base)"
},
"border": { "radius": "4px" },
":hover": {
"color": { "background": "var(--wp--preset--color--secondary)" }
}
}
},
"blocks": {
"core/quote": {
"border": {
"width": "0 0 0 4px",
"color": "var(--wp--preset--color--primary)"
},
"spacing": { "padding": { "left": "var(--wp--preset--spacing--60)" } }
}
}
},
"customTemplates": [
{
"name": "page-wide",
"title": "Full Width Page",
"postTypes": ["page"]
}
]
}
自動生成される CSS カスタムプロパティ
- 色:
var(--wp--preset--color--primary) - フォント:
var(--wp--preset--font-family--system) - サイズ:
var(--wp--preset--font-size--large) - 余白:
var(--wp--preset--spacing--50)
流動的なタイポグラフィ
fluid: { min, max } を指定したフォントサイズは、clamp() を使って自動的に拡縮されます。
{
"slug": "large",
"size": "1.5rem",
"fluid": { "min": "1.25rem", "max": "1.5rem" }
}
ブロックテーマのアーキテクチャ
必須ファイル
my-block-theme/
├── style.css # Theme metadata (REQUIRED)
├── theme.json # Settings/styles (REQUIRED)
├── templates/
│ ├── index.html # Fallback (REQUIRED)
│ ├── single.html
│ ├── page.html
│ └── archive.html
├── parts/
│ ├── header.html
│ └── footer.html
├── patterns/ # Block patterns
│ └── hero.php
└── functions.php # Optional setup
style.css のメタデータ
/*
Theme Name: My Block Theme
Requires at least: 6.4
Requires PHP: 8.1
Version: 1.0.0
*/
HTML テンプレートの構造
templates/single.html:
<div class="wp-block-group">
</div>
templates/index.html(クエリーループを含む):
<main class="wp-block-group">
<div class="wp-block-query">
</div>
</main>
テンプレートパーツ
parts/header.html:
<div class="wp-block-group">
</div>
ブロックパターン
patterns/hero.php:
<?php
/**
* Title: Hero Section
* Slug: my-theme/hero
* Categories: featured
*/
?>
<div class="wp-block-cover alignfull">
<div class="wp-block-cover__inner-container">
<h1>Welcome to Our Site</h1>
<div class="wp-block-buttons">
<div class="wp-block-button"><a class="wp-block-button__link">Get Started</a></div>
</div>
</div>
</div>
パターンカテゴリーの登録:
add_action('init', 'register_pattern_categories');
function register_pattern_categories() {
register_block_pattern_category('hero', [
'label' => __('Hero Sections', 'my-theme')
]);
register_block_pattern_category('cta', [
'label' => __('Call to Action', 'my-theme')
]);
}
カスタムブロックの開発
block.json メタデータ(Block API v3)
blocks/testimonial/block.json:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-theme/testimonial",
"title": "Testimonial",
"category": "widgets",
"icon": "format-quote",
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": ".testimonial-content"
},
"author": { "type": "string", "default": "" },
"role": { "type": "string", "default": "" },
"rating": { "type": "number", "default": 5 }
},
"supports": {
"html": false,
"align": ["wide", "full"],
"color": { "background": true, "text": true },
"spacing": { "padding": true, "margin": true }
},
"render": "file:./render.php"
}
属性のソース
HTML からデータを抽出するさまざまな方法:
"attributes": {
"title": {
"type": "string",
"source": "html",
"selector": "h2"
},
"linkUrl": {
"type": "string",
"source": "attribute",
"selector": "a",
"attribute": "href"
},
"isActive": {
"type": "boolean",
"default": false
},
"items": {
"type": "array",
"source": "query",
"selector": ".item",
"query": {
"text": { "type": "string", "source": "text" }
}
}
}
サーバーサイドレンダリング(render.php)
blocks/testimonial/render.php:
<?php
$content = $attributes['content'] ?? '';
$author = $attributes['author'] ?? '';
$role = $attributes['role'] ?? '';
$rating = absint($attributes['rating'] ?? 5);
$wrapper_attributes = get_block_wrapper_attributes([
'class' => 'testimonial-block',
]);
?>
<div <?php echo $wrapper_attributes; ?>>
<blockquote class="testimonial-content">
<?php echo wp_kses_post($content); ?>
</blockquote>
<?php if ($rating > 0) : ?>
<div class="testimonial-rating">
<?php for ($i = 1; $i <= 5; $i++) : ?>
<span class="star <?php echo $i <= $rating ? 'filled' : 'empty'; ?>">
<?php echo $i <= $rating ? '★' : '☆'; ?>
</span>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php if ($author || $role) : ?>
<cite class="testimonial-author">
<span class="author-name"><?php echo esc_html($author); ?></span>
<?php if ($role) : ?>
<span class="author-role"><?php echo esc_html($role); ?></span>
<?php endif; ?>
</cite>
<?php endif; ?>
</div>
クライアントサイドレンダリング(React)
blocks/testimonial/index.js:
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, RichText, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
registerBlockType('my-theme/testimonial', {
edit: ({ attributes, setAttributes }) => {
const { content, author, role, rating } = attributes;
const blockProps = useBlockProps();
return (
<>
<InspectorControls>
<PanelBody title={__('Settings', 'my-theme')}>
<TextControl
label={__('Author', 'my-theme')}
value={author}
onChange={(v) => setAttributes({ author: v })}
/>
<TextControl
label={__('Role', 'my-theme')}
value={role}
onChange={(v) => setAttributes({ role: v })}
/>
<RangeControl
label={__('Rating', 'my-theme')}
value={rating}
onChange={(v) => setAttributes({ rating: v })}
min={1}
max={5}
/>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
<RichText
tagName="blockquote"
value={content}
onChange={(v) => setAttributes({ content: v })}
placeholder={__('Testimonial text...', 'my-theme')}
/>
<div className="testimonial-rating">
{[1, 2, 3, 4, 5].map((star) => (
<span
key={star}
onClick={() => setAttributes({ rating: star })}
>
{star <= rating ? '★' : '☆'}
</span>
))}
</div>
<cite>
<RichText
tagName="span"
value={author}
onChange={(v) => setAttributes({ author: v })}
placeholder={__('Author', 'my-theme')}
/>
</cite>
</div>
</>
);
},
save: () => null, // Server-side rendering
});
ブロックの登録
functions.php:
add_action('init', 'register_custom_blocks');
function register_custom_blocks() {
register_block_type(__DIR__ . '/blocks/testimonial');
}
InspectorControls(設定サイドバー)
ブロック設定でよく使われるコントロール:
import {
InspectorControls,
PanelColorSettings,
MediaUpload
} from '@wordpress/block-editor';
import {
PanelBody,
SelectControl,
ToggleControl,
RangeControl,
Button
} from '@wordpress/components';
<InspectorControls>
<PanelBody title="Layout">
<SelectControl
label="Columns"
value={columns}
options={[
{ label: '2', value: 2 },
{ label: '3', value: 3 },
{ label: '4', value: 4 }
]}
onChange={(v) => setAttributes({ columns: parseInt(v) })}
/>
<ToggleControl
label="Enable Shadow"
checked={enableShadow}
onChange={(v) => setAttributes({ enableShadow: v })}
/>
<RangeControl
label="Border Radius"
value={borderRadius}
onChange={(v) => setAttributes({ borderRadius: v })}
min={0}
max={50}
/>
</PanelBody>
<PanelBody title="Media">
<MediaUpload
onSelect={(media) => setAttributes({ imageUrl: media.url })}
allowedTypes={['image']}
render={({ open }) => (
<Button onClick={open} variant="secondary">
{imageUrl ? 'Change Image' : 'Select Image'}
</Button>
)}
/>
</PanelBody>
<PanelColorSettings
title="Colors"
colorSettings={[
{
value: bgColor,
onChange: (v) => setAttributes({ bgColor: v }),
label: 'Background'
}
]}
/>
</InspectorControls>
ブロックのサポート機能
WordPress の機能を有効にします:
"supports": {
"html": false,
"anchor": true,
"align": ["wide", "full"],
"color": {
"background": true,
"text": true,
"gradients": true
},
"spacing": {
"padding": true,
"margin": true,
"blockGap": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"fontWeight": true
}
}
ブロックエディターを使ったカスタム投稿タイプ
add_action('init', 'register_book_cpt');
function register_book_cpt() {
register_post_type('book', [
'labels' => [
'name' => __('Books', 'my-theme'),
'singular_name' => __('Book', 'my-theme'),
],
'public' => true,
'has_archive' => true,
'supports' => ['title', 'editor', 'thumbnail'],
'show_in_rest' => true, // REQUIRED for block editor
'menu_icon' => 'dashicons-book',
'template' => [ // Default blocks
['core/paragraph', ['placeholder' => 'Book description...']],
['core/image'],
['my-theme/book-details'],
],
'template_lock' => 'insert', // Can't add/remove blocks
]);
// Register taxonomy
register_taxonomy('genre', 'book', [
'labels' => ['name' => __('Genres', 'my-theme')],
'hierarchical' => true,
'show_in_rest' => true, // REQUIRED
]);
}
テンプレートのロック
false:制限なし'all':構造を変更できません'insert':追加/削除はできませんが、並べ替えはできます'contentOnly':コンテンツのみ編集できます
theme.json での登録
"customTemplates": [
{
"name": "single-book",
"title": "Book Template",
"postTypes": ["book"]
}
]
開発ワークフロー
@wordpress/scripts
package.json:
{
"scripts": {
"start": "wp-scripts start",
"build": "wp-scripts build"
},
"devDependencies": {
"@wordpress/scripts": "^27.0.0"
}
}
コマンド:
npm install
npm run start # Development with hot reload
npm run build # Production build (minified)
wp-env のセットアップ
.wp-env.json:
{
"core": "WordPress/WordPress#6.7",
"phpVersion": "8.3",
"themes": ["./my-block-theme"],
"config": {
"WP_DEBUG": true,
"SCRIPT_DEBUG": true
}
}
使用方法:
npx @wordpress/env start
# Access: http://localhost:8888
# Admin: admin / password
npx @wordpress/env stop
npx @wordpress/env clean # Reset database
クラシックテーマからの移行
テンプレートタグとブロックの対応
| クラシック | 対応するブロック |
|---|---|
the_title() | `` |
the_content() | `` |
the_post_thumbnail() | `` |
the_date() | `` |
wp_nav_menu() | `` |
get_header() | `` |
get_footer() | `` |
get_sidebar() | `` |
移行手順
- style.css からデザイントークンを抽出し、theme.json に移します
- PHP テンプレートを変換し、HTML ブロックテンプレートにします
- functions.php でブロックサポートを追加します:
add_theme_support('wp-block-styles');
add_theme_support('align-wide');
add_theme_support('responsive-embeds');
- 実際のコンテンツを使って十分にテストします
ブロックの検証
WordPress は、ブロックのマークアップを登録済みのブロック定義と照合して検証します。無効なブロックはエディターにエラーを表示します。
よくある検証エラー:
- 属性の型が一致しない(文字列と数値)
- 必須属性がない
- HTML 構造が正しくない
- 属性名が変更されている
検証エラーの修正:
// Add deprecated versions for backward compatibility
const deprecated = [
{
attributes: {
oldName: { type: 'string' }
},
migrate: (attributes) => ({
newName: attributes.oldName
}),
save: (props) => {
// Old save function
}
}
];
パフォーマンスとベストプラクティス
パフォーマンス
✅ 可能な場合はサーバーサイドレンダリング(render.php)を使用します
✅ ブロックのサポート機能を活用します(カスタム CSS を減らせます)
✅ 使用しない機能を無効にします:"defaultPalette": false
✅ 一貫性を保つために CSS カスタムプロパティを使用します
❌ 静的コンテンツにクライアントサイドレンダリングを使用しないでください
❌ !important でコアブロックを上書きしないでください
アクセシビリティ
✅ セマンティック HTML(<header>、<main>、<footer>)
✅ カスタムブロックでキーボード操作に対応します
✅ WCAG AA の色のコントラスト(最低 4.5:1)
✅ すべての画像に代替テキストを設定します
❌ FSE ならアクセシブルだと決めつけないでください(テストが必要です)
アンチパターン
❌ クラシックな手法とブロックの手法を混在させる
❌ 色をハードコードする(CSS 変数を使用します)
❌ ブロックのサポート機能を独自に作り直す
❌ アクセシビリティテストを省略する
❌ HTML テンプレートで get_header() を使用する
関連スキル
- wordpress-plugin-fundamentals:フックシステム、CPT
- react:ブロックエディターのコンポーネント
- typescript:型安全なブロック開発
- php-security:ブロック属性のサニタイズ
重要な注意事項
- ブロックテーマでは theme.json が必須です
- FSE では PHP が HTML テンプレートに置き換わります
- サーバーサイドレンダリングは、多くの場合クライアントサイドより適しています
- ブロックのサポート機能によりカスタムコードを減らせます
- アクセシビリティにはテストが必要です
注意すべき兆候
- CSS ファイルが 5 個を超える → theme.json を使用します
- HTML テンプレートに PHP タグがある → ブロックを使用します
- 静的コンテンツにクライアントサイドレンダリングを使用している → render.php を使用します
- キーボード操作のテストがない → アクセシビリティの問題があります
- 値がハードコードされている → CSS カスタムプロパティを使用します
WordPress: 6.7+ | PHP: 8.1+ | ツール: @wordpress/scripts, wp-env