T-CREATOR

Apollo で“キャッシュが反映されない”を 5 分で直す:ID/ポリシー/write 系の落とし穴

Apollo で“キャッシュが反映されない”を 5 分で直す:ID/ポリシー/write 系の落とし穴

Apollo Client を使っていて、「データを更新したのに画面に反映されない」「キャッシュが効いていない気がする」といった経験はありませんか?実は、Apollo のキャッシュは正しく設定しないと思わぬ落とし穴にはまってしまいます。

本記事では、Apollo Client のキャッシュが反映されない典型的な 3 つの原因(ID の設定、キャッシュポリシー、write 系操作)を取り上げ、それぞれの解決方法を具体的なコードとともに解説します。この記事を読めば、5 分でキャッシュ問題を解決できるようになるでしょう。

背景

Apollo Client は GraphQL クライアントとして、データ取得・管理を効率化するための強力なキャッシュ機構を備えています。

Apollo Client のキャッシュ機構

Apollo Client の InMemoryCache は、取得したデータを正規化してメモリ上に保存します。これにより、同じデータへの重複リクエストを防ぎ、アプリケーションのパフォーマンスを大幅に向上させることができます。

mermaidflowchart TB
  query["useQuery/<br/>クエリ実行"] --> cache{キャッシュに<br/>データあり?}
  cache -->|Yes| return1["キャッシュから<br/>データ返却"]
  cache -->|No| fetch["GraphQL API<br/>へリクエスト"]
  fetch --> normalize["データ正規化<br/>(ID ベース)"]
  normalize --> store["InMemoryCache<br/>へ保存"]
  store --> return2["データ返却"]

  mutation["useMutation/<br/>更新実行"] --> update["キャッシュ<br/>更新処理"]
  update --> store
  store --> rerender["コンポーネント<br/>再レンダリング"]

上図は Apollo Client のキャッシュフローを示しています。クエリ実行時にキャッシュを確認し、存在すれば即座に返却、なければ API リクエストを行います。

正規化キャッシュの仕組み

Apollo Client は取得したデータを __typenameid(または _id)を組み合わせたキーで正規化します。

typescript// キャッシュ内部のイメージ
const cache = {
  'User:1': {
    __typename: 'User',
    id: '1',
    name: '田中太郎',
    email: 'tanaka@example.com',
  },
  'Post:100': {
    __typename: 'Post',
    id: '100',
    title: 'Apollo の使い方',
    author: { __ref: 'User:1' }, // 参照関係も正規化
  },
};

このように、データはフラットな構造で管理され、参照関係は __ref で表現されます。

課題

Apollo Client のキャッシュが期待通りに動作しない場合、以下の 3 つの典型的な問題が考えられます。

課題 1:ID が正しく設定されていない

Apollo は __typenameid でデータを識別しますが、API が id フィールドを返さない場合や、別名(userIdproductId など)を使っている場合、正規化が正しく行われません。

課題 2:キャッシュポリシーが適切でない

fetchPolicy の設定によって、キャッシュを使うか、常にネットワークリクエストを行うかが決まります。デフォルトは cache-first ですが、状況に応じた使い分けが必要です。

課題 3:Mutation 後のキャッシュ更新漏れ

Mutation(データの作成・更新・削除)を実行しても、Apollo は自動的にすべてのキャッシュを更新してくれるわけではありません。特に、リストへの追加や削除は手動でキャッシュを更新する必要があります。

mermaidflowchart LR
  problem1["ID 未設定/<br/>カスタム ID"] --> result1["正規化失敗"]
  problem2["不適切な<br/>fetchPolicy"] --> result2["キャッシュ<br/>未使用"]
  problem3["Mutation 後<br/>更新漏れ"] --> result3["画面に<br/>反映されない"]

  result1 --> final["キャッシュが<br/>機能しない"]
  result2 --> final
  result3 --> final

これらの課題を解決しないと、キャッシュの恩恵を受けられず、パフォーマンスの低下やユーザー体験の悪化につながります。

解決策

Apollo Client のキャッシュ問題を解決するために、3 つの対策を順番に実装していきます。

解決策 1:typePolicies でカスタム ID を設定

API が返す ID フィールドが id でない場合、typePolicies を使ってカスタムキーフィールドを指定できます。

InMemoryCache の設定

まず、Apollo Client の初期化時に InMemoryCache を設定します。

typescriptimport {
  ApolloClient,
  InMemoryCache,
} from '@apollo/client';

// InMemoryCache の初期化
const cache = new InMemoryCache({
  typePolicies: {
    // User 型のキーフィールドを指定
    User: {
      keyFields: ['userId'], // id の代わりに userId を使用
    },
    // Product 型は複合キーを使用
    Product: {
      keyFields: ['productId', 'storeId'], // 複数フィールドの組み合わせ
    },
  },
});

typePolicies の中で、各型(__typename)に対してキーフィールドを明示的に指定します。これにより、Apollo は指定したフィールドで正規化を行います。

複合キーの活用

複数のフィールドを組み合わせてユニークキーを作ることもできます。

typescriptconst cache = new InMemoryCache({
  typePolicies: {
    Comment: {
      // postId と commentId の組み合わせで識別
      keyFields: ['postId', 'commentId'],
    },
    OrderItem: {
      // orderId と productId の組み合わせ
      keyFields: ['orderId', 'productId'],
    },
  },
});

この設定により、Comment:{"postId":"1","commentId":"100"} のようなキーが生成されます。

カスタム関数での動的キー生成

より複雑な条件でキーを生成したい場合は、関数を使います。

typescriptconst cache = new InMemoryCache({
  typePolicies: {
    Article: {
      keyFields: (object, context) => {
        // slug が存在すればそれを、なければ id を使用
        if (object.slug) {
          return `Article:${object.slug}`;
        }
        return `Article:${object.id}`;
      },
    },
  },
});

この方法で、データの状態に応じて柔軟にキーを生成できます。

解決策 2:適切な fetchPolicy を選択

キャッシュの挙動を制御するには、fetchPolicy を状況に応じて使い分けることが重要です。

fetchPolicy の種類と使い分け

Apollo Client には主に 5 つの fetchPolicy があります。

#ポリシー名説明使用場面
1cache-firstキャッシュ優先、なければネットワーク更新頻度が低いマスタデータ
2cache-onlyキャッシュのみ使用オフライン対応、必ずキャッシュがある場合
3network-only常にネットワークリクエスト、キャッシュ更新リアルタイム性が重要なデータ
4no-cache常にネットワークリクエスト、キャッシュ使用なしセンシティブなデータ
5cache-and-networkキャッシュを返しつつバックグラウンドで更新UX とデータ鮮度の両立

cache-first(デフォルト)の使用

最も一般的なパターンで、ユーザー情報や設定など、頻繁に変わらないデータに適しています。

typescriptimport { useQuery, gql } from '@apollo/client';

const GET_USER = gql`
  query GetUser($userId: ID!) {
    user(id: $userId) {
      userId
      name
      email
    }
  }
`;

function UserProfile({ userId }) {
  // デフォルトの cache-first を明示的に指定
  const { data, loading, error } = useQuery(GET_USER, {
    variables: { userId },
    fetchPolicy: 'cache-first', // 省略可能
  });

  if (loading) return <p>読み込み中...</p>;
  if (error) return <p>エラー: {error.message}</p>;

  return (
    <div>
      <h2>{data.user.name}</h2>
      <p>{data.user.email}</p>
    </div>
  );
}

初回はネットワークリクエストが発生しますが、2 回目以降は即座にキャッシュから表示されます。

network-only でリアルタイムデータ取得

在庫数やライブ配信の視聴者数など、常に最新のデータが必要な場合に使います。

typescriptconst GET_STOCK = gql`
  query GetStock($productId: ID!) {
    product(id: $productId) {
      productId
      name
      stockCount
    }
  }
`;

function StockDisplay({ productId }) {
  // 常に最新データを取得
  const { data, loading } = useQuery(GET_STOCK, {
    variables: { productId },
    fetchPolicy: 'network-only',
    pollInterval: 5000, // 5秒ごとに自動更新
  });

  return (
    <div>
      <p>在庫数: {data?.product.stockCount ?? '---'}</p>
    </div>
  );
}

pollInterval と組み合わせることで、定期的な自動更新も実現できます。

cache-and-network で UX を向上

キャッシュデータをまず表示し、バックグラウンドで最新データを取得します。UX とデータ鮮度の両立に最適です。

typescriptconst GET_FEED = gql`
  query GetFeed {
    posts {
      id
      title
      createdAt
    }
  }
`;

function FeedList() {
  const { data, loading, networkStatus } = useQuery(
    GET_FEED,
    {
      fetchPolicy: 'cache-and-network',
      notifyOnNetworkStatusChange: true, // ネットワーク状態の変化を通知
    }
  );

  return (
    <div>
      {/* ネットワークリクエスト中はインジケーターを表示 */}
      {networkStatus === 4 && <p>更新中...</p>}

      {data?.posts.map((post) => (
        <div key={post.id}>
          <h3>{post.title}</h3>
        </div>
      ))}
    </div>
  );
}

この設定により、ユーザーは待たされることなくコンテンツを閲覧でき、同時に最新情報も取得できます。

解決策 3:Mutation 後のキャッシュ更新

Mutation 実行後は、適切にキャッシュを更新しないと画面に反映されません。

refetchQueries で関連クエリを再取得

最もシンプルな方法は、refetchQueries で関連するクエリを再実行することです。

typescriptconst CREATE_POST = gql`
  mutation CreatePost($title: String!, $content: String!) {
    createPost(title: $title, content: $content) {
      id
      title
      content
      createdAt
    }
  }
`;

const GET_POSTS = gql`
  query GetPosts {
    posts {
      id
      title
      createdAt
    }
  }
`;

function CreatePostForm() {
  const [createPost, { loading }] = useMutation(
    CREATE_POST,
    {
      // Mutation 成功後に GetPosts クエリを再実行
      refetchQueries: [{ query: GET_POSTS }],
      awaitRefetchQueries: true, // 再取得完了を待つ
    }
  );

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createPost({
      variables: {
        title: e.target.title.value,
        content: e.target.content.value,
      },
    });
    // refetchQueries により自動的に一覧が更新される
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name='title' placeholder='タイトル' />
      <textarea name='content' placeholder='本文' />
      <button type='submit' disabled={loading}>
        投稿
      </button>
    </form>
  );
}

refetchQueries は簡単ですが、ネットワークリクエストが発生するため、大量のデータを扱う場合はパフォーマンスに注意が必要です。

update 関数で手動キャッシュ更新

より効率的な方法は、update 関数を使ってキャッシュを直接操作することです。

typescriptconst CREATE_POST = gql`
  mutation CreatePost($title: String!, $content: String!) {
    createPost(title: $title, content: $content) {
      id
      title
      content
      createdAt
    }
  }
`;

const GET_POSTS = gql`
  query GetPosts {
    posts {
      id
      title
      createdAt
    }
  }
`;

function CreatePostForm() {
  const [createPost] = useMutation(CREATE_POST, {
    update(cache, { data }) {
      // 既存のクエリ結果を取得
      const existingPosts = cache.readQuery({
        query: GET_POSTS,
      });

      // 新しい投稿を追加
      cache.writeQuery({
        query: GET_POSTS,
        data: {
          posts: [
            data.createPost, // 新規投稿を先頭に追加
            ...existingPosts.posts,
          ],
        },
      });
    },
  });

  // handleSubmit は同じ実装
}

この方法では、ネットワークリクエストなしでキャッシュを更新できるため、即座に画面に反映されます。

cache.modify でピンポイント更新

特定のフィールドだけを更新したい場合は、cache.modify を使います。

typescriptconst LIKE_POST = gql`
  mutation LikePost($postId: ID!) {
    likePost(postId: $postId) {
      id
      likeCount
      isLiked
    }
  }
`;

function LikeButton({ postId }) {
  const [likePost] = useMutation(LIKE_POST, {
    update(cache, { data }) {
      // 特定の Post オブジェクトのフィールドを更新
      cache.modify({
        id: cache.identify({
          __typename: 'Post',
          id: postId,
        }),
        fields: {
          likeCount() {
            return data.likePost.likeCount;
          },
          isLiked() {
            return data.likePost.isLiked;
          },
        },
      });
    },
  });

  return (
    <button
      onClick={() => likePost({ variables: { postId } })}
    >
      いいね
    </button>
  );
}

cache.modify は既存のオブジェクトの特定フィールドを更新するため、部分的な更新に最適です。

cache.evict で削除時のキャッシュクリア

データを削除した際は、cache.evict でキャッシュからも削除します。

typescriptconst DELETE_POST = gql`
  mutation DeletePost($postId: ID!) {
    deletePost(postId: $postId) {
      success
      message
    }
  }
`;

function DeletePostButton({ postId }) {
  const [deletePost] = useMutation(DELETE_POST, {
    update(cache, { data }) {
      if (data.deletePost.success) {
        // キャッシュから該当オブジェクトを削除
        cache.evict({
          id: cache.identify({
            __typename: 'Post',
            id: postId,
          }),
        });

        // ガベージコレクション実行(孤立した参照を削除)
        cache.gc();
      }
    },
  });

  return (
    <button
      onClick={() => deletePost({ variables: { postId } })}
    >
      削除
    </button>
  );
}

cache.evict でオブジェクトを削除し、cache.gc() で参照が切れたデータをクリーンアップします。

具体例

実際のアプリケーションで、3 つの解決策を組み合わせた実装例を見ていきましょう。

シナリオ:ブログ記事管理システム

記事の一覧表示、作成、更新、削除を行うブログシステムを想定します。

Apollo Client の初期設定

まず、プロジェクト全体の Apollo Client を設定します。

typescript// lib/apolloClient.ts
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
} from '@apollo/client';

const httpLink = new HttpLink({
  uri: 'https://api.example.com/graphql',
});

// InMemoryCache の設定
const cache = new InMemoryCache({
  typePolicies: {
    Post: {
      keyFields: ['postId'], // id の代わりに postId を使用
    },
    Comment: {
      keyFields: ['postId', 'commentId'], // 複合キー
    },
  },
});

// Apollo Client インスタンス作成
export const client = new ApolloClient({
  link: httpLink,
  cache,
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network', // デフォルトポリシー
    },
  },
});

この設定で、カスタム ID(postId)を使った正規化と、デフォルトの fetchPolicy が適用されます。

GraphQL クエリとミューテーションの定義

次に、必要なクエリとミューテーションを定義します。

typescript// graphql/posts.ts
import { gql } from '@apollo/client';

// 記事一覧取得
export const GET_POSTS = gql`
  query GetPosts($limit: Int, $offset: Int) {
    posts(limit: $limit, offset: $offset) {
      postId
      title
      excerpt
      createdAt
      author {
        userId
        name
      }
    }
  }
`;

// 記事詳細取得
export const GET_POST = gql`
  query GetPost($postId: ID!) {
    post(postId: $postId) {
      postId
      title
      content
      createdAt
      updatedAt
      author {
        userId
        name
        avatar
      }
    }
  }
`;

すべてのクエリで postId を取得するようにし、正規化が正しく行われるようにします。

記事作成の実装

記事作成時は、update 関数でキャッシュに新規記事を追加します。

typescript// components/CreatePostForm.tsx
import { useMutation } from '@apollo/client';
import { useState } from 'react';
import { GET_POSTS } from '../graphql/posts';

const CREATE_POST = gql`
  mutation CreatePost($title: String!, $content: String!) {
    createPost(title: $title, content: $content) {
      postId
      title
      excerpt
      content
      createdAt
      author {
        userId
        name
      }
    }
  }
`;

export function CreatePostForm() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  const [createPost, { loading, error }] = useMutation(
    CREATE_POST,
    {
      update(cache, { data }) {
        try {
          // 既存の記事一覧を取得
          const existingData = cache.readQuery({
            query: GET_POSTS,
            variables: { limit: 10, offset: 0 },
          });

          if (existingData) {
            // 新規記事を先頭に追加
            cache.writeQuery({
              query: GET_POSTS,
              variables: { limit: 10, offset: 0 },
              data: {
                posts: [
                  data.createPost,
                  ...existingData.posts,
                ],
              },
            });
          }
        } catch (e) {
          // キャッシュにクエリ結果がない場合はスキップ
          console.warn('Cache update skipped:', e);
        }
      },
    }
  );

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      await createPost({
        variables: { title, content },
      });

      // 入力フォームをクリア
      setTitle('');
      setContent('');
      alert('記事を作成しました');
    } catch (err) {
      console.error('作成エラー:', err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>タイトル</label>
        <input
          type='text'
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          required
        />
      </div>

      <div>
        <label>本文</label>
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          rows={10}
          required
        />
      </div>

      {error && <p>エラー: {error.message}</p>}

      <button type='submit' disabled={loading}>
        {loading ? '作成中...' : '記事を作成'}
      </button>
    </form>
  );
}

update 関数内で既存のクエリ結果を取得し、新規記事を追加することで、ネットワークリクエストなしで即座に画面に反映されます。

記事更新の実装

記事を更新する場合、Apollo は自動的にキャッシュを更新してくれますが、明示的に制御することも可能です。

typescript// components/EditPostForm.tsx
import { useMutation } from '@apollo/client';

const UPDATE_POST = gql`
  mutation UpdatePost(
    $postId: ID!
    $title: String
    $content: String
  ) {
    updatePost(
      postId: $postId
      title: $title
      content: $content
    ) {
      postId
      title
      content
      updatedAt
    }
  }
`;

export function EditPostForm({
  postId,
  initialTitle,
  initialContent,
}) {
  const [title, setTitle] = useState(initialTitle);
  const [content, setContent] = useState(initialContent);

  const [updatePost, { loading }] = useMutation(
    UPDATE_POST,
    {
      // 更新対象の記事詳細を自動更新
      update(cache, { data }) {
        cache.modify({
          id: cache.identify({
            __typename: 'Post',
            postId,
          }),
          fields: {
            title() {
              return data.updatePost.title;
            },
            content() {
              return data.updatePost.content;
            },
            updatedAt() {
              return data.updatePost.updatedAt;
            },
          },
        });
      },
    }
  );

  const handleSubmit = async (e) => {
    e.preventDefault();

    await updatePost({
      variables: { postId, title, content },
    });

    alert('記事を更新しました');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type='text'
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        rows={10}
      />
      <button type='submit' disabled={loading}>
        更新
      </button>
    </form>
  );
}

cache.modify を使うことで、特定のフィールドのみをピンポイントで更新できます。

記事削除の実装

削除時は、cache.evict でキャッシュから完全に削除します。

typescript// components/DeletePostButton.tsx
import { useMutation } from '@apollo/client';

const DELETE_POST = gql`
  mutation DeletePost($postId: ID!) {
    deletePost(postId: $postId) {
      success
      message
    }
  }
`;

export function DeletePostButton({ postId, onDeleted }) {
  const [deletePost, { loading }] = useMutation(
    DELETE_POST,
    {
      update(cache, { data }) {
        if (data.deletePost.success) {
          // キャッシュから記事を削除
          const normalizedId = cache.identify({
            __typename: 'Post',
            postId,
          });

          cache.evict({ id: normalizedId });

          // 孤立した参照をクリーンアップ
          cache.gc();

          // コールバック実行
          onDeleted?.();
        }
      },
    }
  );

  const handleDelete = async () => {
    if (!confirm('本当に削除しますか?')) {
      return;
    }

    try {
      await deletePost({ variables: { postId } });
      alert('削除しました');
    } catch (err) {
      alert('削除に失敗しました');
      console.error(err);
    }
  };

  return (
    <button onClick={handleDelete} disabled={loading}>
      {loading ? '削除中...' : '削除'}
    </button>
  );
}

cache.evictcache.gc() を組み合わせることで、キャッシュから完全に削除し、メモリリークを防ぎます。

記事一覧の表示

最後に、記事一覧を表示するコンポーネントです。

typescript// components/PostList.tsx
import { useQuery } from '@apollo/client';
import { GET_POSTS } from '../graphql/posts';

export function PostList() {
  const { data, loading, error, refetch, networkStatus } =
    useQuery(GET_POSTS, {
      variables: { limit: 10, offset: 0 },
      fetchPolicy: 'cache-and-network', // キャッシュを使いつつ最新データも取得
      notifyOnNetworkStatusChange: true,
    });

  if (loading && !data) {
    return <p>読み込み中...</p>;
  }

  if (error) {
    return (
      <div>
        <p>エラー: {error.message}</p>
        <button onClick={() => refetch()}>
          再読み込み
        </button>
      </div>
    );
  }

  return (
    <div>
      {/* ネットワーク更新中のインジケーター */}
      {networkStatus === 4 && (
        <div
          style={{ padding: '10px', background: '#f0f0f0' }}
        >
          最新データを取得中...
        </div>
      )}

      <h2>記事一覧</h2>

      {data?.posts.length === 0 ? (
        <p>記事がありません</p>
      ) : (
        <ul>
          {data?.posts.map((post) => (
            <li key={post.postId}>
              <h3>{post.title}</h3>
              <p>{post.excerpt}</p>
              <small>
                {post.author.name} -{' '}
                {new Date(
                  post.createdAt
                ).toLocaleDateString()}
              </small>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

cache-and-network ポリシーにより、キャッシュデータを即座に表示しつつ、バックグラウンドで最新データを取得します。

フロー図:記事作成から画面反映まで

記事作成時のキャッシュ更新フローを図で確認しましょう。

mermaidsequenceDiagram
  participant User as ユーザー
  participant Form as CreatePostForm
  participant Apollo as Apollo Client
  participant Cache as InMemoryCache
  participant API as GraphQL API
  participant List as PostList

  User->>Form: 記事を作成
  Form->>Apollo: createPost Mutation
  Apollo->>API: POST /graphql
  API-->>Apollo: 新規記事データ

  Note over Apollo,Cache: update 関数実行
  Apollo->>Cache: readQuery(GET_POSTS)
  Cache-->>Apollo: 既存の記事一覧
  Apollo->>Cache: writeQuery(新規記事を追加)

  Cache->>List: キャッシュ更新通知
  List->>List: 再レンダリング
  List-->>User: 新規記事が即座に表示

この図が示すように、Mutation 実行後に update 関数でキャッシュを更新することで、ネットワークリクエストなしで即座に画面に反映されます。

デバッグ時に確認すべきポイント

キャッシュが正しく動作しているか確認するために、以下のポイントをチェックしましょう。

Apollo Client DevTools の活用

Apollo Client DevTools(ブラウザ拡張機能)を使うと、キャッシュの状態をリアルタイムで確認できます。

  • Cache タブ: 正規化されたキャッシュの内容を確認
  • Queries タブ: 実行されたクエリとその結果を確認
  • Mutations タブ: 実行された Mutation を確認

キャッシュ内容のログ出力

コード内でキャッシュの内容を確認するには、cache.extract() を使います。

typescript// キャッシュの内容を確認
console.log('Current cache:', cache.extract());

// 特定のオブジェクトを読み取り
const post = cache.readFragment({
  id: 'Post:{"postId":"1"}',
  fragment: gql`
    fragment PostData on Post {
      postId
      title
      content
    }
  `,
});
console.log('Post in cache:', post);

これにより、キャッシュに期待したデータが存在するか、正しいキー形式で保存されているかを確認できます。

ネットワークリクエストの監視

開発者ツールの Network タブで、GraphQL リクエストの回数を監視しましょう。

  • 期待する動作: キャッシュが効いている場合、同じクエリを実行してもリクエストが発生しない
  • 問題のサイン: 毎回ネットワークリクエストが発生する場合、fetchPolicy や ID 設定を見直す

まとめ

Apollo Client のキャッシュが反映されない問題は、主に以下の 3 つの原因で発生します。

1. ID の設定不備

  • typePolicies でカスタムキーフィールドを指定
  • 複合キーや動的キー生成にも対応可能

2. 不適切なキャッシュポリシー

  • cache-first: 更新頻度が低いデータ
  • network-only: リアルタイム性が重要なデータ
  • cache-and-network: UX とデータ鮮度の両立

3. Mutation 後のキャッシュ更新漏れ

  • refetchQueries: シンプルだがネットワークコストあり
  • update 関数: 効率的な手動更新
  • cache.modify: ピンポイント更新
  • cache.evict: 削除時のキャッシュクリア

これらの解決策を適切に組み合わせることで、Apollo Client のキャッシュを完全に制御し、高速で快適なユーザー体験を実現できます。特に、typePolicies の設定と update 関数の実装は、キャッシュ戦略の中核となる重要なテクニックです。

キャッシュ問題に直面したら、まずは Apollo Client DevTools でキャッシュの状態を確認し、ID が正しく設定されているか、適切な fetchPolicy が選択されているかをチェックしましょう。そして、Mutation 後は必ず update 関数や refetchQueries でキャッシュを更新することを忘れないでください。

関連リンク