Apollo Clientは、GraphQL APIから取得したデータをクライアント側の正規化キャッシュに保存し、画面の更新まで面倒を見るJavaScriptライブラリです。2026年10月時点の最新版はnpmの@apollo/clientで4.3.5(2026年10月19日公開)。2025年8月21日に出たv4で、Reactフックのimport先や接続設定の書き方が変わりました。本記事では、4.3.5とNode.js 26.5.0でキャッシュ・エラー処理・スカラー変換を検証した結果と、React・Next.js向けの実装例をもとに、基本の使い方、キャッシュの挙動、v3から移行するときの落とし穴までを解説します。GraphQL自体の仕組みはGraphQLとは?読み方・REST APIとの違いとクエリ・リゾルバの書き方で先に確認できます。
まとめ:Apollo Clientの要点(2026年10月時点)
- Apollo ClientはGraphQLのクライアント兼データ層。取得結果を
__typenameとIDの組で正規化して保存し、同じデータの再取得を省く。 - 最新は4.3.5。v4では
useQueryなどのReactフックが@apollo/client/reactへ移り、接続はlinkにHttpLinkなどの終端リンクを渡す形になった。rxjsが必須の依存に加わった。 - v3の書き方で
uriを渡すと、4.3.5では生成時にエラーにならず、最初のクエリでTypeErrorになる(実測)。移行は公式codemodから始め、setContextやエラー処理など、自動変換されない変更を手で直す。 - 4.1〜4.3で
@stream対応、ウィンドウ復帰時の自動再取得(RefetchEventManager)、カスタムスカラーの変換が加わった。 - 正規化キャッシュが要らない読み取り中心の画面では、graphql-requestとTanStack Queryを組み合わせれば、エンティティ単位のキャッシュ設計を省ける。
Apollo Clientとは:GraphQLの取得結果をキャッシュするデータ層
Apollo Clientは、GraphQLのクエリを送って結果を受け取り、その結果をアプリ全体で共有するキャッシュに書き込むライブラリです。GitHubのリポジトリは2016年2月に作られ、ライセンスはMIT、スター数は約1万9,800(2026年10月27日時点)。公式ドキュメントは自らを「GraphQLの状態管理ライブラリ」と位置づけています。
REST APIのfetchと違うのは、レスポンスを丸ごと保存しない点です。一覧画面で取った商品と詳細画面で取った商品が同じIDなら、キャッシュ上は1つのオブジェクトになります。どちらかの画面で更新すれば、もう一方の表示も書き換わります。GraphQLとRESTのどちらを選ぶかの判断はREST APIとGraphQLの違いと使い分けで扱っています。
Apollo ServerやGraphOSとの違い
「Apollo」は開発元の会社(Apollo GraphQL)の名前で、同じ名前を冠した製品が複数あります。Apollo Clientはブラウザやアプリ側でクエリを送るライブラリです。Apollo Serverはスキーマとリゾルバを実装するサーバー側のライブラリ、GraphOSはスキーマの登録や利用状況の分析を行うクラウドの管理基盤です。Apollo ClientはApollo Server以外のGraphQLサーバー(Hasura、Spring for GraphQL、graphql-yogaなど)にもそのまま接続できます。GraphOSの画面であるStudioやSandboxはApollo Studio(GraphOS Studio)とは、複数のサービスを1つのグラフにまとめる構成はGraphQLフェデレーションとはを参照してください。
Apollo Clientを使うべき場面・使わない方がよい場面
Apollo Clientの価値は正規化キャッシュにあります。同じデータを複数の画面が参照し、ユーザーの操作で頻繁に更新されるアプリ(SNS、業務SaaSの編集画面、ダッシュボード)では、更新を1か所に書けば全画面に反映されるので効果が大きくなります。
逆に、ページごとに取得して表示するだけのECサイトや社内の管理画面では、正規化キャッシュの設定(keyFieldsやmerge関数)が手間になるだけです。この場合はGraphQLの送信だけを行うgraphql-requestを、取得状態とキャッシュを持つTanStack Queryと組み合わせる方が単純に書けます。一休.comの開発チームも2022年の記事で、ECサイトや管理画面にはApollo Clientは過剰かもしれないと結論づけています。
| ライブラリ | 最新版(2026-10) | 正規化キャッシュ | 向く構成 |
|---|---|---|---|
| Apollo Client | 4.3.5 | 標準 | 更新の多いReactアプリ |
| Relay(react-relay) | 21.0.1 | 標準 | コンパイラ前提の大規模React |
| urql | 5.0.4 | Graphcacheで追加 | 軽量から始めたい場合 |
| graphql-request | 7.4.0 | なし | TanStack Queryと併用 |
Relayはコンパイラがフラグメント単位の型とクエリを生成するため、規約は多いものの大規模開発で破綻しにくい設計です。Apollo Clientで同じ規律を取り入れるなら、フラグメントをコンポーネントの横に置くColocationを採用します(GraphQLのFragment(フラグメント)とは)。
Apollo Client v4のインストールと接続設定
インストールと依存パッケージ
v4では内部のObservableがzen-observableからRxJSに変わり、rxjsがpeer dependencyになりました。公式の移行ガイドどおり、3つをまとめて入れます。4.3.5のpeer dependencyはgraphqlが16系か17系、rxjsが7.3.0以上8未満です。React連携では17・18・19系を利用できます。4.3.0からTypeScriptの最低対応は5.9になりました。
npm install @apollo/client graphql rxjs
ApolloClientとApolloProviderの初期設定
ApolloClient・InMemoryCache・HttpLink・gqlは@apollo/clientから、ApolloProviderと各フックは@apollo/client/reactから読み込みます。接続先はlinkに渡したHttpLinkのuriで指定します。
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import { ApolloProvider } from "@apollo/client/react";
import { createRoot } from "react-dom/client";
import App from "./App";
const client = new ApolloClient({
link: new HttpLink({ uri: "https://example.com/graphql" }),
cache: new InMemoryCache(),
});
createRoot(document.getElementById("root")).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
SetContextLinkによる認証ヘッダの設定
トークンをヘッダに付けるときは、SetContextLinkをHttpLinkの手前に挟みます。v3のsetContextとは引数の順番が逆で、v4では第1引数が直前のコンテキスト、第2引数が操作です。この書き換えは公式codemodの対象外なので、手で直します。
import { ApolloLink, HttpLink } from "@apollo/client";
import { SetContextLink } from "@apollo/client/link/context";
const authLink = new SetContextLink((prevContext) => {
const token = localStorage.getItem("token");
return {
headers: {
...prevContext.headers,
// 未ログイン時は Authorization ヘッダを送らない
...(token ? { authorization: `Bearer ${token}` } : {}),
},
};
});
const link = ApolloLink.from([authLink, new HttpLink({ uri: "/graphql" })]);
useQuery・useMutation・useSubscriptionによる取得・更新・通知
useQueryによるデータ取得と取得状態の判定
取得はuseQueryが基本です。戻り値のloading・error・dataで表示を分けます。v4ではdataStateが加わり、"empty"・"partial"・"streaming"・"complete"のどれかが入ります。TypeScriptではdataState === "complete"を確かめるとdataの型が絞り込まれます。
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
const GET_BOOKS = gql`
query GetBooks($author: String) {
books(author: $author) { id title author }
}
`;
function Books({ author }) {
const { loading, error, data } = useQuery(GET_BOOKS, { variables: { author } });
if (loading) return <p>読み込み中</p>;
if (error) return <p>エラー: {error.message}</p>;
return data.books.map((b) => <p key={b.id}>{b.title}</p>);
}
ボタンを押したときだけ取得したい場合はuseLazyQueryを使います。v4ではレンダー中に実行関数を呼ぶとエラーになり、SSRでの実行もできなくなりました。レンダー時に取得するならuseQueryを使います。
useMutationによるデータ更新とキャッシュへの反映
作成・更新・削除はuseMutationで行います。返り値は実行関数と結果のタプルで、レンダー時には実行されません。サーバーがid付きのオブジェクトを返せば、既存オブジェクトの更新はキャッシュに自動で反映されます。一覧への追加など、返却されたオブジェクトの自動マージだけでは更新できない場合は、updateでキャッシュを書き換えます。booksのように引数を取るフィールドは引数の値ごとに別々に保存されるため、cache.modifyでbooksを書き換えると他の著者の一覧にも追記されます。4.3.5で試すと、著者yの一覧に著者xの新刊が入りました。下の例のようにcache.updateQueryで変数を指定すれば、該当する一覧だけが更新されます。キャッシュに無い一覧はprevがnullになり、書き込まれません。
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";
const ADD_BOOK = gql`
mutation AddBook($title: String!, $author: String!) {
addBook(title: $title, author: $author) { id title author }
}
`;
function AddBook() {
const [addBook, { loading, error }] = useMutation(ADD_BOOK, {
update(cache, { data }) {
if (!data) return;
// 追加した本の著者で絞り込んだ一覧だけに追記する
cache.updateQuery(
{ query: GET_BOOKS, variables: { author: data.addBook.author } },
(prev) => prev && { books: [...prev.books, data.addBook] }
);
},
});
// 実行: addBook({ variables: { title, author } })
}
v4では実行関数の戻り値がMutateResultになり、v3のerrors(配列)はerror(単一)に統合されました。result.errorsを見ているv3のコードは修正が必要です。
useSubscriptionによるサーバー通知の受信
チャットの新着や在庫の変動を受け取るにはuseSubscriptionを使います。通信方式は2つあります。HttpLinkのままmultipart HTTPで受け取る方式と、@apollo/client/link/subscriptionsのGraphQLWsLinkでWebSocketを使う方式です。WebSocketを使う場合はgraphql-ws(4.3.5のpeer dependencyは5.5.5以上6未満、または6.0.3以上7未満)を入れ、クエリとミューテーションはHTTP、サブスクリプションはWebSocketへ振り分けます。どちらが使えるかはサーバーの対応で決まります。サーバー側の実装とスケールの設計はGraphQLサブスクリプションとはで解説しています。
import { gql } from "@apollo/client";
import { useSubscription } from "@apollo/client/react";
const BOOK_ADDED = gql`
subscription BookAdded { bookAdded { id title author } }
`;
function LiveBooks() {
const { data } = useSubscription(BOOK_ADDED);
return <p>{data?.bookAdded?.title}</p>;
}
SuspenseフックとNext.js App Routerでの使い方
Suspense対応フックの取得開始タイミングと使い分け
ReactのSuspenseと組み合わせる場合は、useQueryの代わりにSuspense対応のフックを使います。loadingの分岐を書かずに、読み込み中の表示をSuspenseのfallbackに任せられます。
| フック | 追加された版 | 使いどころ |
|---|---|---|
| useSuspenseQuery | 3.8.0(2023-08) | 描画と同時に取得 |
| useBackgroundQuery | 3.8.0(2023-08) | 親で取得を始め子で読む |
| useLoadableQuery | 3.9.0(2024-01) | クリックなどで取得を開始 |
| useReadQuery | 3.8.0(2023-08) | 上の2つの結果を読む |
useBackgroundQueryは親コンポーネントで取得を始め、受け取ったqueryRefを子に渡してuseReadQueryで読みます。子が描画されるまで取得を待たないので、親子で順番に取得が走るウォーターフォールを避けられます。
import { Suspense } from "react";
import { useSuspenseQuery } from "@apollo/client/react";
function BookList() {
const { data } = useSuspenseQuery(GET_BOOKS);
return data.books.map((b) => <p key={b.id}>{b.title}</p>);
}
export default function Page() {
return (
<Suspense fallback={<p>読み込み中</p>}>
<BookList />
</Suspense>
);
}
Next.js App Router向け統合パッケージの設定
Next.jsのApp Routerでは、公式の統合パッケージ@apollo/client-integration-nextjs(0.14.5、2026年4月9日公開)を使います。旧名は@apollo/experimental-nextjs-app-supportで、今も同じ版が並行して公開されています。サーバーコンポーネントではregisterApolloClientが返すqueryで取得し、リクエストごとに1つのクライアントを共有します。ただし、Server Componentで取得した結果は、ブラウザ側のキャッシュ更新では自動更新されません。同じデータをServer ComponentとClient Componentの双方で取得する場合は、表示の不整合を避ける設計が必要です。統合パッケージのリポジトリには、React Router向けとTanStack Start向けのパッケージもあります(2026年10月時点でそれぞれalpha版とrc版)。
import { HttpLink } from "@apollo/client";
import {
registerApolloClient,
ApolloClient,
InMemoryCache,
} from "@apollo/client-integration-nextjs";
export const { getClient, query, PreloadQuery } = registerApolloClient(() =>
new ApolloClient({
cache: new InMemoryCache(),
// SSRでは相対URLを使えないので絶対URLを書く
link: new HttpLink({ uri: "https://example.com/graphql" }),
})
);
fetchPolicyによるキャッシュとネットワークの使い分け
クエリごとにキャッシュとネットワークのどちらを優先するかはfetchPolicyで決めます。既定はcache-firstで、更新頻度の低い画面はこのままで足ります。
| fetchPolicy | 挙動 | 向く用途 |
|---|---|---|
| cache-first(既定) | キャッシュ優先、無ければ取得 | 更新頻度が低い一覧 |
| cache-and-network | キャッシュを表示しつつ再取得 | 表示速度と鮮度の両立 |
| network-only | 常に取得し結果は保存 | 残高・在庫 |
| no-cache | 取得するが保存しない | 一度きりのデータ |
| cache-only | ネットワークを使わない | キャッシュ済みの表示 |
| standby | 自動では取得しない | 手動のrefetchのみ |
タブに戻ったときやネットワーク復帰時に最新化したい場合、v4.2.0(2026年5月20日公開)からはRefetchEventManagerで自動の再取得を有効にできます。既定では無効で、CHANGELOGの例どおり、クライアントに渡したときだけ動きます。クエリ単位でrefetchOn: falseを指定すれば外せます。
import { RefetchEventManager, windowFocusSource, onlineSource } from "@apollo/client";
const client = new ApolloClient({
link,
cache: new InMemoryCache(),
refetchEventManager: new RefetchEventManager({
sources: { windowFocus: windowFocusSource, online: onlineSource },
}),
});
InMemoryCacheの正規化とキャッシュ更新の検証結果
正規化されたキャッシュの中身
4.3.5で、GraphQLサーバーの代わりに手元のスキーマを直接実行するSchemaLinkを使い、リゾルバの呼び出し回数とキャッシュの中身を数えました(Node.js 26.5.0)。
| 操作 | 結果 |
|---|---|
| 同じクエリを既定の設定で2回 | リゾルバ実行は1回 |
| 3回目をnetwork-onlyで実行 | リゾルバ実行が2回目に |
| cache.extract()のキー | Book:1、Book:2、ROOT_QUERY |
| idを選ばずに取得 | ROOT_QUERYの中に埋め込み |
| ミューテーションでtitleを変更 | 一覧クエリのキャッシュも変更後の値 |
注意したいのは4行目です。クエリでidを選ばないと、Book:1のような独立したエントリができず、一覧の結果の中に値が埋め込まれます。こうなると、別の場所に保存された同じ本のデータとは、IDによる自動更新を共有できません。正規化を効かせたい型は、どのクエリでもid(またはkeyFieldsで指定したキー)を必ず選ぶようにします。
typePoliciesによる識別キーと配列マージの設定
主キーがidや_idでない型はkeyFieldsで指定します。以下はISBNをキーにする別構成の例です。前掲コードへ適用する場合は、Bookを返すすべての選択セットにisbnも追加してください。ページネーションで結果を既存の配列に追記したいフィールドはmerge関数を書きます。以下の単純連結は、重複しないページを順番に取得する場合の例です。同じページの再取得や取得順の逆転に対応する場合は、offsetやカーソルに応じて挿入位置を制御します。既定ではすべての引数がキャッシュキーに入り、ページごとに別の値として保存されます。下の例のようにkeyArgsでauthorだけをキーにするとページが1つの配列にまとまり、そのときmerge関数が無いと後の結果で置き換わります。
const cache = new InMemoryCache({
typePolicies: {
Book: { keyFields: ["isbn"] },
Query: {
fields: {
books: {
keyArgs: ["author"],
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
});
カスタムスカラーとDateの相互変換(4.3.0)
4.3.0(2026年9月11日公開)から、InMemoryCacheのscalarsでカスタムスカラーの変換を定義できます。日時の文字列をキャッシュから読むときにDateへ変換し、変数に渡したDateは送信時に文字列へ戻ります。実測では、publishedAtがDateのインスタンスとして返りました。
import { InMemoryCache, Scalar } from "@apollo/client";
const cache = new InMemoryCache({
scalars: {
DateTime: new Scalar({
parse: (v) => new Date(v),
serialize: (v) => v.toISOString(),
is: (v) => v instanceof Date,
}),
},
typePolicies: {
Book: { fields: { publishedAt: { scalar: "DateTime" } } },
},
});
エラー処理:CombinedGraphQLErrorsとErrorLink
v4ではエラーの種類ごとにクラスが分かれました。GraphQLのエラーはCombinedGraphQLErrors、HTTPのステータス異常はServerError、JSONとして読めない応答はServerParseErrorです。判定はCombinedGraphQLErrors.is(error)のような静的メソッドで行います。リゾルバが例外を投げるスキーマで試すと、client.queryはCombinedGraphQLErrorsを投げ、errorPolicy: "all"を指定するとdata(該当フィールドはnull)とerrorが両方返りました。
全リクエスト共通のログ出力や再ログインの処理はErrorLinkに書きます。v3のonErrorはgraphQLErrorsとnetworkErrorを別々に受け取りましたが、v4ではerror1つにまとまっています。
import { CombinedGraphQLErrors } from "@apollo/client";
import { ErrorLink } from "@apollo/client/link/error";
const errorLink = new ErrorLink(({ error, operation }) => {
if (CombinedGraphQLErrors.is(error)) {
error.errors.forEach((e) =>
console.log(`[GraphQL] ${operation.operationName}: ${e.message}`)
);
} else {
console.log(`[Network] ${error.message}`);
}
});
Apollo Client 4への移行と4.1〜4.3の追加機能
v3からv4への主な変更点
v4.0.0は2025年8月21日に公開されました。公式ブログの発表によれば、ビルド対象を2023年以降のブラウザとNode.js 20以上に絞り、ESM対応を整えたことで、多くのアプリでApollo Clientがバンドルに占めるサイズが20〜30%減ります。ローカル状態管理(@client)は使うときだけLocalStateを読み込む方式になりました。
| 変更点 | v3 | v4 |
|---|---|---|
| Reactフックのimport | @apollo/client | @apollo/client/react |
| 接続先の指定 | uriを直接指定 | link(HttpLink) |
| Observable | zen-observable | RxJS(peer依存) |
| エラー | ApolloError | 種類別のクラス |
| useMutationの戻り値 | errors(配列) | error(単一) |
| ローカル状態 | resolversを直接指定 | LocalStateを渡す |
| Devtools接続 | connectToDevTools | devtools.enabled |
落とし穴は接続先の指定です。4.3.5でnew ApolloClient({ uri, cache })とv3の書き方をすると、生成時には何も起きず、最初のクエリでTypeError: Cannot read properties of undefined (reading 'request')になりました。TypeScriptなら型エラーで気づけますが、JavaScriptのままでは原因が分かりにくいエラーです。
移行は公式のcodemodで機械的に進めます。importの付け替え、uriからHttpLinkへの書き換え、connectToDevToolsの置き換えまでを自動で行います。実行にはrequireでESMを読み込めるNode.jsの版が必要です。TypeScriptのプロジェクトでは、.tsと.tsxを分けて実行するよう移行ガイドが勧めています。
npx @apollo/client-codemod-migrate-3-to-4 src
npx @apollo/client-codemod-migrate-3-to-4 --parser ts --extensions ts src
npx @apollo/client-codemod-migrate-3-to-4 --parser tsx --extensions tsx src
新規開発は最初からv4で始めます。動いているv3のアプリを急いで上げる必要はありませんが、Vue Apolloのように周辺ライブラリがv3のままの場合は、それがv4に対応するまで上げられません(後述のFAQ)。
4.1〜4.3で追加された主な機能
| 版 | 公開日 | 主な追加 |
|---|---|---|
| 4.1.0 | 2026-01-15 | @streamディレクティブに対応 |
| 4.2.0 | 2026-05-20 | RefetchEventManager、modernシグネチャ |
| 4.3.0 | 2026-09-11 | カスタムスカラー、TypeScript 5.9以上 |
4.2.0のmodernシグネチャは、TypedDocumentNodeから型を推論する新しい型定義です。従来の書き方はclassicシグネチャとしてそのまま使えます。クエリの型を手書きしないなら、GraphQL Code GeneratorでTypedDocumentNodeを生成します。Apollo Clientの移行ガイドは、client presetにはApollo Clientと相性の悪い機能が含まれるとして、プラグインを直接設定する構成を勧めています。client presetを使う場合は、公式の推奨設定に合わせます。設定の詳細はGraphQL Code Generatorとはで解説しています。GraphQLのAPI開発やフロントエンドとの連携を外部に相談したい場合は、一創のAPI開発・システム連携でも対応しています。
よくある質問
Apollo Clientの最新バージョンはいくつですか?
2026年10月27日時点で4.3.5(2026年10月19日公開)です。メジャー版のv4は2025年8月21日に4.0.0が出ました。最新はnpm view @apollo/client versionで確認できます。
Apollo Client 4でuseQueryはどこからimportしますか?
@apollo/client/reactからです。useMutation・useSubscription・useSuspenseQuery・ApolloProviderも同じです。gql・ApolloClient・InMemoryCache・HttpLinkは引き続き@apollo/clientから読み込みます。
Apollo ClientはVueやAngularでも使えますか?
使えますが、対応状況に差があります。Angular向けのapollo-angularは14.2.0が@apollo/client 4.2.3以上5未満に対応しています。Vue向けのVue Apollo(@vue/apollo-composable)は、npmで配布されている安定版4.2.2のpeer dependencyが@apollo/client 3系のままで、v4対応は5.0.0-alpha.4(2026年8月6日公開)にとどまります。Vueでv4を使うならalpha版を採用することになります。
JavaやAndroidで使えるApollo Clientはありますか?
JavaScript版のApollo ClientはJVMでは動きません。Android・Kotlin向けにはApollo Kotlinがあり、最新はv5.2.0(2026年9月16日公開)で、Kotlin Multiplatformの各ターゲットで動きます。iOSのSwift向けにはApollo iOSが別にあります。
Apollo Client Devtoolsは何ができますか?
ChromeとFirefoxの拡張機能で、開発者ツールに「Apollo」タブが加わります。公式ドキュメントによると、GraphiQLでのクエリ送信、実行中クエリとミューテーションの確認、キャッシュの検索ができます。本番環境で有効にするには、v4ではdevtools: { enabled: true }を指定します(同ページのconnectToDevToolsはv3の書き方です)。