2026年6月23日

2026年6月23日

WordPressのadmin_enqueue_scriptsで管理画面の自作ページだけにVue.jsを読み込む設定

WordPress の管理画面に Vue.js で作った設定画面を載せるには、admin_menu で管理ページを追加し、admin_enqueue_scripts フックで「そのページのときだけ」Vue と自作スクリプトを読み込むのが基本です。管理画面の全ページに Vue を読み込むと、表示が遅くなるだけでなく、他のプラグインとの衝突の原因にもなります。この記事では、フックの引数でページを絞り込む方法、Vue 本体の置き方、PHP から Vue へのデータの渡し方、REST API での保存、つまずきやすい点と確認方法を順に説明します。

全体の流れ

作るものは次の4つです。

  1. admin_menu で管理ページを追加し、Vue をマウントする空の <div> を出力する
  2. admin_enqueue_scripts で、そのページのときだけ Vue と自作スクリプトを読み込む
  3. PHP から、REST API の URL や翻訳済みの文言などの初期データを渡す
  4. 設定の読み書きは、権限確認付きの REST API ルートで行う

管理画面の機能はテーマを切り替えても残したいものなので、コードはテーマではなく独自プラグイン(または mu-plugins)に置きます。本番サイトにいきなり入れず、ステージング環境で動作を確かめてから反映してください。PHP のエラーで管理画面に入れなくなった場合でも、FTP などでプラグインのフォルダー名を変えれば無効化できます。

wp-content/plugins/my-vue-admin/
├── my-vue-admin.php
└── assets/
    ├── vue.global.prod.js
    └── admin-app.js

管理ページを追加する

まず管理ページを追加します。表示用のコールバックでは、Vue のマウント先になる要素だけを出力します。ID は app のような一般的な名前を避け、プラグイン名の接頭辞を付けておくと他の機能とぶつかりません。

<?php
/**
 * Plugin Name: My Vue Admin
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

add_action( 'admin_menu', function () {
	add_menu_page(
		'My Vue 設定',
		'My Vue 設定',
		'manage_options',
		'my-vue-admin',
		'my_vue_admin_render_page',
		'dashicons-admin-generic'
	);
} );

function my_vue_admin_render_page() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}
	echo '<div class="wrap"><h1>' . esc_html__( 'My Vue 設定', 'my-vue-admin' ) . '</h1>';
	echo '<div id="my-vue-admin-app"></div></div>';
}

admin_enqueue_scripts でページを絞り込む

admin_enqueue_scripts のコールバックには、現在の管理ページを表す $hook_suffix が渡されます。add_menu_page() で追加したトップレベルのページなら toplevel_page_{メニュースラッグ}、add_submenu_page() で設定メニューの下に追加したページなら settings_page_{メニュースラッグ} のような値になります。この値と比べて、自分のページ以外では何もしないようにします。

add_action( 'admin_enqueue_scripts', function ( $hook_suffix ) {
	if ( 'toplevel_page_my-vue-admin' !== $hook_suffix ) {
		return;
	}

	$base = plugin_dir_url( __FILE__ ) . 'assets/';

	wp_enqueue_script(
		'my-vue-admin-vue',
		$base . 'vue.global.prod.js',
		array(),
		'3',
		array( 'in_footer' => true )
	);

	wp_enqueue_script(
		'my-vue-admin-app',
		$base . 'admin-app.js',
		array( 'my-vue-admin-vue', 'wp-api-fetch' ),
		'1.0.0',
		array( 'in_footer' => true )
	);

	wp_add_inline_script(
		'my-vue-admin-app',
		'window.myVueAdmin = ' . wp_json_encode( array(
			'labels' => array(
				'save'   => __( '保存', 'my-vue-admin' ),
				'saved'  => __( '保存しました。', 'my-vue-admin' ),
				'failed' => __( '保存に失敗しました。', 'my-vue-admin' ),
			),
		) ) . ';',
		'before'
	);
} );

自分のページの $hook_suffix が分からないときは、一時的に error_log( $hook_suffix ); を入れて debug.log で確認できます(確認後はこの行を削除します)。親メニューの位置やメニュー名の翻訳によって値が想定と異なる場合があるため、思い込みで書かずに実際の値を確かめてください。

wp_enqueue_script() の第5引数に配列を渡す書き方(in_footer や strategy)は WordPress 6.3 で入ったもので、現在はこちらが主流です(2026年10月現在)。古いバージョンでは真偽値の true を渡すとフッターで読み込まれます。

Vue 本体の置き方

WordPress のコアには Vue が含まれていないため、自分で用意します。ここでは npm でインストールした vue パッケージに含まれる dist/vue.global.prod.js をプラグイン内にコピーして使う前提にしています。

CDN から直接読み込む方法もありますが、管理画面のスクリプトが外部サーバーに依存すると、そのサーバーの障害や改ざんの影響を受けます。また WordPress.org のプラグインディレクトリで公開する場合は、外部から実行コードを読み込むことが原則として認められていないので、ガイドラインを確認してください。ファイルをプラグインに同梱しておけば、オフラインの開発環境でも動きます。

ビルドの種類にも注意が要ります。

  • **vue.global.prod.js(フルビルド)**:テンプレートを実行時にコンパイルする機能を含むので、下の例のように template に文字列を書く形で使える
  • **vue.runtime.global.prod.js(ランタイムのみ)**:軽い代わりに、テンプレート文字列を書くと動かない。Vite などで単一ファイルコンポーネントを事前にビルドする場合向け

Vite でビルドした成果物が ES モジュール形式なら、type="module" で読み込む必要があります。WordPress 6.5 以降には wp_enqueue_script_module() という ES モジュール用の関数もありますが、従来の wp_enqueue_script() で読み込んだスクリプト(wp-api-fetch など)との依存関係の扱いが異なります。まずは IIFE 形式で出力して wp_enqueue_script() で読み込む方が、つまずきは少なくなります。

Vue アプリ側のコード

admin-app.js では、PHP から渡したデータを読み、wp.apiFetch で REST API を呼び出します。依存関係に wp-api-fetch を指定すると、管理画面ではログイン中ユーザーの REST 用 nonce を自動で付けてくれるため、nonce を手で渡す必要がありません。

( function () {
	const { createApp, ref, onMounted } = Vue;
	const config = window.myVueAdmin || { labels: {} };

	createApp( {
		setup() {
			const message = ref( '' );
			const saving = ref( false );
			const notice = ref( '' );

			onMounted( async () => {
				const res = await wp.apiFetch( { path: '/my-vue-admin/v1/settings' } );
				message.value = res.message;
			} );

			async function save() {
				saving.value = true;
				notice.value = '';
				try {
					const res = await wp.apiFetch( {
						path: '/my-vue-admin/v1/settings',
						method: 'POST',
						data: { message: message.value },
					} );
					message.value = res.message;
					notice.value = config.labels.saved;
				} catch ( e ) {
					notice.value = config.labels.failed;
				} finally {
					saving.value = false;
				}
			}

			return { message, saving, notice, save, labels: config.labels };
		},
		template: `
			<div>
				<p><input type="text" class="regular-text" v-model="message"></p>
				<p><button type="button" class="button button-primary" :disabled="saving" @click="save">{{ labels.save }}</button></p>
				<p v-if="notice" role="status">{{ notice }}</p>
			</div>
		`,
	} ).mount( '#my-vue-admin-app' );
} )();

Vue の {{ }} は値を自動でエスケープして表示します。一方 v-html はエスケープせずに HTML として挿入するので、ユーザーが入力した値や外部から取得した値には使わないでください。ボタンや入力欄には管理画面標準の button や regular-text クラスを付けておくと、追加の CSS なしで WordPress らしい見た目になります。

保存用の REST API ルート

設定の読み書きは、権限確認とサニタイズを入れた REST API ルートで受けます。permission_callback で manage_options を確認するので、管理者以外は読み書きできません。

add_action( 'rest_api_init', function () {
	register_rest_route( 'my-vue-admin/v1', '/settings', array(
		array(
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => function () {
				return array( 'message' => (string) get_option( 'my_vue_admin_message', '' ) );
			},
			'permission_callback' => function () {
				return current_user_can( 'manage_options' );
			},
		),
		array(
			'methods'             => WP_REST_Server::CREATABLE,
			'callback'            => function ( WP_REST_Request $request ) {
				update_option( 'my_vue_admin_message', $request->get_param( 'message' ) );
				return array( 'message' => (string) get_option( 'my_vue_admin_message', '' ) );
			},
			'permission_callback' => function () {
				return current_user_can( 'manage_options' );
			},
			'args'                => array(
				'message' => array(
					'type'              => 'string',
					'required'          => true,
					'sanitize_callback' => 'sanitize_text_field',
				),
			),
		),
	) );
} );

args に sanitize_callback を書いておくと、コールバックに届く前に値が整えられます。画面側の Vue で入力チェックをしていても、REST API は直接呼び出せるため、サーバー側の検証は省略できません。

つまずきやすい点と確認方法

  • **Vue is not defined と表示される**:自作スクリプトの依存関係に Vue のハンドルが入っていないか、Vue のファイルの URL が間違っています。開発者ツールの「ネットワーク」で vue.global.prod.js が 200 で読み込まれているか確認します。
  • 画面に何も表示されない:マウント先の ID が PHP と JS で一致しているか、ランタイムのみのビルドでテンプレート文字列を使っていないかを確認します。コンソールの警告にヒントが出ることが多くあります。
  • 他の管理ページの表示が崩れた:$hook_suffix の条件が効いておらず、全ページで読み込まれている可能性があります。別の管理ページを開き、ネットワークに自作スクリプトが出ていないことを確かめます。
  • 保存すると 401 や 403 になる:wp-api-fetch を依存関係に入れているか、ログイン中のユーザーに manage_options の権限があるかを確認します。
  • 管理画面の CSS と見た目がぶつかる:自作の CSS はマウント先の ID を起点に書き、button や input 全体を指定するような広いセレクターは避けます。

まとめ

  • 管理画面に Vue を使うときは、admin_enqueue_scripts に渡される $hook_suffix で自分のページだけに読み込む
  • Vue 本体はプラグインに同梱し、テンプレート文字列を使うならフルビルド、事前ビルドならランタイムのみを選ぶ
  • 初期データは wp_add_inline_script() で渡し、REST API の呼び出しは wp-api-fetch に任せると nonce の扱いが簡単になる
  • 保存は permission_callback と sanitize_callback を備えた REST API ルートで受け、表示は {{ }} に任せて v-html を避ける
  • 動かないときは、ネットワークでの読み込み状況、マウント先の ID、ビルドの種類、権限の順に確認する

お気軽にご相談ください

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