2026年10月7日

2026年10月7日

WPGraphQLでネストしたデータを効率よく取得する方法

WPGraphQL を使うと、投稿・著者・カテゴリー・アイキャッチ画像のように関連するデータを、1回のリクエストで入れ子のまま取得できます。効率よく取るコツは、「必要なフィールドだけを書く」「入れ子の各階層で件数を絞る」「一覧と詳細でクエリを分ける」の3点です。この記事では、ネストしたクエリの書き方、件数とページ送りの扱い、重いクエリの見つけ方とキャッシュまでを順に説明します。

前提:WPGraphQL のデータの形

WPGraphQL は WordPress に GraphQL API を追加するプラグインです。有効化すると標準では /graphql がエンドポイントになり、管理画面の GraphQL メニューにある GraphiQL IDE でクエリを試せます。プラグインの設定画面や対応バージョンは更新で変わることがあるため、導入前にプラグインページの更新状況を確認してください(2026年10月現在)。

ネストしたデータを扱うときに知っておきたいのが「コネクション」という考え方です。投稿の一覧や、投稿に付いたカテゴリーの一覧のように「複数のものへのつながり」は、次の形で表されます。

  • nodes:つながっているもの自体の一覧
  • edges:つながりの情報(カーソルなど)を含む一覧。普段は nodes で足ります
  • pageInfo:次のページがあるか、次に読み込む位置はどこか

投稿の著者やアイキャッチ画像のように「1つのものへのつながり」も、author { node { ... } } のように node を挟んで書きます。REST API の感覚で author { name } と書くとエラーになるので、ここが最初のつまずきどころです。

基本:一覧ページ用のネストしたクエリ

ブログの記事一覧に必要なデータを、1回で取得するクエリです。

query PostList($first: Int!, $after: String) {
  posts(first: $first, after: $after, where: { status: PUBLISH }) {
    nodes {
      databaseId
      title
      date
      uri
      excerpt
      author {
        node {
          name
        }
      }
      categories(first: 3) {
        nodes {
          name
          slug
        }
      }
      featuredImage {
        node {
          sourceUrl(size: MEDIUM)
          altText
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

変数は {"first": 10} のように渡します。REST API で同じ情報を集めようとすると、投稿一覧に加えて著者やメディアの情報を追加で問い合わせるか、_embed で余分なデータまで受け取ることになりがちです。GraphQL では、書いたフィールドだけが返ってきます。

このクエリで意識している点は次のとおりです。

  • **本文(content)を取らない**:一覧に本文は不要です。本文はデータ量が大きいので、詳細ページ用のクエリで取ります
  • 入れ子のコネクションにも件数を付ける:categories(first: 3) のように、内側の一覧にも上限を指定します。指定しないと既定の件数まで取りに行きます
  • 画像はサイズを指定する:sourceUrl(size: MEDIUM) で表示に合うサイズの URL だけを取ります

入れ子が深くなるときの考え方

GraphQL では入れ子をいくらでも書けますが、データ量は「外側の件数 × 内側の件数」で増えます。たとえば投稿20件それぞれにコメント20件、さらに各コメントの返信を取ると、1回のリクエストで数百件のデータを組み立てることになります。

query PostWithComments($id: ID!) {
  post(id: $id, idType: DATABASE_ID) {
    title
    content
    comments(first: 20, where: { parent: 0 }) {
      nodes {
        databaseId
        content
        date
        author {
          node {
            name
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

この例では、1件の投稿の詳細ページで、トップレベルのコメントだけを20件取っています。返信まで一度に取らず、「返信を表示」を押したときに別のクエリで取る、といった分け方をすると、最初の表示が軽くなります。where で使える条件はスキーマのバージョンによって変わることがあるので、GraphiQL IDE のスキーマ表示(ドキュメント)で、使っている版の引数を確認してください。

件数の上限とページ送り

WPGraphQL は、コネクション1つあたりで一度に返す件数に上限を設けており、既定では100件です。first: 500 と書いても100件までしか返りません。上限は graphql_connection_max_query_amount フィルターで変えられますが、上げるほど1回のリクエストが重くなります。全件を取りたい場合は、上限を上げるより、pageInfo の hasNextPage と endCursor を使ってページ送りで分けて取るのが安全です。

async function fetchAllPosts(endpoint, query) {
  const all = [];
  let after = null;
  do {
    const res = await fetch(endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ query, variables: { first: 50, after } }),
    });
    const json = await res.json();
    if (json.errors) {
      throw new Error(json.errors[0].message);
    }
    const { nodes, pageInfo } = json.data.posts;
    all.push(...nodes);
    after = pageInfo.hasNextPage ? pageInfo.endCursor : null;
  } while (after);
  return all;
}

GraphQL は、エラーがあっても HTTP ステータス 200 で返すことが多く、本文の errors を確認しないと失敗に気づけません。上のコードでは errors があれば例外にしています。

内部の取得処理:N+1 問題への備え

ネストしたデータで心配になるのが、投稿1件ごとに著者やカテゴリーを個別に問い合わせてしまう「N+1 問題」です。WPGraphQL は、DataLoader と呼ばれる仕組みで、同じ種類のデータをまとめて読み込むように作られています。そのため、標準の投稿・ユーザー・タームなどのフィールドであれば、入れ子にしただけで問い合わせが爆発的に増えることは起きにくくなっています。

注意が必要なのは、自分で追加したフィールドです。register_graphql_field() で独自フィールドを追加し、その resolve 関数の中で毎回 get_post_meta() や WP_Query を呼ぶと、投稿の件数だけ処理が繰り返されます。投稿メタは WordPress が投稿一覧の取得時にまとめてキャッシュすることが多いものの、独自のクエリや外部 API を呼ぶ処理は件数分だけ実行されます。

<?php
add_action( 'graphql_register_types', function () {
	register_graphql_field( 'Post', 'readingMinutes', array(
		'type'        => 'Int',
		'description' => '本文の文字数から計算した読了目安(分)',
		'resolve'     => function ( $post ) {
			$minutes = get_post_meta( $post->databaseId, 'reading_minutes', true );
			return '' === $minutes ? null : absint( $minutes );
		},
	) );
} );

この例では、重い計算を resolve 関数の中で行わず、保存時に計算してメタに入れておいた値を読むだけにしています。一覧で表示される可能性のあるフィールドは、このように「読むだけ」で済む形にしておくと、件数が増えても負荷が伸びにくくなります。

ACF のフィールドを GraphQL で使う場合は、WPGraphQL 向けの ACF 連携プラグイン(WPGraphQL for ACF)を使う方法があります。対応する ACF の版や設定方法は更新で変わるため、プラグインの公式ドキュメントで確認してください。

重いクエリの見つけ方

遅いクエリを直すには、どこが重いのかを先に確かめます。WPGraphQL の設定画面には、デバッグや、処理時間を記録するトレース、実行された SQL を記録するクエリログに関する設定があります(項目の表記は版によって異なります)。ステージング環境で有効にし、レスポンスに含まれる情報で、どのフィールドの処理に時間がかかっているかを確認します。本番では無効に戻してください。

確認するときは、次の順で疑うと原因にたどり着きやすくなります。

  1. 件数の多い入れ子:外側と内側の first の掛け算で、データ量が膨らんでいないか
  2. 独自フィールドの resolve 関数:件数分だけ重い処理を繰り返していないか
  3. 不要な大きいフィールド:一覧で content や大きな画像のデータまで取っていないか
  4. キャッシュが効いていない:同じクエリを毎回 WordPress で処理していないか

キャッシュと悪用への備え

GraphQL は POST で送ることが多く、そのままでは CDN やページキャッシュに乗りにくい性質があります。WPGraphQL Smart Cache は、GET リクエストでのクエリのキャッシュや、投稿の更新に合わせたキャッシュの無効化を扱うプラグインです。対応しているホスティングや CDN、設定方法はプラグインの説明を確認してください。

公開している GraphQL エンドポイントには、誰でも深い入れ子のクエリを送れます。WPGraphQL の設定には、クエリの深さを制限する項目があり、深すぎるクエリを拒否できます。あわせて、Web サーバーや WAF でエンドポイントへのリクエスト数を制限しておくと、意図的に重いクエリを繰り返し送られた場合の影響を抑えられます。変更前にはバックアップを取り、ステージング環境で自分のフロントエンドが使うクエリが制限に引っかからないことを確かめてください。

まとめ

  • 単数・複数のつながりは node と nodes で表す。author { node { name } } の形に慣れる
  • 一覧では本文を取らず、入れ子のコネクションにも first を付けて件数を絞る
  • コネクションの既定の上限は100件。全件はページ送り(endCursor)で分けて取る
  • 標準フィールドは DataLoader でまとめて読み込まれる。独自フィールドの resolve 関数は「読むだけ」にする
  • トレースやクエリログで重い箇所を確かめ、Smart Cache と深さ制限、リクエスト制限で守る

お気軽にご相談ください

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