---
title: "GraphQLのFragment（フラグメント）とは？使い方・インラインフラグメント・Colocation・@deferを実例で解説"
url: "https://www.issoh.co.jp/tech/details/9660/"
published: 2025-11-06
updated: 2026-10-31
categories: ["GraphQL"]
publisher: "株式会社一創"
---

# GraphQLのFragment（フラグメント）とは？使い方・インラインフラグメント・Colocation・@deferを実例で解説

GraphQLのFragment（フラグメント）は、型を指定したフィールドの組に名前を付け、複数のクエリから`...名前`で差し込めるようにする仕組みです。この記事では、仕様の最新版（September 2025）の該当節とgraphql-js 17.0.2での実測をもとに、書き方、インラインフラグメント（`... on`）の使いどころ、検証で弾かれるパターン、コンポーネント単位でフラグメントを持たせるColocation、フラグメント単位で後から返させる`@defer`までを順に説明します。

## まとめ：GraphQL Fragmentの要点

- **定義**：`fragment 名前 on 型 { フィールド }`で定義し、`...名前`で展開します。展開先の型と`on`の型が重ならないと検証エラーです。
- **インラインフラグメント**：union・interfaceの返り値から型ごとにフィールドを取るときは`... on 型`を使います。型条件を省けば`@include`・`@skip`でフィールドをまとめて出し分けられます。
- **検証ルール**：未使用・名前重複・循環・存在しない型・スカラー型への定義は、どれも仕様5.5節でエラーと定められています。
- **性能**：フラグメントを使ってもサーバーが返すJSONは同じです。速くなるのは記述と保守で、通信量ではありません。
- **Colocation**：表示コンポーネントの隣にフラグメントを置き、親のクエリで1本に合成する設計です。RelayやApollo Client 3.12以降のデータマスキングと組み合わせると、親が子のフィールドへ触れなくなります。
- **@defer**：付けられるのはフラグメントの展開とインラインフラグメントだけで、フィールドには付けられません。仕様には未収録（RFC 2のDraft）で、Apollo Client 4系では`incrementalHandler`を設定しないと使えません。
- **フラグメント引数**：仕様には未収録（RFC 2のDraft）です。graphql-js 17では実験オプションでのみ解釈され、実務で使える仕組みはRelayの`@argumentDefinitions`です。

## GraphQL Fragmentの定義と書き方

フラグメントは[仕様の2.9節「Fragments」](https://spec.graphql.org/September2025/#sec-Language.Fragments)で定義されている、選択セット（フィールドの組）の再利用単位です。同じユーザー情報を一覧・詳細・コメント欄の3つのクエリで取る場面を考えると、フィールドを足すたびに3か所を直す手間がなくなります。

### 名前付きフラグメントの構文とスプレッド

`fragment`キーワード、フラグメント名、`on`に続く型条件、選択セットの順に書きます。展開するときは名前の前にドットを3つ付けます。

```
fragment UserBasic on User {
  id
  name
}

query GetUser {
  user(id: "1") {
    ...UserBasic
    email
  }
}
```

フラグメントの外にフィールドを並べてもかまいません。上の例では`UserBasic`の`id`・`name`に`email`を足しています。フラグメントの中から別のフラグメントを展開することもでき、入れ子の深さに仕様上の上限はありません。ただし循環すると検証エラーになります（後述）。

### フラグメント内での変数参照とレスポンスの形

フラグメントの中では、それを使う操作（query・mutation）で宣言された変数をそのまま参照できます。逆にいえば、フラグメントを使う操作のすべてでその変数を宣言しておく必要があります。

レスポンスにフラグメント名は残りません。graphql-js 17.0.2で上のクエリと、フラグメントを使わず`id name email`と直書きしたクエリを実行したところ、返るJSONは次の1行で、両者は1バイトも違いませんでした。

```
{"data":{"user":{"id":"1","name":"Sato","email":"sato@example.com"}}}
```

## インラインフラグメント（… on）とunion・interfaceの取り分け

インラインフラグメントは名前を持たないフラグメントで、仕様2.9.2節で定義されています。その場で`... on 型 { }`と書き、主に返り値の型が実行時まで決まらないフィールドで使います。

### union・interfaceで型ごとにフィールドを取る書き方

検索結果が`User`か`Post`のどちらかになる`union ResultItem = User | Post`では、union自体はフィールドを持ちません。`search { id }`と書くと、graphql-js 17.0.2は次のエラーを返しました。

```
Cannot query field "id" on type "ResultItem". Did you mean to use an inline fragment on "Node", "Post", or "User"?
```

型ごとに`... on`で分け、クライアント側で判定できるよう`__typename`を一緒に取ります。

```
query FindItems {
  search(q: "graphql") {
    __typename
    ... on User { name }
    ... on Post { title }
  }
}
```

```
{"data":{"search":[{"__typename":"User","name":"Sato"},{"__typename":"Post","title":"Hello"}]}}
```

interfaceの場合は共通フィールド（例：`Node`の`id`）を外側に書き、実装型ごとのフィールドだけを`... on`で取ります。型の切り分けは名前付きフラグメントでもできるので、同じ型別の取り方を複数のクエリで使うなら`fragment UserHit on User`のように名前を付けて共有します。

### 型条件なしのインラインフラグメントと@include・@skip

[仕様2.9.2節](https://spec.graphql.org/September2025/#sec-Inline-Fragments)には「TypeConditionを省略したインラインフラグメントは、外側と同じ型とみなす」とあります。これは型の切り分けではなく、複数フィールドにまとめてディレクティブを掛けるための書き方です。

```
query GetUser($full: Boolean!) {
  user(id: "1") {
    id
    ... @include(if: $full) {
      name
      email
    }
  }
}
```

この例では、nameとemailにそれぞれ`@include`を付ける代わりに、1回でまとめて指定できます。`...`記法は、型条件があれば型の切り分け、なければディレクティブのまとめ役、と覚えておけば迷いません。

### Apollo ClientのpossibleTypes設定

union・interfaceへのフラグメントは、サーバー側では問題なく動いても、Apollo Clientのキャッシュ側で設定が要ります。[公式ドキュメント（Fragments）](https://www.apollographql.com/docs/react/data/fragments)は、`InMemoryCache`の初期化時に`possibleTypes`を渡す必要があると明記しています。これはunion・interface名から、それに属する具体型の一覧への対応表です。

```
import { InMemoryCache } from "@apollo/client";

const cache = new InMemoryCache({
  possibleTypes: {
    ResultItem: ["User", "Post"],
    Node: ["User", "Post"],
  },
});
```

手書きするとスキーマに型を足したときに更新を忘れるので、公式はイントロスペクションの結果からJSONを生成するスクリプトか、GraphQL Code Generatorの`@graphql-codegen/fragment-matcher`プラグイン（2026年10月時点で7.1.0）での生成を案内しています。生成したJSONを`possibleTypes`に読み込ませ、最新のスキーマからJSONを再生成する処理をビルド前に実行すれば、スキーマの変更を反映できます。旧版の記事やApollo Client 2系の情報に出てくる`IntrospectionFragmentMatcher`は2系までの仕組みで、3系以降のコードでは使いません。Apollo Client全体の構成と4系の変更点は[Apollo Clientの使い方とv4の変更点](/tech/details/3062/)で扱っています。

## フラグメントが検証で弾かれるパターンとエラーメッセージ

フラグメントの誤りは実行前の検証段階で止まります。[仕様5.5節](https://spec.graphql.org/September2025/#sec-Validation.Fragments)の検証ルールそれぞれに違反する文書を作り、graphql-js 17.0.2（Node.js 26.5.0）の`validate()`に掛けた結果が次の表です。エラーメッセージで検索したときに、どのルールに当たっているかを引けるようにしています。

| 仕様の節    | ルール    | 違反の例             |
| ------- | ------ | ---------------- |
| 5.5.1.1 | 名前の一意性 | 同名のfragmentを2つ定義 |
| 5.5.1.2 | 型の存在   | on Usr（綴り誤り）     |
| 5.5.1.3 | 複合型のみ  | on String        |
| 5.5.1.4 | 未使用の禁止 | 定義だけで展開しない       |
| 5.5.2.1 | 展開先の定義 | 存在しない…Nopeを展開    |
| 5.5.2.2 | 循環の禁止  | AがBを、BがAを展開      |
| 5.5.2.3 | 展開の可能性 | User上でon Postを展開 |

それぞれのエラーメッセージは次のとおりです（上の表と同じ順）。

```
There can be only one fragment named "A".
Unknown type "Usr". Did you mean "User"?
Fragment "S" cannot condition on non composite type "String".
Fragment "UserBasic" is never used.
Unknown fragment "Nope".
Cannot spread fragment "A" within itself via "B".
Fragment "P" cannot be spread here as objects of type "User" can never be of type "Post".
```

クエリからフラグメントの展開を消したのに定義を残すと、そのクエリ文書全体が「未使用の禁止」違反でエラーになります。Apollo Clientで`gql`のテンプレートリテラルにフラグメント定義を埋め込んでいる場合、使わなくなった定義がそのまま本番のエラーになるので、GraphQL Code GeneratorやESLintのGraphQLプラグインのように、ビルド時にスキーマ検証を通す仕組みを入れておくべきです。

### 同じフィールドを別の引数で取るときの衝突とエイリアス

2つのフラグメントが同じフィールドを違う引数で取ると、[仕様5.3.2節「Field Selection Merging」](https://spec.graphql.org/September2025/#sec-Field-Selection-Merging)（以前の版の「Overlapping Fields Can Be Merged」）に反して検証で止まります。画像サイズを変えて同じ`avatar`を取る例です。

```
fragment SmallAvatar on User { avatar(size: 64) }
fragment LargeAvatar on User { avatar(size: 128) }

query { user(id: "1") { ...SmallAvatar ...LargeAvatar } }
```

```
Fields "avatar" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.
```

レスポンスのキーが両方とも`avatar`になり、どちらの値を入れるか決められないためです。メッセージどおり、`small: avatar(size: 64)`と`large: avatar(size: 128)`のようにエイリアスで名前を分ければ通ります。フラグメントを別々のコンポーネントで書いていると、合成した親クエリで初めてこの衝突が見つかるので、コンポーネント側のフラグメントには用途を表すエイリアスを付けておくと安全です。

## Fragment Colocation：コンポーネントごとにデータ依存を宣言する設計

Fragment Colocation（コロケーション）は、画面の部品ごとに「自分が表示に使うフィールド」をフラグメントとして部品の隣に置く設計です。ページのクエリにすべてのフィールドを直書きすると、子の部品が`email`を表示しなくなっても、親のクエリから`email`を消してよいのかを判断するには全部品を読む必要があります。Colocationでは部品とフラグメントが1対1なので、部品を消せばフラグメントも消せます。

### 親クエリへの合成と1リクエストへのまとめ方

部品ごとにクエリを発行すると、ページ表示に複数回の往復がかかります。Colocationでは部品が持つのはフラグメントだけで、ページの親が1本のクエリに合成して1回で取得します。

```
// UserCard.tsx
import { gql } from "@apollo/client";

export const USER_CARD = gql`
  fragment UserCard on User { id name avatar(size: 64) }
`;

// ProfilePage.tsx
import { gql } from "@apollo/client";
import { USER_CARD } from "./UserCard";

const PROFILE = gql`
  query Profile($id: ID!) {
    user(id: $id) { ...UserCard email }
  }
  ${USER_CARD}
`;
```

`${USER_CARD}`でフラグメント定義のDocumentNodeを補間しているのは、GraphQLの文書は自己完結している必要があり、展開する`...UserCard`の定義が同じ文書に無いと「Unknown fragment」になるためです。埋め込みを書き忘れやすい場合、Apollo Clientでは`createFragmentRegistry`でフラグメントを登録すると、文字列の埋め込みなしに名前で参照できます。

### データマスキングによる親子間のフィールド参照制限

Colocationだけでは、親コンポーネントが子のフラグメントで取ったフィールドを勝手に読めてしまいます。子が`avatar`を外したら、それを読んでいた親が壊れる、という依存が残るわけです。データマスキングは、親が受け取るデータから子のフラグメントのフィールドを隠し、子は自分のフラグメント経由でしか読めないようにする仕組みです。

| ライブラリ                                | マスキング                | 子が読む手段        |
| ------------------------------------ | -------------------- | ------------- |
| Relay（react-relay 21.0.1）            | 既定で有効                | useFragment   |
| Apollo Client（3.12以降・最新4.3.5）        | dataMasking: trueで有効 | useFragment   |
| GraphQL Code Generator client preset | 型の上で既定有効             | useFragment関数 |

Apollo Clientの`dataMasking`とclient presetのFragment Maskingは同時には使いません。Apollo Clientの公式ドキュメントはclient presetのFragment Maskingを非互換の機能としており、Apollo側のマスキングを使う設定例ではclient presetに`fragmentMasking: false`を指定し、子コンポーネントはApolloの`useFragment`で読む形にしています。

Apollo Clientの`useFragment`は3.7.0で実験版として入り、3.8.0で正式版になりました。キャッシュから読むだけでネットワークリクエストは発行しません。4.1.0（2026年1月）からは`from`に配列を渡せるようになり、一覧の各要素を1回の呼び出しでまとめて監視できます。データマスキングは3.12.0で追加され、既存アプリ向けには、全スプレッドに`@unmask`を付ける、`dataMasking`を有効にする、マスク済みの型を生成する、子コンポーネントを`useFragment`へ置き換える、という4段階の移行手順が公式ドキュメントに載っています。client presetの型マスキングの設定と注意点は[GraphQL Code Generatorのclient presetとv7の変更点](/tech/details/16021/)で詳しく扱っています。

### Colocationの導入を見送る条件と過分割の失敗例

画面が数枚で、1つのクエリを1つのコンポーネントが表示し切る構成なら、Colocationは入れない方が読みやすくなります。部品ごとのフラグメントとマスキングは、複数の場所で使う部品や、親子間の暗黙のデータ依存が多いアプリで導入効果を判断しやすい設計です。小さな管理画面で全部品にフラグメントを切ると、配置によっては、1フィールドの追加でも表示部品・フラグメント定義・親クエリを行き来する手間が増えます。

もう1つの失敗例は、フラグメントを細かく割りすぎて、同じレスポンスキーに対し、異なる引数で同じフィールドを取るフラグメントが増えることです。前章の`SmallAvatar`と`LargeAvatar`のように、別々の部品が同じ`avatar`へ異なる`size`を指定すると、合成先の親クエリで`Fields "avatar" conflict`の検証エラーが出ます。部品を独立させたはずが、合成のたびに親クエリで調整が必要になります。分割の単位は「画面上で独立して置き換わる部品」までに留めるのが目安です。

## フラグメントと性能・キャッシュの関係

フラグメントへの書き換えだけで高速化するとは限りません。今回測定したのはレスポンスの一致とクエリ文書の長さで、実行時間は測定していません。前述のとおり、レスポンスはフラグメントの有無で同一でした。保存した検証スクリプト内のクエリ文字列は、フラグメントを使わない版が42バイト、1回使う版が86バイトでした。これは掲載コードの改行・空白を含めた長さではありません。文書が短くなるのは、同じフラグメントを何度も展開したときだけです。

性能に効くのは、フラグメントそのものではなく次の2点です。

- **取るフィールドを減らす**：Colocationで各部品が本当に使うフィールドだけを宣言すると、使われないフィールドが親クエリに残りにくくなります。効くのは「不要フィールドの削除」の方です。
- **キャッシュの同一性**：Apollo Clientの正規化キャッシュはオブジェクトを`__typename`と識別用フィールドで識別します。既定では`id`または`_id`を使い、型ごとの`keyFields`で変更できます。`useFragment`の`from`で渡すオブジェクトについては、子のフラグメントではなく親のクエリまたはフラグメントでこの識別用フィールドを選択しておく必要があります。欠けているとキャッシュ上で特定できず、返る`data`は不完全になり、コンソールに警告が出ます。

サーバーの負荷には、取得フィールドの深さや数に加え、フラグメントを繰り返し展開する処理のコストも関係します。[Apollo Routerのリクエスト制限](https://github.com/apollographql/router/blob/dev/docs/source/routing/security/request-limits.mdx)では、深さ（`max_depth`）やフィールド数（`max_height`）をフラグメント内のフィールドも含めて数えるうえ、フラグメントの展開を再帰的にたどる際の選択数にも`max_recursive_selections`（既定1,000万）で上限を設けています。自前でクエリの深さや複雑度を制限する場合も、フラグメントを展開した後の形に対して掛けます。スキーマ側の設計判断は[GraphQLスキーマ設計のベストプラクティス](/tech/details/8355/)にまとめています。フラグメントの分け方を含めたAPIの設計から構築までを外部に任せたい場合は、[API開発・システム連携の支援](/service/system/api/)で相談を受け付けています。

## @deferによるフラグメント単位の遅延配信

`@defer`は、時間のかかるフィールドの到着を待たずに先にレスポンスを返させ、残りを後から送らせるディレクティブです。付けられる場所はフラグメントの展開（`...名前`）とインラインフラグメントだけで、「どこまでを後回しにするか」をフラグメントで区切る仕組みになっています。仕様の最新版には入っておらず、提案はgraphql-specの[PR #1110（Incremental Delivery）](https://github.com/graphql/graphql-spec/pull/1110)で、2026年10月時点のステージはRFC 2（Draft）です。

### graphql-js 17.0.2での書き方と返り方

graphql-js 17.0.2は`@defer`の定義（`GraphQLDeferDirective`）を持っていますが、標準のディレクティブ一覧（`specifiedDirectives`）には含めていません。スキーマに宣言しないまま使うと`Unknown directive "@defer".`で検証に落ちました。SDLで宣言し、実行には`experimentalExecuteIncrementally()`を使います。

```
directive @defer(if: Boolean! = true, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT

query Profile {
  user(id: "1") {
    id
    name
    ...Reviews @defer(label: "rv")
  }
}

fragment Reviews on User { reviews }
```

1行目はスキーマ側のSDLに書く宣言で、2行目以降がクライアントから送るクエリです。`reviews`の解決に50ミリ秒かかるリゾルバで実行すると、最初の応答には`id`と`name`だけが入り、`reviews`は2回目の応答で届きました。

```
{"data":{"user":{"id":"1","name":"A"}},"pending":[{"id":"0","path":["user"],"label":"rv"}],"hasNext":true}
{"hasNext":false,"incremental":[{"id":"0","data":{"reviews":["good","bad"]}}],"completed":[{"id":"0"}]}
```

インラインフラグメント（`... @defer(label: "rv") { reviews }`）でも同じ2回の応答になりました。弾かれたのは次の2つです。

```
Directive "@defer" may not be used on FIELD.
Value for arguments "defer(label:)" and "stream(label:)" must be unique across all Defer/Stream directive usages.
```

1つ目は`reviews @defer`のようにフィールドへ直接付けた場合、2つ目は同じ`label`を2か所で使った場合です。後回しにしたいフィールドが1つでも、型条件なしのインラインフラグメントで包めば付けられます。

### Apollo Clientで@deferの応答を受ける設定

Apollo Client 4.0.0では、`@defer`の応答の解釈を`incrementalHandler`オプションで選ぶ方式になりました。既定の`NotImplementedHandler`は、`@defer`を含む操作を送ると例外を投げます。上の`pending`・`completed`を含む新しい形式を受けるには4.1.0で追加された`GraphQL17Alpha9Handler`を、Apollo Routerが返す従来の形式には`Defer20220824Handler`を指定します。サーバーと違う形式のハンドラを選ぶと正しく組み立てられないので、まずサーバーがどちらの形式を返すかを確かめます。

```
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import { GraphQL17Alpha9Handler } from "@apollo/client/incremental";

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: new HttpLink({ uri: "/graphql" }),
  incrementalHandler: new GraphQL17Alpha9Handler(),
});
```

部品側では、3.13.0で追加された`useSuspenseFragment`を使うと、後回しにしたフラグメントのデータがそろうまでその部品だけをSuspenseで待たせられます。公式ドキュメントは、`from`でキャッシュ上の対象を特定するための識別用フィールドは`@defer`の外に置くよう注意しています。Apollo Routerを使ったサブグラフ構成で`@defer`を通す場合は、[GraphQLフェデレーションのサブグラフ合成とルーターの実装](/tech/details/16181/)も合わせて確認してください。

## フラグメント引数（Fragment Arguments）の現状

フラグメントの中で使う値を展開側から渡したい、という要望はGraphQLで長く議論されています。2026年10月時点の状況は次のとおりです。

- **仕様**：最新版のSeptember 2025にも、2026年9月28日付のWorking Draftにも、フラグメント引数の構文はありません。提案はgraphql-specの[PR #1224](https://github.com/graphql/graphql-spec/pull/1224)で、ステージはRFC 2（Draft）、推進役を募る「Needs Champion」のラベルが付いています。
- **graphql-js 17系**：2026年6月15日に正式公開された17.0.0から、フラグメント引数を実験機能として実装しています。17.0.2では`parse()`に`experimentalFragmentArguments: true`を渡したときだけ解釈します。渡さないと`Syntax Error: Expected "on", found "(".`で止まります。
- **Relay**：`@argumentDefinitions`と`@arguments`というディレクティブで、仕様の外側で同じことを実現しています。

```
fragment Avatar($size: Int = 64) on User {
  avatar(size: $size)
}

query Q { user(id: "1") { ...Avatar(size: 128) } }
```

graphql-js 17.0.2で上の文書を実験オプション付きで実行すると、`size`を渡せば`img_128.png`、省略すれば既定値の`img_64.png`が返りました。ただし、上の例のように**フラグメント定義を操作より前に書くと、`validate()`が検証エラーではなく例外（TypeError）で落ちました**。操作を先に書けば検証も実行も通ります。仕様に入っていない機能で、実装側もこの程度に未成熟なので、本番のクエリには使わず、必要ならRelayのディレクティブで代替するのが現実的です。

## よくある質問

### フラグメントとインラインフラグメントの違いは何ですか？

名前を付けて定義し複数か所から`...名前`で再利用するのがフラグメント、名前を付けずにその場で`... on 型`と書くのがインラインフラグメントです。インラインフラグメントは主にunion・interfaceの型ごとの取り分けと、`@include`・`@skip`をまとめて掛ける用途で使います。

### フラグメントは.graphqlファイルに分けて管理できますか？

できます。GraphQL Code Generatorは`documents`に指定した`.graphql`ファイルやソース中の`gql`からフラグメントを集めて型を生成します。webpackで`.graphql`ファイルを直接読み込む場合、Apollo Clientの公式ドキュメントは`graphql-tag/loader`の`#import`文で別ファイルのフラグメントを取り込む方法を案内しています。

### gqlタグの中で別のフラグメントを使うにはどうしますか？

gqlで作成したフラグメント定義のDocumentNodeを、`${USER_CARD}`のようにテンプレートリテラルへ補間します。埋め込まないと同じ文書に定義が無いため「Unknown fragment」の検証エラーになります。Apollo Clientでは`createFragmentRegistry`に登録すれば、埋め込みなしで名前参照できます。

### フラグメントを使うとレスポンスは軽くなりますか？

軽くなりません。graphql-js 17.0.2の実測では、レスポンスは直書きのクエリと1バイトも変わりませんでした。軽くなるのは取得フィールドを減らしたときです。

### フラグメントの中で別のフラグメントを使えますか？

使えます。フラグメントの選択セットに`...OtherFragment`を書けば入れ子にできます。ただしAがBを、BがAを展開するような循環は仕様5.5.2.2節で禁止されており、`Cannot spread fragment "A" within itself via "B".`のエラーになります。

## 関連記事

- [GraphQLとは？読み方・REST APIとの違いとクエリ・リゾルバの書き方をわかりやすく解説](/tech/details/2924/)
- [Apollo Clientとは？使い方・キャッシュの仕組みとv4（最新4.3）の変更点を解説](/tech/details/3062/)
- [GraphQL Code Generatorとは？client presetの設定とv7の変更点を実装目線で解説](/tech/details/16021/)
- [GraphQLスキーマ設計のベストプラクティス｜null・命名・ミューテーション・非推奨化の判断基準](/tech/details/8355/)
- [GraphQLフェデレーションとは？サブグラフ合成とルーターの実装・採用判断を解説](/tech/details/16181/)

---

出典: [GraphQLのFragment（フラグメント）とは？使い方・インラインフラグメント・Colocation・@deferを実例で解説](<https://www.issoh.co.jp/tech/details/9660/>)（株式会社一創）
