コンテンツにスキップ

WordPress CMS統合ガイド

このドキュメントは、Storefront、Vendure、および WordPress CMSの統合方法、アーキテクチャ、実装パターンを説明します。

概要

このプロジェクトでは、WordPressをHeadless CMSとして使用し、以下のコンテンツを管理します:

  • ナビゲーションメニュー: ヘッダー・フッターの動的なリンク構造
  • お知らせ(Announcements): サイト全体のお知らせ、重要なお知らせの固定表示
  • キャンペーン(Campaigns): プロモーションキャンペーン情報
  • メインバナー(ホームヒーロー): ホームページのヒーローセクション用バナー
  • 店舗設定: ロゴ、コピーライト、コンタクト情報、ホーム下部バナー、SNSリンクなど

ストア設定内のSNSリンク

ストア設定(site-settings)の socialList は、WordPress の ACF Options で管理し、Vendure の wordPressStoreSettings 経由で Storefront に渡します。

  • socialList未取得 (null / undefined) の場合でも、Storefront は推測で外部 SNS を表示せず、有効な実 URL がある行だけ を表示する
  • socialList空配列、または登録行がすべて無効・URL未設定で 有効リンク 0 件 の場合は、Storefront は SNS を表示しない
  • example.com / example.org / example.net 系のプレースホルダー URL は、本番導線として扱わず非表示にする
  • iconUrl は未知の SNS 種別または custom 用のアイコンとして使い、既知の LINE / Instagram / YouTube は固定アイコンを優先する

ブランド表示と導線の正本

ブランドの名称・説明文・画像と、その配下のメニュー導線は WordPress ではなく、Vendure の brands 親 collection 配下の collection tree を正本として管理します。

  • まず Vendure 側で brand Facet と各 FacetValue を用意する
  • 1 ブランド 1 child collection を作成し、facet-value-filterbrand=<facet value> を設定する
  • 必要に応じてブランド配下に 業務用商品 / 店販用商品 / セット商品 / 販促品 / キャンペーン商品 などの menu collection を追加し、親 filter 継承 + 追加 Facet 条件で絞り込む
  • ブランド / menu collection の slug は Storefront 導線と plugin 判定に使う安定キーとして管理する
  • name / description / featuredAsset / assets を Storefront のブランド表示やメニュー導線に利用する
  • ホームの「ご契約ブランド一覧」と商品一覧上部のブランド紹介は、 当該ブランド collection に可視商品が1件以上ある場合のみ表示する
  • visibleCollectionIds は collection の許可条件としては使うが、表示の最終判定は 可視商品件数で行う
  • Storefront のブランドメニューは Vendure Shop API の collections から brands root 配下の collection tree を作り、その後 visibleCollectionIds で顧客別に再帰フィルタする。isPrivate=true の collection は Shop API の tree 取得時点で除外されるため、商品可視性ルールとは別条件として扱う
  • ブランド表示判定は Storefront で span / structured log / breadcrumb を記録し、 判定対象数・表示件数・所要時間を観測できるようにする
  • Brand のデータ真実は brand FacetValue とし、Collection はそのページ / 導線表現として扱う
  • 価格・配送・同意・契約条件は WordPress ではなく Vendure の Facet / custom field で管理する
  • WordPress の site-settings はブランド情報を保持せず、ロゴ・連絡先・ホーム下部バナー・SNSなどの共通設定だけを管理する

ブランドメニュー表示条件 matrix

Vendure collection の状態 Shop API collections の tree 顧客別 visibleCollectionIds / 可視商品 Storefront ブランドメニュー 運用診断 /diagnostics 対応方針
brands 配下で isPrivate=false tree に含まれる 自身、または子孫に可視商品がある 表示される 警告なし 正常。
brands 配下で isPrivate=false tree に含まれる 自身にも子孫にも可視商品がない 表示されない 警告なし 商品紐付け、可視性ルール、検索 index を確認する。
brands 配下で isPrivate=true tree から除外される 可視商品がある 表示されない private-brand-collections が warning 意図的に隠す collection でなければ非公開を解除する。
brands 配下で isPrivate=true tree から除外される 可視商品がない 表示されない private-brand-collections が warning 非公開解除後、可視性ルールと商品紐付けも確認する。
brands 配下の祖先 collection が isPrivate=true private 祖先が tree から除外され、子孫も brands tree に接続できない 子孫に可視商品がある場合でも menu tree に載らないことがある 表示されない private 祖先が warning 親を公開するか、導線に出す階層構造を組み直す。
brands 配下ではない collection ブランド menu の対象外 任意 ブランドメニューには表示しない 警告なし キャンペーン・商品タイプなど別 root の導線で扱う。

アーキテクチャ

StorefrontはWordPressへ直接アクセスするのではなく、Vendureの CmsIntegrationPlugin をプロキシとして経由します。これにより、認証情報の秘匿、APIの一元化、およびデータの正規化が可能になります。

┌─────────────────┐
│ WordPress CMS   │
│ (Headless)      │
│                 │
│ - Menu Items    │
│ - Custom Posts  │
│ - ACF Settings  │
└────────┬────────┘
         │ WPGraphQL (Internal)
         │
         ▼
┌─────────────────┐
│ Vendure Server  │
│ (CMS Plugin)    │
│                 │
│ - Data Proxy    │
│ - Normalization │
│ - Resolvers     │
└────────┬────────┘
         │ Shop API (Public)
         │
         ▼
┌─────────────────┐
│ Storefront      │
│ (Vite)          │
│                 │
│ - Route Loaders │
│ - Components    │
│ - Display       │
└─────────────────┘

1. サイト外枠(枠組み)

  • 担当: Storefront
  • 対象: ヘッダー、フッター、グローバルナビゲーション
  • 管理: WordPressの「メニュー」機能(位置: PRIMARY, FOOTER)で管理。Vendureがこれを headerMenu, footerMenu として公開し、StorefrontがReactコンポーネントとして描画します。

2. メインコンテンツ(中身)

  • 担当: WordPress(本文・埋め込みコンテンツ)/ Storefront(レイアウト・ページ構成)
  • 対象: 固定ページ、お知らせ詳細、キャンペーン詳細(Storefrontの該当セクション)
  • 実装: WordPressが生成するHTMLをVendure経由で取得し、Storefrontの ContentProcessor で加工して描画します。
  • 本文中リンクの扱い: content-processor.tsnormalizeInternalHref は WordPress オリジンと同一ホストの絶対URLだけを Storefront パスへ寄せ、 ホストが異なる外部URL (parsed.hostname !== base.hostname) はそのまま保持します。 メニュー (normalizeCmsUrl) / コンポーネントリンク (classifyLinkHref) / アセットURL (normalizeCmsAssetUrl) も同様に、外部ドメインを上書きしない方針で統一しています。

WordPress stylesheet の適用境界

WordPress の block markup を styleMode = wordpress_theme で表示する場合、Storefront は WordPress core の wp-block-library / wp-block-library-theme を CMS 表示境界で読み込みます。 これは Vimeo などの埋め込みに付く .wp-embed-aspect-16-9 / .wp-has-aspect-ratio を Storefront 側で個別再実装せず、WordPress の表示作法を継承するためです。

  • WordPress stylesheet の読み込み責務は Storefront の CmsAssetLoader に限定する。
  • Vendure Server / Vendure Dashboard / React Dashboard 側の CSS や component には WordPress core CSS、.editor-styles-wrapper.wp-content 前提の補正を持ち込まない。
  • 商品リッチ説明では styleMode = wordpress_theme のときだけ WordPress stylesheet を読み込み、styleMode = storefront_theme では Storefront 側 typography を使う。
  • WordPress core stylesheet はブラウザ上では global stylesheet として効くため、同じ Storefront ページ内での影響を避けたい本文は storefront_theme として管理する。
  • 具体的な商品リッチ説明の選択順、collection 階層優先、sanitizer 経路は product-detail-product-scope.md を正本にする。

実装パターン

正規化処理の共通化

WordPress特有のデータ構造をRitsubiドメインのクリーンなデータに変換するため、正規化処理を重層的に行います。

  1. Shared Utilities (@ritsubi/utils): slugify / normalizeCmsUrl / cmsUrlToStorefrontPath といった純粋な変換関数を提供します。バックエンドとフロントエンドの両方で使用されます。
  2. normalizeCmsUrl(url, { cmsHosts }): リンク描画用。相対パス (/page/... 等) は投稿系ルートへ寄せる。 絶対URLは cmsHosts (= CMS 自身の host) に一致した場合だけ Storefront path へ寄せ、 それ以外のドメインを含む絶対URL(外部リンク)は 一切加工せず保持する。 これにより「編集者が CMS ドメインで書いた内部ナビ(例: ヘッダー)」は相対パス化されて SPA 遷移を維持しつつ、forms.gle 等の 外部URLは origin を上書きされず正しく機能するcmsHosts は host の正本である WordPress エンドポイント (backend=config.options.endpoint / Storefront=VITE_PUBLIC_WORDPRESS_URLgetCmsHosts()) から導出する。
  3. cmsUrlToStorefrontPath: 入力が CMS 自身の内部URLであることが確定している用途 (お知らせ permalink、WP ページ lookup キー) でのみ使用し、cmsHosts 判定なしで絶対URLでも origin を落としてパス化する。
  4. Vendure Plugin (Backend): WPGraphQLの冗長なネスト(nodes { ... } 等)を排除し、フロントエンドが扱いやすいフラットな型へ正規化します。
  5. Storefront (Frontend): Vendureから受け取ったデータを、UIコンポーネントのProps(NavigationItem 等)に最終マッピングします。

GraphQLクライアント

Storefront からの CMS リクエストは apps/storefront/src/lib/cms/client.tsfetchCmsGraphQL を使用します。この関数は Vendure の Shop API を唯一の取得先 とし、Storefront から WordPress へ直接アクセスしません。

  • Storefront の責務は「Vendure schema 上の CMS query を呼ぶ」ことに限定する
  • 上流 CMS が WordPress であることは Vendure plugin 側の実装詳細として閉じ込める
  • apps/storefront/src/lib/cms/content/* は、現時点では WordPress 由来コンテンツの型・整形責務を表すディレクトリ名であり、通信先を意味しない
Storefront -> Vendure /shop-api -> CmsIntegrationPlugin -> WordPress WPGraphQL

メニュー同期 (Menus)

  • WordPress側: 「外観」→「メニュー」で作成。位置を「Header」または「Footer」に設定。
  • Vendure側: CmsIntegrationPluginheaderMenu, footerMenu クエリを提供。menu の WP fetch は plugin 内の fetch cache(既定 TTL 300 秒)を使う。 即時反映よりも runtime 安定性(slow CMS での 5s timeout 回避)を優先する設計で、 withPersistentSnapshot は毎回ライブ取得し、上流障害時のみ snapshot へフォールバックする (参照: wordpress.service.ts fetchMenuFromWordPress のコメント)。このため WordPress 側の メニュー編集が Shop API へ反映されるまで 最大で plugin fetch cache TTL(約 5 分)かかる。
  • Storefront側: fetchMenus() で両方のメニューを一括取得し、Header / Footer コンポーネントに反映。
  • リンクの正規化(host 判定): 各メニュー項目の URL は host で内部 / 外部を判定する。 CMS 自ドメイン(WordPress エンドポイント host)の絶対URLと相対パスは Storefront ルートへ 寄せて SPA 遷移を維持し、それ以外の外部ドメイン(forms.gle 等)は origin を保持して そのまま使う(openInNewTab で別タブ)。詳細は後述の「メニューリンクのドメイン処理」節を参照。
  • 編集者向け運用: 内部ページへの導線は相対パス(/products / /pages/...)または page link で設定することを推奨(環境差や余計なリダイレクトを避けられる)。外部サイトへは フルURLを設定し、必要に応じて「新しいタブで開く」を有効化する。

即時反映 WP-to-Storefront Webhook

WordPress の管理画面で保存しただけで storefront へ即時反映する。Cloudflare 管理画面や CLI 操作は不要。

  • 発火点: apps/wordpress-cms/plugins/ritsubi-ec-plugin/ritsubi-acf-vendure-bridge.php
  • wp_update_nav_menu / wp_create_nav_menu / wp_delete_nav_menuevent: "menu.updated"
  • transition_post_status (publish 状態の出入りのみ) → event: "<type>.updated"
    • pagepage.updated
    • announcement / word_press_announcementannouncement.updated
    • campaign / word_press_campaigncampaign.updated
    • product_detailproduct.updated(storefront wordPressProductDetailproducts タグを invalidate するため、page.updated ではなく専用イベント)
  • acf/save_post (post_id が非数値の文字列、user/term/comment/widget/block meta 以外) → event: "settings.updated"
    • 対象は ACF Options ページ全般(ストア設定 / メインバナー等)。 post_id を "options" 既定で使うものも、明示指定した custom post_id も一様に捕捉する。
  • 送信先: STOREFRONT_REVALIDATE_URL(未設定なら STOREFRONT_URL + /api/revalidate/cms
  • 認証: x-cms-revalidate-secret ヘッダ。値は storefront と共有の CMS_REVALIDATE_SECRETb2b-ecommerce/{env}/shared 配下)。
  • 失敗時挙動: 非 blocking POST で管理画面の保存はブロックしない。失敗は error_log に残す。

各 event がどのタグを invalidate するかは apps/storefront/src/lib/cms/content/cache-tags.tsEVENT_PREFIX_TO_TAGS を参照。 webhook は storefront 側のデータキャッシュタグを invalidate するが、上流 WordPress の 鮮度は Vendure plugin 側の fetch cache に依存する。

[!NOTE] menu を含むすべての CMS fetch は Vendure 側の WORDPRESS_FETCH_CACHE_TTL_SECONDS(既定 300s)の影響を受ける(menu も bypass しない。 安定性優先の設計理由は wordpress.service.ts fetchMenuFromWordPress のコメント参照)。 したがって WordPress 編集の Shop API への反映は 最大で約 5 分 遅延し得る。即時反映が必要なら 該当 query への invalidate API 追加が follow-up となる。

設定

環境変数 (Vendure Server)

バックエンドがWordPressへアクセスするための設定です。

  • WORDPRESS_ENDPOINT: WordPressのベースURL(例: http://localhost:8181
  • WORDPRESS_GRAPHQL_TOKEN: WPGraphQLの認証が必要な場合のBearerトークン

環境変数 (Storefront)

  • VENDURE_BASE_URL: Vendure サーバーのベースURL(推奨・一本化)
  • VITE_PUBLIC_VENDURE_BASE_URL: Storefront が参照する公開向け Vendure ベースURL
  • VITE_PUBLIC_STOREFRONT_API_MOCKING: enabled に設定するとMSW経由でのモックが有効になります。

Storefront の CMS 通信に WORDPRESS_ENDPOINT は使用しません。これは Vendure が上流 CMS へ接続するための設定 と、 /api/health/cms における upstream 診断用途に限定します。


開発とテスト

テスト戦略

  • バックエンドテスト: packages/plugins/src/cms-integration/wordpress/*.spec.ts で、WordPressからの生データが正しく正規化され、GraphQLリゾルバーが機能することを検証します。
  • フロントエンドテスト: apps/storefront/src/lib/cms/content/*.test.ts で、VendureからのデータがUIコンポーネント向けに正しくマッピングされることを検証します。
  • SNSリンク表示テスト: Storefront 側で「未取得時非表示」「明示的に空のとき非表示」「プレースホルダーURLの除外」「未知SNSの iconUrl 表示」を検証します。

モックデータの扱い

ソースコード内にハードコードされた大規模なモックデータは廃止されました。開発時にモックが必要な場合は、apps/storefront/src/mocks/cms-handlers.ts (MSW) にデータを定義してください。

トラブルシューティング

メニューが反映されない

  1. WordPress側でメニューが作成され、正しい「位置」(PRIMARY/FOOTER)に割り当てられているか確認してください。
  2. Vendureサーバーのログを確認し、WordPress APIへのリクエストが成功しているか(200 OK)を確認してください。
  3. GraphQL Playground (/admin-api 等) で query { headerMenu { items { label } } } がデータを返すか直接確認してください。

メニューリンクのドメイン処理(内部 / 外部の判定)

メニューリンクは host で内部 / 外部を判定します。内部 host = CMS 自ドメイン + storefront 自身の canonical / alias で、いずれかに一致する絶対URLは相対パスへ寄せます。

  • CMS 自ドメイン (config.options.endpoint / VITE_PUBLIC_WORDPRESS_URL の host、 例: cms.ritsubi-platform.com) の絶対URLは 内部ナビとみなし、相対パスへ寄せます。 これによりヘッダー等の内部導線は SPA 遷移 (高速) を維持します。相対パス (/page/...) や page link (linkType: page) も同様に内部扱い(permalink / page uri は cmsUrlToStorefrontPath)。
  • storefront 自身の canonical / alias (storefrontConfig.siteUrl の host + VITE_PUBLIC_STOREFRONT_ALIAS_HOSTS、例: order.ritsubi-platform.com / medical.ritsubi.co.jp) の絶対URLも 内部扱いで相対化します。これは編集者が公開エイリアスの フルURL (https://medical.ritsubi.co.jp/pages/guides) をメニューに設定した場合に、 別オリジン遷移による cookie / セッションロスを避けるため(SSOT は getStorefrontSelfHosts() / classifyLinkHref と共通)。storefront 層の正規化が backend (Shop API) 由来の絶対URLも再正規化する。
  • それ以外のドメイン (forms.gle、提携サイト等) の絶対URLは 外部リンクとみなし、 origin を一切上書きせずそのまま使います。openInNewTab を設定すれば別タブで開きます。
  • 注意: CMS 自ドメインの判定は環境ごとの WordPress エンドポイント host が正本です。 staging データに production の CMS ドメイン (cms.ritsubi-platform.com) を直書きすると、 staging の host (cms-staging...) と一致せず外部リンク扱いになります。内部導線は相対パス推奨。

Storefront から WordPress に直接つながって見える

  • Storefront には apps/storefront/src/lib/cms/content/* というディレクトリ名が残っていますが、これは主に WordPress 由来コンテンツの型・整形責務を表します
  • 実際の通信入口は apps/storefront/src/lib/cms/client.tsfetchCmsGraphQL で、接続先は Vendure shop-api です
  • 上流 CMS の疎通だけを見たい場合は /api/health/cmsupstreamCms を確認し、通常導線の確認は vendureCms を確認してください
  • /api/health/cmsTier 3 診断用途 であり、継続的なプローブには使わないこと(Vendure・WordPress upstream へのファンアウトが発生するため)

ヘルスチェック・プローブの使い分け

エンドポイント早見表

用途 エンドポイント 備考
継続的プローブ(Tier 0 Safe Probe) storefront /api/health/live
storefront /api/health/ready
storefront /api/version
上流ファンアウトなし。スケジュール自動実行に使う
CMS 診断(Tier 3 Diagnostic) storefront /api/health/cms Vendure + WordPress upstream 両方を確認。手動診断時のみ使う

継続的なスケジュールプローブは .github/workflows/scheduled-safe-probes.yml が30分ごとに Tier 0 エンドポイント(Storefront・Vendure 両方)を確認します。 /api/health/cms をスケジュール監視の対象にしないでください。


各エンドポイントが答える問い

それぞれのエンドポイントは「異なる問い」に答えます。問いが違うため、用途も異なります。

エンドポイント 答える問い
/api/health/live アプリのプロセスは生きているか? ─ プロセスがクラッシュしていないかを確認する最も軽量なチェック。
/api/health/ready リクエストを受け付けられる状態か? ─ 起動・初期化が完了しトラフィックを処理できるかを確認する。
/api/version デプロイ済みのバージョンは何か? ─ 期待するリリースがデプロイされているかを確認する。
/api/health/cms Vendure→WordPress の CMS 取得経路は健全か? ─ コンテンツが取得できない原因が Vendure 側にあるか WordPress upstream 側にあるかを切り分ける診断用チェック。

なぜ /api/health/live / /api/health/ready があるのに /api/health/cms が必要なのか

/api/health/live/api/health/readyアプリ自身の状態(プロセス生存・初期化)しか確認しません。WordPress のコンテンツが取得できるかどうかは、アプリの「生存」とは独立した別の問題です。

たとえば、次の状況ではアプリは live かつ ready であっても、コンテンツは表示されません:

  • WordPress が停止または応答遅延している
  • Vendure の CmsIntegrationPlugin の環境変数(WORDPRESS_ENDPOINT など)が誤設定
  • WordPress と Vendure 間のネットワーク経路に障害

こうした CMS 経路特有の問題を切り分けるために、/api/health/cms が独立した診断エンドポイントとして存在します。

なぜ /api/health/cms を継続的プローブに使ってはいけないのか

/api/health/cms を呼ぶたびに Vendure と WordPress upstream の両方へ実際にリクエストが飛びます(ファンアウト)。これを高頻度で実行すると:

  • WordPress に不要な負荷がかかる
  • Vendure のロードバランサーやヘルスチェック判定に誤影響を与えるおそれがある
  • Tier 0 セーフプローブが担うシンプルな死活監視の目的と混在する

継続的な死活監視は Tier 0 エンドポイントで十分です。/api/health/cms は障害調査など必要なときだけ手動で呼ぶものとして扱ってください。

vendureCmsupstreamCms の読み方

/api/health/cms のレスポンスには2つのステータスフィールドが含まれます。これを使って障害箇所を絞り込めます。

フィールド 意味 fail のとき疑う箇所
vendureCms.status Vendure(CmsIntegrationPlugin)側で CMS データを取得・整形できているか Vendure の環境変数、プラグイン設定、Vendure→WordPress 内部経路
upstreamCms.status Vendure から WordPress(upstream)へ直接疎通できているか WordPress 本体、WPGraphQL プラグイン、パーマリンク設定、ネットワーク・認証

解釈の目安:

vendureCms upstreamCms 読み取り
ok ok CMS 経路は正常
ok fail Vendure はキャッシュなどで応答できているが upstream に異常。早めに WordPress を確認する
fail ok Vendure 内部の問題(プラグイン設定ミスなど)。upstream は生きている
fail fail Vendure から WordPress への経路に全体的な問題がある

ランブック: コンテンツが見えないがアプリは生きている

「お知らせ・バナー・メニューが表示されない、でも Storefront 自体は動いている」という状況での調査手順です。

ステップ 1: アプリ自体の生存を確認する

GET /api/health/live
GET /api/health/ready

両方 200 OK かつ status: ok であれば、アプリは正常稼働中。問題は CMS 経路にあります。

ステップ 2: CMS 経路を診断する(手動・1回だけ)

GET /api/health/cms

レスポンスの vendureCms.statusupstreamCms.status を確認し、上の表で障害箇所を絞り込みます。

production では公開面の診断情報を絞るため、/api/health/cms の詳細 message は返さず status-only です。詳細切り分けが必要な場合は、non-production 環境または runtime logs / Sentry / upstream 側の確認を併用してください。

ステップ 3A: upstreamCms.statusfail の場合 → WordPress を確認する

  1. WordPress 管理画面(/wp-admin)にアクセスできるか確認
  2. WPGraphQL エンドポイント(/graphql)が応答するか確認
  3. Vendure 側の WORDPRESS_ENDPOINT 環境変数が正しいか確認
  4. WordPress のパーマリンク設定(推奨: 数字ベース)を確認

ステップ 3B: vendureCms.statusfail の場合 → Vendure を確認する

  1. Vendure サーバーのログで CmsIntegrationPlugin 関連のエラーを確認
  2. WORDPRESS_ENDPOINT / WORDPRESS_GRAPHQL_TOKEN の環境変数を確認
  3. Vendure Dashboard の GraphQL Playground で CMS クエリを直接実行して切り分け

ステップ 4: 通常導線に戻って確認する

問題が解消したら、/api/health/cms ではなく実際の Storefront 画面(お知らせ一覧・バナーなど)で表示を確認してください。プローブが ok でもキャッシュの問題でコンテンツが古い場合があります。


WordPress 管理画面のリソースピッカーでサジェストが表示されない

メインバナー等の設定画面で「商品選択」「コレクション選択」など Vendure リソースを選ぶピッカーフィールドのオートコンプリートが機能しない場合の診断手順です。

ピッカーの仕組み

WordPress 管理画面 (ACF フィールド)
  └─ sku-field.js が Select2 として初期化
        └─ POST /wp-admin/admin-ajax.php
              { action: ritsubi_product_search, nonce, term }
           └─ PHP AJAX ハンドラ
                 └─ GET <VENDURE_BASE_URL>/cms-api/product-search
                       headers: x-ritsubi-signature, x-ritsubi-timestamp
                    └─ Vendure が DB 検索して JSON 返却

ピッカーの種類と対応エンドポイント:

ACF フィールドタイプ AJAX アクション Vendure エンドポイント
ritsubi_product_picker ritsubi_product_search /cms-api/product-search
ritsubi_sku_picker ritsubi_sku_search /cms-api/sku-search
ritsubi_collection_picker ritsubi_collection_search /cms-api/collection-search
ritsubi_campaign_picker ritsubi_campaign_search /cms-api/campaign-search
ritsubi_customer_picker ritsubi_customer_search /cms-api/customer-search
ritsubi_customer_group_picker ritsubi_customer_group_search /cms-api/customer-group-search
ritsubi_subject_set_picker ritsubi_subject_set_search /cms-api/subject-set-search

[!IMPORTANT] ritsubi_customer_search のみ、CMS の edit_posts ではなく manage_options(Administrator 相当)が必要です。CMS 編集ロール(Editor/Author)が顧客 PII を引き出せないようにするためで、ピッカーが 403 を返す場合はロール設定を確認してください。あわせて customer picker のラベルは customerCode – name のみで、emailAddress は返しません。

ステップ 1: ブラウザ DevTools で AJAX レスポンスを確認する

ブラウザの DevTools → Network タブを開き、admin-ajax.php への POST を探してレスポンスを確認します。

レスポンス 意味 次のステップ
{"success":false,"data":{"code":"vendure_connection_not_configured"}} Vendure URL / トークン未設定 ステップ 2A へ
HTTP 502/503 + {"success":false,"data":{"code":"vendure_upstream_error"}} Vendure への接続失敗 ステップ 2B へ
HTTP 401 + {"success":false,"data":{"code":"vendure_upstream_error"}} HMAC 署名ミスマッチ ステップ 2C へ
[](空配列、HTTP 200) Vendure への接続は成功だが結果 0 件 ステップ 2D へ
[{value, label}, ...] なのに UI に表示されない JavaScript 初期化の問題 ステップ 2E へ

ステップ 2A: Vendure 接続設定を確認する

WordPress 管理画面の「サイト設定」オプションページ、または環境変数で以下が設定されているか確認します。

RITSUBI_VENDURE_BASE_URL  例: https://api.example.com(末尾スラッシュ不要)
RITSUBI_CMS_API_TOKEN     Vendure 側の CMS_API_TOKEN と一致する値

PHP の優先順位: PHP定数 RITSUBI_* > 環境変数 RITSUBI_* > ACF オプション > WP オプション

ステップ 2B: ネットワーク疎通を確認する

WordPress サーバーから Vendure への HTTP 到達性を確認します。

# WordPress コンテナ内から
curl -I <VENDURE_BASE_URL>/cms-api/product-search?term=&take=1

タイムアウトは 5 秒です。Vendure の cold start がある場合は間に合わないことがあります。

ステップ 2C: HMAC 署名を確認する

Vendure の CMS_API_TOKEN と WordPress 側の RITSUBI_CMS_API_TOKEN が完全一致しているかを確認します(空白・改行の混入に注意)。Vendure サーバーのログで expired_signature / unauthorized が出ていれば、トークン不一致またはサーバー時刻のずれ(±5分以内が必須)が原因です。

ステップ 2D: Vendure の DB データを確認する

Vendure に有効な商品・コレクション等が存在するか確認します。

  • 商品ピッカーは variant.enabled = true AND product.enabled = true の条件でフィルタされます
  • デフォルト言語のスラッグが存在しない商品は表示されません(翻訳 JOIN が NULL になるため)
  • packages/plugins/src/cms-integration/cms-api/cms-api.plugin.tsproductSearch エンドポイントを参照

ステップ 2E: JavaScript 初期化を確認する

Select2 が初期化されていない場合、フィールドは素の <select> として表示されます。

  1. ブラウザコンソールで window.RITSUBI_PICKER_FIELD が定義されているか確認
  2. acf オブジェクトが存在するか確認(ACF Pro が有効か)
  3. 他プラグインによる jQuery / Select2 の競合がないか確認

既知の不具合と修正(2026-05-08)

症状: ピッカーで文字を入力して検索すると結果が表示されない、または無効化済みの商品が混入する。

原因: packages/plugins/src/cms-integration/cms-api/cms-api.plugin.tsskuSearch / productSearch / collectionSearch で、検索タームがある場合に query.where(...) を呼ぶと TypeORM QueryBuilder の仕様により事前設定の andWhere 条件(variant.enabled = true 等)が消えていた。

修正: query.where(...)query.andWhere(...) に変更し、OR 条件を括弧でくくることですべての条件が正しく AND 結合されるよう修正(fix-cms-api-search-andwhere changeset)。

キャッシュのクリア

サジェスト結果は 5 分間 WordPress の Transient キャッシュに保存されます。設定変更後もすぐに反映されない場合は、以下で Transient をクリアしてください。

just wp-shell
wp transient delete --all