はじめに ─ メジャーバージョン更新を慎重に進めてきた案件で
対象案件は、長期にわたって同じ構成で運用してきた WordPress の EC サイトです。商品コンテンツが 終了時刻を持つ性質(在庫が時間で消える)を持ち、有料プラグイン(UAP / Ultimate WP Auction)、独自子テーマ(Flatsome ベース + 7 ファイルの WC 上書きテンプレート)、WPML による多言語対応(日本語 / 英語)を抱えています。一般的な EC とは異なり、オークションのステータス(現在の入札額・残り時間・終了状態)を正確に表示する必要があるため、商品関連ページは Kinsta page cache から除外する ことが運用上の必須条件です。
メジャーバージョン更新に慎重姿勢を取ってきたのは、この組み合わせの互換性検証コストと独自実装の表示崩れリスクが大きかったためです。WC のメジャー版を上げると、子テーマの上書きテンプレートが古いコア前提のまま残って表示が崩れる、UAP の挙動が変わる、WCML が崩れる ── これらを一度に検証する負荷が、得られる効果に対して大きすぎる状態が続いていました。結果として、WC は 4.5.5(2020 年リリース)、UAP は 2.3.2 で長らく止まっていました。
これを Claude Code に SSH 経由で WP-CLI を叩いてもらう運用設計 に切り替えて、WooCommerce 4.5.5 → 10.0.4(6 メジャー版分)を段階的に進めたのが本記事の主題です。Claude Code 単体の話ではなく、Kinsta Staging のスナップショット前提 + CLAUDE.md / docs/tasks/ で Phase 状態を持たせる分業 + 人間が最終承認する境界線、を組み合わせた運用設計の話として書いています。
対象読者は、長期運用の WordPress 案件のメジャーバージョン更新に頭を悩ませている制作・運用担当者と、運用案件に AI を具体的にどう通すかの型を探しているエンジニアです。
1. WordPress のメジャーバージョン更新が止まりがちな構造
WordPress 案件のメジャーバージョン更新が止まりがちになる構造は、技術・経済合理性・組織の三層に分けて整理できます。本章ではその構造を順に開示します。
1-1. 「触らないのが正解」の積み重ねが生むもの
WP・プラグイン・テーマの更新を止めていても、サイトは見かけ上は動き続けます。「動いているなら触らない」が経験的に最も事故率が低く、最初は合理的な判断です。
しかし時間が経つにつれて、以下が静かに積み上がります:PHP のメジャー更新で deprecation 警告が増える(PHP 7.x → 8.x で多数)、WC / プラグインのセキュリティ更新で残された脆弱性が増える、古い WC 同梱版と新規プラグインのインターフェースが微妙にズレて表示の細部が崩れる、商品数・カテゴリ数が増えるにつれて古いコードの非効率パターンが顕在化する。「触らない」を選び続けても、外側の WP コア / PHP / プラグインエコシステムは進み続けるので、放っておいても「現状維持」が成立しないのが WordPress 運用の特徴です。
1-2. 検証コスト > 効果 と感じる構造
メジャーバージョン更新を止める判断は、検証コストの体感が効果を上回る瞬間に出てきます。WP の場合、典型的には以下が積み重なります:
- 有料プラグインの互換性確認 ── ベンダーが必ずしも最新 WC を即サポートしないため、ユーザー側で「どの組み合わせが大丈夫か」を当てる必要がある
- 独自子テーマの動作確認 ── 上書きテンプレートはコア更新ごとに drift する。差分検証は熟練者でないと正確に追えない
- UAT(動作確認)の範囲が広い ── EC サイトなら商品一覧・詳細・カート・チェックアウト・マイページ・管理画面・メール送信まで、一通り通す必要がある
これらを「メジャー版を 1 つ上げるたび」「マイナー版を上げるたび」に通すのは現実的に重く、結果として 「全部一気にやる、ただしいつかやる」 が常態化します。「いつか」はだいたい遠ざかります。
1-3. 一人で抱えるとどこで判断が止まるか
組織的な要因も無視できません。WP 運用案件は単一担当が抱える構造になりやすく、これがアップグレード判断を止める原因にもなります:バージョンアップ計画のレビュー相手がいない、失敗時のロールバック手順を相談する相手がいない、進捗のチェックポイントが共有されない、といった「壁打ち相手の不在」が意思決定の停滞を生みます。
技術・経済・組織の 3 層が重なって、メジャーバージョン更新は構造的に止まりがちになります。逆に言えば、この 3 層に対して具体的なアプローチを当てれば「止まらない」運用に近づきます。次章でその具体策を整理します。
2. Claude Code を SSH 経由で WordPress に通すと何が変わるか
1 章で見た 3 層の構造的問題に対して、Claude Code を SSH 経由で WordPress に通すと何が変わるかを 3 観点で整理します。
2-1. WP-CLI を Claude Code が直接叩ける
WordPress の運用作業は伝統的に管理画面(WP Admin)のクリック操作で進めるのが標準ですが、これには 2 つの問題がありました ── 操作の履歴がほぼ残らない(「いつ・誰が・どのプラグインのどの設定を変えたか」がブラックボックス)、操作の自動化が難しい(同じ作業を 10 サイトで繰り返すと労力が比例して増える)。
WP-CLI は WordPress のサーバ側コマンドラインで、これらの両方を解消します。Claude Code に SSH 経由で WP-CLI を叩いてもらうと、「管理画面でぽちぽちクリック」が「コマンドで一回」に変わります。
$ wp plugin update woocommerce --version=7.2.1
$ wp wc tool run db_update_routine
$ wp transient delete --all実際に本記事の WooCommerce 4.5.5 → 10.0.4(6 メジャー版分)のうち、プラグイン更新コマンドは全て CLI 経由でした。
2-2. 作業ログと diff が自然に残る
SSH/CLI ベースで作業を進めると、以下が自然と残ります:ターミナルのスクロールバックに「何を実行したか」が完全に残る、ファイルを書き換えた場合は git diff で「何を変えたか」が見える、失敗したコマンドもその出力(エラーメッセージ)とともに残る。
WP 業界では長年「変更履歴が残らない」が運用上の標準でしたが、SSH + WP-CLI + Git で運用すれば、調査の起点(「あの設定はいつ・なぜ・誰が変えた?」)が常に手元にある状態を作れます。Claude Code のセッションログとも組み合わさるので、人間 + AI の協業ログとしても追跡可能です。
2-3. Phase 設計でステップを刻める
最大の変化は 「全部一気にやる」から「Phase 単位で段階的に進める」 に運用思想を変えられることです。伝統的なやり方は「検証環境で全更新を一気に → 大きな統合テスト → 動いたら本番、動かなかったら全部戻す」ですが、Phase 設計は「Phase 0(準備)、Phase 1(プラグイン X だけ上げる)、Phase 2(WC を 1 メジャー版だけ)...」とステップを刻み、各 Phase の前後に Kinsta スナップショットを取って戻せる前提を作ります。
Phase ごとの状態は docs/tasks/<task>.md にチェックリストで記録します。Claude Code は次回セッションでもそのファイルを読めば「ここまで終わっている、次は Phase X」を即座に把握できるので、長期作業を細切れに進められます。1-3 で書いた「一人で抱える」のレビュー相手不在問題も、Phase ファイルとセッションログを介して Claude Code が一定の役を果たします。
3. Claude Code に WordPress を任せるための 3 点セット + Kinsta Staging 前提
ここからは、Claude Code を WordPress 運用に通すための具体的な構成要素を整理します。3 点セット(CLAUDE.md / docs/tasks/ / 自動メモリ)と、それを支える前提条件(Kinsta Staging)の組み合わせです。
3-1. CLAUDE.md ─ プロジェクト前提・固定フロー・禁止事項
CLAUDE.md は Claude Code がセッション開始時に必ず読み込むファイルで、プロジェクトのルートに置きます。「このプロジェクトでは何を前提に作業するか」を毎回ゼロから説明し直さなくて済むようになるのが最大の効果です。
- プロジェクト前提(WP / WC のバージョン、Kinsta 構成、PHP バージョン、利用プラグイン一覧)
- 本番反映フロー(Files-only Push、ダッシュボード操作、人間最終承認)
- 禁止事項(本番への直接 SSH を Claude Code に許可しない、wp-config.php の直接編集禁止 等)
- メール文体・クライアント連絡の方針
- 緊急時手順(ロールバック、サポートエスカレーション先)
本案件では、CLAUDE.md に「Kinsta Staging までのアクセスのみ許可」「本番反映は Files-only Push を人間が手動実行」「mu-plugin の本番反映前退避手順」などを固定化しています。
3-2. docs/tasks/<task>.md ─ Phase + チェックボックスで進捗を持たせる
長期の作業は docs/tasks/ 配下にマークダウンファイルを作り、Phase ごとのチェックリストとして状態を持たせます。本案件の WooCommerce アップグレードは docs/tasks/woocommerce-uap-upgrade.md として、Phase 0 〜 Phase 6 の各タスクをチェックボックスで管理しました。
このファイルがあると:(1)次回のセッションで Claude Code が「いま Phase 3 まで完了、次は Phase 5」を即座に把握できる、(2)進捗を中断・再開しても状態が消えない、(3)1 人運用でも「過去の自分」がレビュー相手の役を果たす(レビュー対象は前回 Phase の完了基準)。長期作業の「忘却によるやり直し」を構造的に消せます。
3-3. 自動メモリ ─ 協業作法を蓄積
Claude Code には自動メモリ機能があり、ユーザーごとに「協業作法」を蓄積していけます。たとえば「ステージング前提で作業する、本番には直接触らない」「日本語で応答」「リスクの高い操作の前は必ず確認を取る」「コミットメッセージのスタイル」などを書き溜めると、毎セッションこれらを指示しなくても自動で意識される状態になります。運用が長期化しても文脈がぶれません。
3-4. Kinsta Staging との組み合わせ前提 ─ 壊しても戻せる
3 点セットは Kinsta Staging という「壊しても戻せる」サンドボックスがあって初めて成立します。Kinsta Staging はワンクリックで本番のフルコピーを作れて、スナップショットで任意時点に戻せて、「Staging → Live」を Files only で実行できる(DB を本番から守る)という特徴があります。
Claude Code に SSH を許可しても怖くないのは、この「いつでも戻せる」前提があるからです。逆に言えば、戻せない環境に AI を放つ運用は本記事の型では成立しません。
4. 実例 ─ WooCommerce 4.5.5 → 10.0.4(6 メジャー版分)
本案件のメイン作業は、WooCommerce 4.5.5 と有料プラグイン UAP 2.3.2 を、それぞれ 10.0.4 / 2.4.4 まで段階的に進める工程でした。Phase を 0 〜 6 に切り、各 Phase の前後で Kinsta スナップショットを取得して「戻せる前提」を確保しています。
4-1. Phase 設計と全体の見取り図
検証コストを「全部やって動作確認」から「段階的に確認」に置き換えるため、メジャーバージョン更新は最小単位に刻みました。実際に切った Phase は次の通りです。
| Phase | 名前 | 主な作業内容 |
|---|---|---|
| 0 | 準備 | Kinsta スナップショット取得、WCML 状態確認、WP Mail SMTP 無効化、各 WC マイナー版の changelog ハイライト把握 |
| 1 | UAP 2.3.2 → 2.4.4 | WC は 4.5.5 のまま UAP のみ更新。サイト応答と WCML 動作確認 |
| 2 | WC 4.5.5 → 7.2.1 | wp plugin update woocommerce --version=7.2.1、db_update_routine と Action Scheduler を流す。子テーマの $uwa_countdown_format 未定義警告修正、上書きテンプレ 7 件と core の diff チェック、uwa-bids-history.php を UAP 2.4.4 ベースに rebase |
| 3 | WC 7.2.1 → 10.0.4 | wp plugin update woocommerce --version=10.0.4、HPOS OFF 維持、StoreApi Fatal を wp transient delete --all で解消、スモークテスト全パス |
| 4 | ─ | 当初設計に存在せず、UAP テスト範囲を 7.2.1 → 10.0.4 まで広げた経緯で番号スキップ |
| 5 | 動作確認 + 警告解消 | CLI でテストユーザー 3 名作成・入札ログ投入・入札履歴表示・落札メールブロック確認・テストデータ削除。並行して outdated template 警告を 35+ → 1 件に解消 |
| 6 | 本番反映 | Kinsta「Staging → Live」を Files only で実行、staging-mail-disabled.php を退避、反映後に本番管理画面で WC DB 更新ボタン |
Phase 4 の番号が抜けているのは、当初設計時には存在せず、UAP のテスト範囲を WC 10.0.4 まで広げた経緯でナンバリングをそのまま残したためです。実装上の意味はありません。
4-2. 子テーマ 7 ファイルの diff 移植
Flatsome 子テーマ(flatsome-child)の woocommerce/ 配下に 7 件の WooCommerce 上書きテンプレートがあり、これらが各 WC コア更新ごとに outdated template warning の発生源になっていました。Phase 5 でこれらを WC 10.0.4 / Flatsome 親 3.20.6 のコア版と diff し、必要な独自実装だけを最新版にパッチ移植する形で rebase しました。
| ファイル | 独自実装の中身 |
|---|---|
| archive-product.php | 商品アーカイブ全体(Flatsome の layouts/category に委譲) |
| content-product.php | 商品ループ内カード(オークション専用カードに書き換え) |
| layouts/category.php | カテゴリページレイアウト |
| myaccount/form-login.php | ログインフォーム(Remember Me を default checked、Flatsome wrapper 維持) |
| single-product/related.php | 関連商品(削除目的の空オーバーライド) |
| single-product/tabs/sections.php | タブ構造 |
| single-product/tabs/uwa-bids-history.php | UAP 入札履歴(自動入札列を ※ 印化) |
rebase の進め方は 「core 版から始めて、自前の divergence を最小単位でパッチする」の一本でした。たとえば content-product.php は @version 3.6.0 → @9.4.0 へ、archive-product.php は @version 3.4.0 → @8.6.0 へ、それぞれ rebase 時に docblock に divergence note を残しています。リポジトリ側に scripts/wc-template-overrides/ としてマスター版をコミットしておき、ステージング再同期で消えても再配置できる状態にしました。
4-3. WC 10.0.4 移行時の StoreApi Fatal と transient delete --all
Phase 3(WC 7.2.1 → 10.0.4)直後、商品ページや管理画面が Fatal: woocommerce-blocks/StoreApi/deprecated.php が見つからない 系のエラーで落ちました。原因は jetpack-autoloader が WP の transient に保持していた stale なクラスマップで、WC 10.0.4 のファイル構成と整合しなくなっていたためです。
WP-CLI で transient を全削除するだけで解消します。
$ wp transient delete --allこのコマンドだけで Fatal が消え、その後のスモークテストは全パスしました。WC のメジャー版更新で StoreApi / Blocks 配下の構成が変わったときは、まず transient と object cache の状態を疑うのが最短ルートです。
4-4. 互換テンプレート警告 35+ → 1 件
WooCommerce のシステムステータス(管理画面 → WooCommerce → ステータス → テンプレート)では、コア同梱版より古い構造で上書きされているテンプレートを outdated template warning として警告します。Phase 5 着手時、本案件では合計 35 件以上が出ていました。
| カテゴリ | 着手時 | 完了後 | 対処内容 |
|---|---|---|---|
flatsome-child(子テーマ) | 4 | 0 | 各ファイルを WC 10.0.4 コアと rebase、@version 更新、divergence note を docblock 化 |
flatsome(親テーマ) | 30+ | 0 | Flatsome 親テーマを 3.11.3 → 3.20.6 へ更新(ThemeForest 経由) |
booster-plus-for-woocommerce | 1 | 1 | プラグイン側に更新提供なし。子テーマフォーク以外の即時対策なし |
| 合計 | 35+ | 1 |
最終的に残った 1 件は booster-plus-for-woocommerce プラグイン同梱のテンプレートで、上流に更新が提供されていません。子テーマでフォークして rebase する選択肢はありますが、プラグイン側が将来出してくる更新と毎回衝突するリスクを抱えるため、現時点では「警告 1 件残」を許容しています。
なお同じ booster-plus-for-woocommerce は PHP 8.2 系の「Creation of dynamic property」deprecation も大量に発火させており、商品詳細 1 ページあたりで約 700 件が捕捉されます(その大半がこのプラグイン由来)。現状の機能には影響しませんが、PHP 9 で fatal 化する可能性が予告されているため、長期保守上の宿題がこのプラグインに集中している構図です。
5. 副産物 ─ パフォーマンス劣化も同時に解消できた
アップグレード作業の途中で Claude Code にコードを読み返してもらう過程で、パフォーマンス上の課題が 2 系統見えてきました。PHP 実行コスト(商品詳細ページに無駄な WP_Query が大量に走る)と、キャッシュ戦略(本来エッジで返せる静的ページまでキャッシュバイパスされる)です。アップグレードと合わせて両方を整理し直し、商品詳細 1 ページのロードで クエリ -42% / ピークメモリ -30% / PHP 生成時間 -37% を本日(2026-05-12)の再計測で確認、加えて 4 年間続いていた静的ページの不要なキャッシュバイパスも宣言的な実装に書き換えました。
5-1. 何が遅くしていたか
Claude Code に Query Monitor の出力を読ませて、商品詳細ページで走っている SQL を一つずつ見ていくと、機能上は不要な処理が 2 系統見つかりました。
- UAP のカテゴリカウントフィルタ ── 商品カテゴリごとに
WP_Query × 3(全商品マイナス 1 件の取得を 3 パターン)を発火させて在庫件数を集計する処理が、商品詳細ページでも実行されていました。商品詳細では「現在見ている 1 商品」しか必要ないのに、その商品が属する全カテゴリでカウントクエリが走る形です。 - 関連商品 / アップセル / クロスセルの空クエリ ── 子テーマで
single-product/related.phpを空オーバーライド(関連商品を表示しない設計)していたにもかかわらず、内部のWP_Queryは別経路から依然として走っていました。表示には出ないが DB は叩く、という典型的な「半分残った」パターンです。
どちらも「機能を変えずに止めて良い」処理で、合わせて商品詳細 1 ページのロードに 200 件以上の余分なクエリを発生させていました。
5-2. mu-plugin で「機能を変えずに止める」
上の 2 つを 商品詳細・固定ページに限定して抑止する mu-plugin を入れました。グローバルではなく is_singular() 条件で発火させること、プラグイン設定画面を触らずに済むこと、リポジトリで管理できることを優先しています。実コードは以下:
<?php
/**
* Plugin Name: WC Product Page Performance
* Description: 性能最適化用 mu-plugin。子テーマで非表示にしている WC のクエリを
* 未然に短絡し、UAP のカテゴリカウントフィルタを単一商品ページで切る。
* Author: MOOBON
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) exit;
// 1. 関連商品 / アップセル / クロスセル のクエリを抑止
// 子テーマで related.php を空にしているので、そもそも取得不要。
// upsell / cross-sell も使っていない前提で空配列を返す
add_filter( 'woocommerce_product_related_posts_query', function() {
return [];
}, 10, 1 );
add_filter( 'woocommerce_product_get_upsell_ids', function( $ids ) {
return [];
}, 999 );
add_filter( 'woocommerce_product_get_cross_sell_ids', function( $ids ) {
return [];
}, 999 );
add_filter( 'woocommerce_get_related_product_cat_terms', function() {
return [];
}, 10 );
add_filter( 'woocommerce_get_related_product_tag_terms', function() {
return [];
}, 10 );
// 2. UAP のカテゴリカウント書き換えフィルタを単一商品ページで無効化
// そのフィルタは category 毎に WP_Query x 3(全商品 -1 件取得)を発火させ、
// 商品詳細ページでは 30+ クエリ・100+ms を浪費している。
// 商品詳細ページのサイドバーでも category 数字は表示されるが、
// 細かい数字より体感速度を優先する判断
add_action( 'wp', function() {
if ( is_singular( 'product' ) || is_singular( 'page' ) ) {
// UAP の filter は class instance 経由なのでオブジェクト経由で remove
if ( class_exists( 'UWA_Front' ) ) {
global $wp_filter;
if ( isset( $wp_filter['get_terms'] ) ) {
foreach ( $wp_filter['get_terms']->callbacks as $priority => $callbacks ) {
foreach ( $callbacks as $id => $cb ) {
if ( is_array( $cb['function'] ) && is_object( $cb['function'][0] )
&& get_class( $cb['function'][0] ) === 'UWA_Front'
&& $cb['function'][1] === 'uwa_change_count_product_category' ) {
unset( $wp_filter['get_terms']->callbacks[$priority][$id] );
}
}
}
}
}
}
}, 1 );UAP 側のフィルタは add_filter() をクラスインスタンスメソッドで仕込んでいるため、単純な remove_filter() では消えません。$wp_filter をスキャンしてクラス名と method 名で照合する古典的アプローチで剥がしています(プラグインの内部実装に依存する手法なので、UAP の更新で動かなくなる可能性は承知の上)。
配置先は wp-content/mu-plugins/wc-product-page-perf.php(staging-mail-disabled.php と同じ理由 ── 管理画面からの停止が不可、再同期で消えにくい)。リポジトリ側にマスター版を置き、ステージング再同期後も再配置できる状態にしています。
5-3. 計測結果 ─ クエリ -42% / メモリ -30% / PHP 生成時間 -37%
本記事の執筆に合わせて本日(2026-05-12)ステージング上で再計測しました。perf mu-plugin の OFF / ON を切り替えての商品詳細 1 ページ ロード比較です。
計測条件
- 対象 URL:商品詳細ページ 1 件(固定 URL)
- 言語:日本語(
/ja/) - ログイン状態:Query Monitor 計測時は管理者ログイン、TTFB 計測時は匿名(curl 経由)
- キャッシュ:Kinsta page cache flush +
wp transient delete --all+wp cache flush後の初回ロード(MISS) - 計測ツール:Query Monitor v4.0.6(Queries / Memory / Page Generation / DB Query Time)、curl(TTFB の 5 サンプル中央値)
- サンプル数:QM は各 Variant で 1 サンプル、TTFB は 5 サンプル
| 指標 | Variant A (perf OFF) | Variant B (perf ON) | 差 |
|---|---|---|---|
| Total Queries | 489 | 285 | -204(-42%) |
| Peak Memory | 186.5 MB | 130.8 MB | -55.7 MB(-30%) |
| Page Generation | 2.02s | 1.27s | -0.75s(-37%) |
| DB Query Time | 0.23s | 0.04s | -0.19s(-83%) |
| 匿名 TTFB(中央値) | 1.37s | 1.10s | -0.27s(-20%) |
DB Query Time の差(-83%)が突出していますが、これは Variant A 側で無駄な WP_Query が大量に走っていた分がそのまま消えた結果です。Total Queries が約半分になり、各クエリの実行コストもさらに整理されています。
匿名 TTFB(-20%)が他指標と比べて控えめなのは、Kinsta の page cache や Cloudflare のエッジに乗ったタイミングで PHP 実行コストの差が薄まるためです。ただし本案件はオークションサイトの性質上、はじめに で述べたとおり 商品関連ページの page cache を原則無効化しているので、本番のユーザーリクエストもほぼ「キャッシュ MISS = PHP を毎回実行する」状態になります。つまり、PHP 生成時間そのものの -37% がそのまま本番ユーザーの体感速度に効く構造で、これがこの最適化を行った主目的でした。
5-4. もう一つの副産物 ─ 「グローバルにキャッシュ無効化」を「宣言的に範囲指定」へ
はじめにで書いた「商品関連ページの page cache を無効化する」ロジックも、アップグレード作業に合わせて実装を整理し直しました。これは PHP 実行コストとは別ベクトルの副産物で、4 年間積み残していた「静的ページまで全部キャッシュバイパスする」状態を解消した話です。
旧実装(2022-04 〜 2026-05-10):wordpress_logged_in_ クッキーの強制発行ハック
旧実装は子テーマの embed_login_cookie.php で、全訪問者(匿名含む)に wordpress_logged_in_* クッキーを発行していました。WordPress / Kinsta / Cloudflare の慣例として「ログイン中ユーザにはキャッシュを返さない」があるため、このクッキーが付いている = ログイン中扱い = サイト全体のキャッシュがバイパスされる、という挙動を利用したハックです。
オークション情報のリアルタイム性は確かに守れますが、会社概要・返品ポリシー・お問い合わせフォーム・アカウント作成ページなどの静的ページまでバイパスされるため、TTFB ~1s / PHP 実行率 100% という状態が 4 年続いていました。
新実装(2026-05-11〜):URL ベースの宣言的な範囲指定
新実装は template_redirect フックで、URL パスと WC コンディショナルの両方を見て、オークション系のページのみ nocache_headers() を送出する形に書き換えました(リポジトリ内マスター版は scripts/embed_login_cookie.php.master.php)。
add_action( 'template_redirect', function () {
// 1. URL パスベースのチェック(非標準レイアウト用)
$req = isset( $_SERVER['REQUEST_URI'] ) ? (string) $_SERVER['REQUEST_URI'] : '';
$path_matches_auction_area =
strpos( $req, '/shop' ) !== false ||
strpos( $req, '/product' ) !== false ||
strpos( $req, '/auction' ) !== false;
// 2. WC 標準コンディショナルでもチェック
$is_auction_info_page =
is_front_page() ||
is_home() ||
is_shop() ||
is_product() ||
is_product_category() ||
is_product_tag() ||
is_product_taxonomy();
// どちらかに該当すれば no-cache ヘッダを送る
if ( $path_matches_auction_area || $is_auction_info_page ) {
nocache_headers();
}
}, 1 );nocache_headers() は WordPress コアの関数で、Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private を含む 3 つのヘッダを送出します。Kinsta のエッジはこの no-cache を見るとレスポンスをキャッシュしないので、結果として「オークション情報はリアルタイム、それ以外はエッジキャッシュ可」という分離が実現します。
設計上のポイント
is_shop()だけで判定しない ── 本案件は WC レイアウトが非標準で、「ショップページ」がフロントページ/ja/に割り当てられ、/ja/shop/には別の固定ページが当たっています。is_shop()が期待どおりに動かないケースがあるので、URL パスにも/shop/product/auctionが含まれるかを併用していますtemplate_redirectの priority 1 ── WC コンディショナルが確定したあと、HTML 出力より前のタイミングで最初に走らせて、後続フックがヘッダを送ってしまう前に確実にno-cacheを確定させる。WC 自身もカート / チェックアウト / マイアカウントでは独自にnocache_headers()を送出するので、そちらは触らずに任せています- ステージングでは効果検証できない ── Kinsta はステージング環境ではサイト全体でページキャッシュが無効なので、本実装の効果はステージングでは観測できません。Phase 6 の本番反映後に初めて効果が見える項目です
ページ種別ごとの Before/After
| ページ種別 | 旧実装 | 新実装 |
|---|---|---|
トップ /ja/ | バイパス | バイパス(維持) |
商品一覧 /ja/shop/ | バイパス | バイパス(維持) |
商品詳細 /ja/product/xxx | バイパス | バイパス(維持) |
カテゴリ /ja/product-category/xxx | バイパス | バイパス(維持) |
会社概要 /ja/about/ | バイパス(無駄) | エッジキャッシュ可 |
返品ポリシー /ja/refund_returns/ | バイパス(無駄) | エッジキャッシュ可 |
アカウント作成 /ja/account-register/ | バイパス(無駄) | エッジキャッシュ可 |
| カート / チェックアウト / マイアカウント | バイパス | バイパス(WC が自前で送出) |
オークション情報は引き続きリアルタイム配信され、静的コンテンツは初回 PHP 実行後にエッジで配信されるため、4 年間まとめてバイパスされていた「会社概要・返品ポリシー・アカウント作成」等の TTFB が大幅に短縮される、というのが狙いです。本番反映後に Kinsta の cache-perf ログでヒット率の変化を観測する想定です。
この書き換えの本質は、「グローバルにキャッシュを潰す」というハックを URL ベースで宣言的に範囲指定する形に置き換えた点にあります。今後ページが追加されたとき、その URL がオークション系か静的かを コードを読めば挙動が予測できる状態に変わったので、運用上の認知負荷も同時に下がりました。
6. 副産物 ─ メール誤送信リスクも片付けられた
本案件は商品コンテンツが 終了時刻 を持つ性質上、ステージングに本番 DB をコピーすると終了タイマー由来のメール(落札通知など)が本物の顧客に届くリスクを抱えていました。アップグレード作業を進めるにあたって、このリスクを多重防御する形で設計し直しました。
6-1. ステージング固有のリスク構造
Kinsta Staging は本番 DB のコピーから始まります。顧客テーブルもそのまま入っているので、staging で wp-cron が動いて終了時刻を過ぎた商品コンテンツのメール送信処理が走ると、本物の顧客のメールアドレス宛てに通知が飛びます。「ステージングだから安全」の前提が、終了タイマー × wp-cron の組み合わせで簡単に崩れる構造です。
管理画面で WP Mail SMTP を停止しても、PHP の mail() 経由のフォールバック送信や、別プラグインからの直接送信が抜け穴になります。「停止し忘れ」「再同期で設定が戻る」も含めて、一次対策だけでは事故が起こりうる構造を消しきれません。
6-2. pre_wp_mail 短絡 mu-plugin による多重防御
二次対策として、pre_wp_mail フィルタ(WP 5.7+)で wp_mail() 呼び出しそのものを短絡する mu-plugin を入れました。フィルタは「実送信より前」のレイヤで走るので、WP Mail SMTP でも PHP mail() でも、wp_mail() を経由する全送信を捕まえられます。
<?php
/**
* Plugin Name: Staging Mail Disabled
* Description: ステージング環境からのメール送信を pre_wp_mail 層で完全ブロックする。
* 誤って本番顧客へメールが飛ぶ事故を防ぐ belt-and-suspenders。
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
add_filter( 'pre_wp_mail', function ( $null, $atts ) {
// 本番ドメインでは何もしない(誤検知防止のための二重チェック)
$home = home_url();
if ( false === strpos( $home, 'stg-yourdomain.kinsta.cloud' ) ) {
return $null;
}
// 監査用にエラーログへ記録
$to = isset( $atts['to'] )
? ( is_array( $atts['to'] ) ? implode( ',', $atts['to'] ) : (string) $atts['to'] )
: '(no recipient)';
$subject = isset( $atts['subject'] ) ? (string) $atts['subject'] : '';
error_log( sprintf( '[STAGING MAIL BLOCKED] to=%s subject=%s', $to, $subject ) );
// wp_mail() の呼び出し元に「送信成功」と返して通常フローを継続させる
return true;
}, 10, 2 );設計上のポイントが 3 つあります。
home_url()二重ガード ── 本番に誤コピーされた場合に本番でメール送信を止めてしまわないための保険。ステージング独自の hostname パターンが含まれているときだけ短絡します。trueを返す ──wp_mail()の呼び出し元に「送信成功」と通知することで、UAP のような送信失敗時にリトライするプラグインが再送ループに入らないようにしています。error_log()で監査ログ ── 「何の送信が短絡されたか」を運用ログとして残し、想定外の送信トリガーがあれば後追いできるようにしています。
6-3. 再同期しても消えない配置と、リポジトリのマスター版
mu-plugin の配置先は wp-content/mu-plugins/staging-mail-disabled.php。mu-plugins/ 配下は WP がコード変更なしで自動ロードする領域で、管理画面からは無効化できません(=「停止し忘れ」「誤クリック」を構造的に排除できる)。
ただし Kinsta の ステージング → 本番の Files Push でこのファイルがそのまま本番に押し出されると、今度は本番のメールが止まる事故になります。これを避けるため、Phase 6(本番反映)では staging-mail-disabled.php を本番反映直前に退避(リネームで非ロード化)し、反映後に staging 側へ再配置する手順を CLAUDE.md の本番反映フローに固定しました。
再同期で mu-plugin が消えても即座に再配置できるよう、リポジトリ側に scripts/staging-mail-disabled.php をマスター版として置いています。「staging を作り直す手順 = リポジトリから mu-plugin を撒き直す手順」を CLAUDE.md に書いておけば、Claude Code が次回 staging を立て直すときも自動的に正しい状態に持っていけます。
7. SSH 経由で動かしてもらう上での境界線
この型は 境界線 を引いて初めて成立します。AI に任せる範囲と、人間が止める範囲を 3 点で整理します。
7-1. すべての作業はステージング上で行う
Kinsta Staging のスナップショットで「戻せる前提」が成立しているため、検証・試行錯誤・破壊的な変更はすべてステージング側で完結させています。本番は「動作確認済みのものを反映する場所」として、開発作業の対象には含めません。戻せない環境を AI に触らせる構造を作らない、というのが本章で伝えたい原則です。
7-2. 本番反映は人間の最終承認を挟む
本番反映は Kinsta の Files-only Push(DB は同期せず、ファイルだけを本番に押し出す機能)を、MyKinsta ダッシュボードから人間が実行します。Claude Code には MyKinsta のクレデンシャルを渡していないため、AI が単独で本番に届かない構造です。Claude Code からは「反映可能な状態になりました」という報告まで届き、最終的にボタンを押すのは人間。
7-3. API キーは事前に削除しておく
Kinsta Staging は本番コピーから始まるため、本案件では SMTP の API キー は事前に手作業で削除しています。SMTP / 決済 / 外部連携プラグインの API キーが本番値のまま残っていることがあるので、セキュリティとして注意しています。
8. この型を他案件に移す場合
最後に、本記事の型を他の WordPress 案件に移すときに何が再利用でき、何が案件依存かを整理します。
8-1. 再利用できる部分(他案件にもそのまま持っていける)
以下は案件を問わずほぼそのまま移植できる「型」です:
- CLAUDE.md の章立てテンプレート(プロジェクト前提 / 固定フロー / 禁止事項 / メール文体 / 緊急時手順)
docs/tasks/<task>.mdの Phase + チェックボックス設計- 本番反映フロー(Files-only Push + 人間最終承認、CLAUDE.md に文書化)
- 境界線の設計(ステージング限定、シークレット事前削除)
- 計測スタイル(Query Monitor + 計測条件明示 + Variant A/B 比較表)
- mu-plugin によるパフォーマンス対策・ステージングメール多重防御の配置パターン(
wp-content/mu-plugins/+ リポジトリでマスター版管理)
これらは「Claude Code を WordPress 運用に通す型」として、案件特性に関係なく機能します。
8-2. 案件依存の部分
逆に、以下は案件ごとに固有なので毎回作り直す必要があります:
- 子テーマの diff 内容(どのファイルをどの理由で上書きしているか)
- 有料プラグインの個別事情(本案件の UAP / WPML / Booster Plus、別案件なら別の組み合わせ)
- サイト固有の業務ルール(メール文体、通知タイミング、決済プロバイダ仕様、表示要件)
- パフォーマンス上の阻害要因(本案件は UAP のカテゴリカウントだったが、別案件では別の重い処理)
- キャッシュ戦略(本案件は動的なオークションステータスを正確に表示するため商品関連ページの page cache を無効化しているが、一般的な EC や情報サイトでは page cache を活かす方針が普通)
これらは Phase 0(準備)で案件固有の調査を行い、CLAUDE.md / docs/tasks/ に書き留めるところから始まります。
8-3. クライアントを巻き込むコミュニケーション設計
技術的な型に加えて、クライアントとの合意形成も再現可能な部分です。ステージング URL をクライアントに共有し、結果ベースで動作確認してもらう運用に寄せます(「次の Phase が完了したのでこちらで確認お願いします」)。AI 利用の伝え方は「結果が出てから」ベースで、Phase ごとに動作確認できる成果物が段階的に出てくる形を取ります。
クライアントから見ると 「動作確認できる成果物が段階的に出てくる」 運用になるので、AI 利用について抽象的に説明するよりも、結果を通じて信頼を獲得しやすくなります。
あとがき
本記事は、メジャーバージョン更新を慎重に運用してきた WordPress 案件を、Claude Code に SSH 経由で WP-CLI を叩いてもらう運用設計に切り替えて、WooCommerce 4.5.5 → 10.0.4(6 メジャー版分)を段階的に進めた記録です。Claude Code 単体の話ではなく、Kinsta Staging のスナップショット前提 + CLAUDE.md / docs/tasks/ で Phase 状態を持たせる分業 + 人間が最終承認する境界線を組み合わせた運用設計の話として書きました。同じ構造の課題を抱える方の参考になれば幸いです。
MOOBON では、WordPress / Kinsta 運用案件の整理、長期運用案件のアップグレード支援、Claude Code を含む AI 協業フローの設計支援を承っています。お問い合わせは info@moobon.jp までお気軽にどうぞ。
よくある質問
QClaude Code を本番環境に直接 SSH させても安全ですか?
本記事では本番には接続情報そのものを Claude Code に渡していません。Claude Code が SSH するのは Kinsta Staging までで、本番反映は人間が MyKinsta ダッシュボードから Files-only Push を手動実行する設計にしています。一般論として、本番への直接 SSH を AI に許可するかどうかは、その環境が「戻せる前提」になっているかで判断します。スナップショットで瞬時に戻せる環境なら検討の余地はありますが、戻せない環境を AI に触らせる構造は本記事の型では推奨していません。
QCLAUDE.md にはどんな情報を書いておくべきですか?
プロジェクト前提(WP / プラグインのバージョン、Kinsta 構成、PHP バージョン)、本番反映の固定フロー(Files-only Push、人間最終承認)、禁止事項(本番への直接 SSH、wp-config.php の直接編集等)、メール文体、緊急時手順を書きます。本案件では、Kinsta Staging までのアクセスのみ許可・本番反映前の mu-plugin 退避手順・クライアントへのメール文体までを CLAUDE.md に固定しています。毎セッション開始時に Claude Code が読み込むので、文脈の取り直しが不要になります。
Q既存の WordPress 案件にこの仕組みをすぐ導入できますか?
SSH と WP-CLI が使える運用基盤(Kinsta、ConoHa Wing、自前 EC2 など)があれば、CLAUDE.md と docs/tasks/ を整備するだけで導入可能です。本案件で使ったテンプレート構成は他案件にもほぼそのまま持っていけます。「壊しても戻せる」前提を作る部分(本案件では Kinsta Staging のスナップショット)が一番のハードルになります。スナップショット相当の仕組みがない環境では、まずバックアップ・復元手順を整備するところから始めるのが現実的です。
Q6 メジャーバージョン分のアップグレードは本当に Claude Code 経由で完結したのですか?人間が手で動いた部分はどこですか?
大半は Claude Code が SSH + WP-CLI で実行しましたが、以下は人間が判断・実行しました。(1)子テーマ 7 ファイルの diff 移植時の「独自実装を残すか捨てるか」の判断、(2)StoreApi Fatal の調査結果と修正コマンドの最終承認、(3)mu-plugin の設計判断(Booster Plus の警告 1 件残を許容する判断 等)、(4)本番反映時の MyKinsta ダッシュボード操作(Files-only Push のボタン押下)。AI が単独で完結できない部分を境界線として明示するのが、本記事 7 章の主題です。
Qクライアントに AI(Claude Code)利用を伝える必要はありますか?
本案件では、ステージング URL を共有し、結果ベースで動作確認してもらう運用に寄せました。「AI を使った」を抽象的に説明するよりも、「動作確認できるステージングが Phase ごとに出てくる」結果ベースの進行で信頼を獲得しています。クライアントから AI 利用について明確に確認があれば率直に説明しますが、最初から「AI を使うことの是非」を議題にしない方が、実務はスムーズに進む印象です。AI 利用に対する顧客の許容範囲は案件ごとに違うので、ケースバイケースで合意形成します。
