Apollo Client のキャッシュ初期化戦略:既存データ注入・rehydration・GC 設定
Apollo Client でキャッシュを初期化する方法を学ぶことで、ページ遷移やリロード時のユーザー体験を大きく向上できます。今回は、既存データをキャッシュに注入する方法、サーバーサイドレンダリング(SSR)時の rehydration、そしてガベージコレクション(GC)の設定について、実践的なコード例とともに詳しく解説していきます。
背景
Apollo Client のキャッシュとは
Apollo Client は GraphQL クライアントとして、取得したデータを自動的にキャッシュする機能を持っています。このキャッシュは InMemoryCache として実装されており、正規化されたデータストアとして機能します。
キャッシュを適切に初期化することで、以下のようなメリットが得られるのです。
- 初回レンダリング時のデータ表示が高速化される
- ネットワークリクエストの削減によるパフォーマンス向上
- ユーザーが以前閲覧したデータの即座な表示
- サーバーサイドレンダリング(SSR)との統合が可能になる
キャッシュ初期化が必要になるケース
Apollo Client のキャッシュ初期化は、以下のようなシナリオで特に重要になります。
Next.js などの SSR 環境での利用
サーバー側で取得したデータをクライアント側のキャッシュに引き継ぐ必要があります。これにより、ページ読み込み時に既にデータが表示された状態を実現できますね。
永続化されたキャッシュの復元
LocalStorage や IndexedDB に保存したキャッシュデータを、アプリケーション起動時に復元することで、オフライン対応やページリロード時の体験が向上します。
テスト環境でのモックデータ注入
テストコードにおいて、特定の状態を再現するために初期データをキャッシュに設定する場面があります。
以下の図は、Apollo Client のキャッシュが動作する基本的なフローを示しています。
mermaidflowchart TB
component["React<br/>コンポーネント"] -->|useQuery| client["Apollo<br/>Client"]
client -->|キャッシュ確認| cache["InMemory<br/>Cache"]
cache -->|キャッシュなし| network["GraphQL<br/>API"]
network -->|レスポンス| cache
cache -->|データ返却| component
cache -->|キャッシュあり| component
このように、Apollo Client はキャッシュを中心としたデータフローを構築します。
課題
キャッシュ初期化における一般的な課題
Apollo Client のキャッシュを初期化する際には、いくつかの技術的な課題に直面することがあります。
データの正規化形式への変換
Apollo Client のキャッシュは正規化されたデータ構造を持ちます。通常の JSON データをそのまま注入しても、期待通りに動作しない場合があるのです。
SSR と CSR の状態同期
サーバーサイドで生成されたキャッシュデータを、クライアントサイドで正確に復元する必要があります。この際、データの形式や型の不一致が問題になることがあります。
メモリ管理とパフォーマンス
大量のデータをキャッシュに保持し続けると、メモリ消費が増大します。適切なガベージコレクション(GC)設定がないと、アプリケーションのパフォーマンスが低下してしまいますね。
タイミングの問題
キャッシュの初期化を行うタイミングが適切でないと、コンポーネントのレンダリングとデータの準備が競合する可能性があります。
以下の図は、これらの課題がどのように関連しているかを示しています。
mermaidflowchart LR
init["キャッシュ<br/>初期化"] --> norm["正規化<br/>形式"]
init --> sync["SSR/CSR<br/>同期"]
init --> gc["メモリ<br/>管理"]
norm -->|形式不一致| error1["データ取得<br/>失敗"]
sync -->|状態不整合| error2["hydration<br/>エラー"]
gc -->|設定不足| error3["メモリ<br/>リーク"]
これらの課題を理解することで、適切な初期化戦略を選択できるようになります。
解決策
キャッシュへの既存データ注入
Apollo Client では、writeQuery と writeFragment という 2 つの主要な API を使ってキャッシュにデータを注入できます。それぞれの使い方を見ていきましょう。
writeQuery によるクエリ単位のデータ注入
writeQuery は、GraphQL クエリと対応するデータをキャッシュに書き込むメソッドです。
typescriptimport {
ApolloClient,
InMemoryCache,
gql,
} from '@apollo/client';
// Apollo Client のインスタンスを作成
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache(),
});
次に、クエリを定義してデータを注入します。
typescript// GraphQL クエリの定義
const GET_USER_QUERY = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
実際にキャッシュへデータを書き込む処理は以下のようになります。
typescript// キャッシュにデータを注入
client.writeQuery({
query: GET_USER_QUERY,
variables: {
id: '123',
},
data: {
user: {
__typename: 'User', // 型名は必須
id: '123',
name: '田中太郎',
email: 'tanaka@example.com',
},
},
});
重要なポイント: __typename フィールドは Apollo Client がデータを正規化するために必須です。このフィールドを省略すると、キャッシュが正常に機能しない場合があります。
writeFragment による部分的なデータ更新
writeFragment は、特定のオブジェクトの一部だけを更新する際に便利です。
typescriptimport { gql } from '@apollo/client';
// フラグメントの定義
const USER_FRAGMENT = gql`
fragment UserInfo on User {
id
name
email
}
`;
フラグメントを使ってキャッシュに書き込みます。
typescript// 特定のユーザーデータを更新
client.writeFragment({
id: 'User:123', // キャッシュID(型名:ID)
fragment: USER_FRAGMENT,
data: {
__typename: 'User',
id: '123',
name: '田中次郎', // 名前だけを更新
email: 'tanaka@example.com',
},
});
この方法は、既存のキャッシュデータの一部だけを更新したい場合に効率的です。
SSR における rehydration 戦略
Next.js などの SSR 環境では、サーバーで取得したデータをクライアントに引き継ぐ必要があります。この処理を「rehydration(再水和)」と呼びます。
サーバーサイドでのキャッシュ抽出
まず、サーバーサイドで Apollo Client を初期化し、データを取得します。
typescriptimport {
ApolloClient,
InMemoryCache,
HttpLink,
} from '@apollo/client';
import { GetServerSideProps } from 'next';
// サーバーサイド用の Apollo Client を作成
function createApolloClient() {
return new ApolloClient({
ssrMode: true, // SSR モードを有効化
link: new HttpLink({
uri: 'https://api.example.com/graphql',
credentials: 'same-origin',
}),
cache: new InMemoryCache(),
});
}
Next.js の getServerSideProps でデータを取得し、キャッシュを抽出します。
typescriptexport const getServerSideProps: GetServerSideProps =
async (context) => {
const apolloClient = createApolloClient();
// クエリを実行してデータを取得
await apolloClient.query({
query: GET_USER_QUERY,
variables: { id: '123' },
});
// キャッシュの状態を抽出
const apolloState = apolloClient.cache.extract();
return {
props: {
apolloState, // クライアントに渡す
},
};
};
cache.extract() メソッドは、キャッシュの内部状態を JSON 形式で取り出します。
クライアントサイドでのキャッシュ復元
次に、クライアントサイドで受け取ったキャッシュデータを復元します。
typescriptimport {
ApolloClient,
InMemoryCache,
HttpLink,
} from '@apollo/client';
// クライアントサイド用の Apollo Client を作成
function createApolloClient(initialState = {}) {
return new ApolloClient({
ssrMode: false, // クライアントサイドでは false
link: new HttpLink({
uri: 'https://api.example.com/graphql',
credentials: 'same-origin',
}),
cache: new InMemoryCache().restore(initialState), // キャッシュを復元
});
}
Next.js のページコンポーネントで Apollo Client を初期化します。
typescriptimport { ApolloProvider } from '@apollo/client';
import type { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
// サーバーから渡された状態でクライアントを初期化
const client = createApolloClient(
pageProps.apolloState || {}
);
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default MyApp;
この仕組みにより、サーバーで取得したデータがクライアントのキャッシュに即座に反映され、初回レンダリング時からデータが表示されます。
以下の図は、SSR における rehydration の流れを示しています。
mermaidsequenceDiagram
participant Server as Next.js<br/>Server
participant API as GraphQL<br/>API
participant Browser as Browser
participant Client as Apollo<br/>Client
Server->>API: クエリ実行
API->>Server: データ返却
Server->>Server: cache.extract()
Server->>Browser: HTML + apolloState
Browser->>Client: cache.restore(apolloState)
Client->>Browser: データ表示(即座)
このように、サーバーとクライアント間でキャッシュ状態をシームレスに引き継ぐことができます。
ガベージコレクション(GC)の設定
Apollo Client のキャッシュは、使われなくなったデータを自動的に削除する GC 機能を持っています。適切に設定することで、メモリ使用量を最適化できますね。
typePolicies による GC の制御
InMemoryCache の初期化時に、各型のキャッシュポリシーを設定できます。
typescriptimport { InMemoryCache } from '@apollo/client';
const cache = new InMemoryCache({
typePolicies: {
User: {
// User 型のキーフィールドを指定
keyFields: ['id'],
// フィールドごとのポリシー設定
fields: {
friends: {
merge(existing = [], incoming: any[]) {
return [...existing, ...incoming];
},
},
},
},
},
});
evict と gc メソッドの使用
特定のオブジェクトをキャッシュから削除するには evict メソッドを使います。
typescript// 特定のユーザーをキャッシュから削除
cache.evict({
id: 'User:123', // 削除対象のキャッシュID
});
削除操作の後、孤立したオブジェクトを一括削除するために gc を実行します。
typescript// ガベージコレクションを実行
// 参照されていないオブジェクトをすべて削除
const removedIds = cache.gc();
console.log('削除されたオブジェクト数:', removedIds.length);
自動 GC の設定例
定期的にガベージコレクションを実行する仕組みを作ることもできます。
typescriptimport { useEffect } from 'react';
import { useApolloClient } from '@apollo/client';
function useAutoGC(intervalMs: number = 60000) {
const client = useApolloClient();
useEffect(() => {
// 定期的に GC を実行(デフォルト: 1分ごと)
const timerId = setInterval(() => {
const removed = client.cache.gc();
if (removed.length > 0) {
console.log(
`GC実行: ${removed.length}個のオブジェクトを削除`
);
}
}, intervalMs);
// クリーンアップ
return () => clearInterval(timerId);
}, [client, intervalMs]);
}
このカスタムフックをアプリケーションのルートコンポーネントで使用します。
typescriptfunction App() {
// 1分ごとに自動 GC を実行
useAutoGC(60000);
return <div>{/* アプリケーションのコンテンツ */}</div>;
}
possibleTypes の設定
Union 型や Interface を使用している場合、possibleTypes の設定が重要になります。
typescriptconst cache = new InMemoryCache({
possibleTypes: {
// SearchResult は Book または Author の可能性がある
SearchResult: ['Book', 'Author'],
// Node インターフェースの実装型
Node: ['User', 'Post', 'Comment'],
},
typePolicies: {
Query: {
fields: {
search: {
// 検索結果のマージポリシー
merge(existing, incoming) {
return incoming;
},
},
},
},
},
});
この設定により、Apollo Client が Union 型や Interface を正しく識別し、適切にキャッシュできるようになります。
具体例
実践例:ユーザーダッシュボードのキャッシュ初期化
実際のアプリケーションを想定して、ユーザーダッシュボードのキャッシュ初期化を実装してみましょう。
プロジェクトのセットアップ
まず、必要なパッケージをインストールします。
bashyarn add @apollo/client graphql
yarn add -D @types/node
Apollo Client の設定ファイル作成
プロジェクト全体で使用する Apollo Client の設定を作成します。
typescript// lib/apolloClient.ts
import {
ApolloClient,
InMemoryCache,
HttpLink,
NormalizedCacheObject,
} from '@apollo/client';
let apolloClient: ApolloClient<NormalizedCacheObject> | null =
null;
// Apollo Client を作成する関数
function createApolloClient(
initialState: NormalizedCacheObject = {}
) {
const httpLink = new HttpLink({
uri:
process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT ||
'http://localhost:4000/graphql',
credentials: 'include', // Cookie を含める
});
return new ApolloClient({
ssrMode: typeof window === 'undefined', // サーバーサイドか判定
link: httpLink,
cache: new InMemoryCache({
typePolicies: {
User: {
keyFields: ['id'],
},
Post: {
keyFields: ['id'],
},
},
}).restore(initialState),
});
}
次に、クライアントのシングルトンを管理する関数を追加します。
typescript// Apollo Client のシングルトンを取得・初期化
export function initializeApollo(
initialState: NormalizedCacheObject = {}
) {
// サーバーサイドでは毎回新しいインスタンスを作成
const _apolloClient =
apolloClient ?? createApolloClient(initialState);
// クライアントサイドでは既存のキャッシュとマージ
if (initialState && apolloClient) {
const existingCache = apolloClient.cache.extract();
// 既存のキャッシュと新しいデータをマージ
apolloClient.cache.restore({
...existingCache,
...initialState,
});
}
// SSR では常に新しいクライアントを返す
if (typeof window === 'undefined') {
return _apolloClient;
}
// CSR では同じインスタンスを再利用
if (!apolloClient) {
apolloClient = _apolloClient;
}
return _apolloClient;
}
GraphQL クエリの定義
ダッシュボードで使用するクエリを定義します。
typescript// graphql/queries.ts
import { gql } from '@apollo/client';
// ユーザー情報を取得するクエリ
export const GET_CURRENT_USER = gql`
query GetCurrentUser {
currentUser {
id
name
email
avatar
stats {
postsCount
followersCount
followingCount
}
}
}
`;
ダッシュボードの投稿一覧を取得するクエリも定義します。
typescript// ユーザーの投稿一覧を取得するクエリ
export const GET_USER_POSTS = gql`
query GetUserPosts($userId: ID!, $limit: Int = 10) {
userPosts(userId: $userId, limit: $limit) {
id
title
content
createdAt
likes {
count
}
comments {
count
}
}
}
`;
Next.js ページコンポーネントの実装
SSR を使用したダッシュボードページを実装します。
typescript// pages/dashboard.tsx
import { GetServerSideProps } from 'next';
import { ApolloProvider } from '@apollo/client';
import { initializeApollo } from '../lib/apolloClient';
import {
GET_CURRENT_USER,
GET_USER_POSTS,
} from '../graphql/queries';
import DashboardContent from '../components/DashboardContent';
interface DashboardPageProps {
apolloState: any;
}
export default function DashboardPage({
apolloState,
}: DashboardPageProps) {
// クライアントサイドで Apollo Client を初期化
const client = initializeApollo(apolloState);
return (
<ApolloProvider client={client}>
<DashboardContent />
</ApolloProvider>
);
}
サーバーサイドでデータを取得する処理を実装します。
typescriptexport const getServerSideProps: GetServerSideProps =
async (context) => {
const apolloClient = initializeApollo();
try {
// 並列でデータを取得
const [userResult, postsResult] = await Promise.all([
apolloClient.query({
query: GET_CURRENT_USER,
}),
apolloClient.query({
query: GET_USER_POSTS,
variables: {
userId: '123', // 実際は認証情報から取得
limit: 10,
},
}),
]);
// キャッシュの状態を抽出
return {
props: {
apolloState: apolloClient.cache.extract(),
},
};
} catch (error) {
console.error('データ取得エラー:', error);
// エラー時は空の状態を返す
return {
props: {
apolloState: {},
},
};
}
};
ダッシュボードコンポーネントの実装
実際にデータを表示するコンポーネントを作成します。
typescript// components/DashboardContent.tsx
import { useQuery } from '@apollo/client';
import {
GET_CURRENT_USER,
GET_USER_POSTS,
} from '../graphql/queries';
export default function DashboardContent() {
// キャッシュから即座にデータを取得(SSR で注入済み)
const { data: userData, loading: userLoading } = useQuery(
GET_CURRENT_USER
);
const { data: postsData, loading: postsLoading } =
useQuery(GET_USER_POSTS, {
variables: {
userId: userData?.currentUser?.id,
limit: 10,
},
skip: !userData?.currentUser?.id, // ユーザーIDがない場合はスキップ
});
if (userLoading) {
return <div>ユーザー情報を読み込み中...</div>;
}
return (
<div className='dashboard'>
<header className='user-info'>
<img
src={userData.currentUser.avatar}
alt='アバター'
/>
<h1>{userData.currentUser.name}</h1>
<p>{userData.currentUser.email}</p>
<div className='stats'>
<span>
投稿: {userData.currentUser.stats.postsCount}
</span>
<span>
フォロワー:{' '}
{userData.currentUser.stats.followersCount}
</span>
<span>
フォロー中:{' '}
{userData.currentUser.stats.followingCount}
</span>
</div>
</header>
<section className='posts'>
<h2>最近の投稿</h2>
{postsLoading ? (
<div>投稿を読み込み中...</div>
) : (
<PostList posts={postsData.userPosts} />
)}
</section>
</div>
);
}
投稿一覧を表示するサブコンポーネントも作成します。
typescriptinterface Post {
id: string;
title: string;
content: string;
createdAt: string;
likes: { count: number };
comments: { count: number };
}
function PostList({ posts }: { posts: Post[] }) {
return (
<ul className='post-list'>
{posts.map((post) => (
<li key={post.id} className='post-item'>
<h3>{post.title}</h3>
<p>{post.content.substring(0, 100)}...</p>
<div className='post-meta'>
<span>いいね: {post.likes.count}</span>
<span>コメント: {post.comments.count}</span>
<time>
{new Date(post.createdAt).toLocaleDateString(
'ja-JP'
)}
</time>
</div>
</li>
))}
</ul>
);
}
キャッシュの手動更新機能
ユーザーが「いいね」ボタンをクリックした際のキャッシュ更新を実装します。
typescript// hooks/useLikePost.ts
import {
useMutation,
gql,
useApolloClient,
} from '@apollo/client';
const LIKE_POST_MUTATION = gql`
mutation LikePost($postId: ID!) {
likePost(postId: $postId) {
id
likes {
count
}
}
}
`;
export function useLikePost() {
const client = useApolloClient();
const [likePost, { loading }] = useMutation(
LIKE_POST_MUTATION,
{
// 楽観的 UI 更新
optimisticResponse: (variables) => ({
likePost: {
__typename: 'Post',
id: variables.postId,
likes: {
__typename: 'LikeStats',
count: 0, // 実際の値は update で計算
},
},
}),
// キャッシュを手動更新
update(cache, { data }) {
if (!data?.likePost) return;
const postId = `Post:${data.likePost.id}`;
// 既存のデータを読み取る
const existingPost = cache.readFragment({
id: postId,
fragment: gql`
fragment PostLikes on Post {
id
likes {
count
}
}
`,
});
if (existingPost) {
// いいね数をインクリメント
cache.writeFragment({
id: postId,
fragment: gql`
fragment PostLikes on Post {
id
likes {
count
}
}
`,
data: {
...existingPost,
likes: {
...existingPost.likes,
count: existingPost.likes.count + 1,
},
},
});
}
},
}
);
return { likePost, loading };
}
以下の図は、このダッシュボードアプリケーションにおけるキャッシュの流れを示しています。
mermaidflowchart TB
ssr["SSR<br/>getServerSideProps"] -->|並列クエリ実行| api["GraphQL<br/>API"]
api -->|ユーザー情報| cache1["Cache<br/>currentUser"]
api -->|投稿一覧| cache2["Cache<br/>userPosts"]
cache1 & cache2 -->|extract| state["apolloState<br/>(JSON)"]
state -->|props| browser["Browser"]
browser -->|restore| clientCache["Client<br/>Cache"]
clientCache -->|即座に表示| ui["UI<br/>コンポーネント"]
ui -->|いいねクリック| mutation["Mutation"]
mutation -->|update| clientCache
図で理解できる要点:
- SSR でサーバー側のキャッシュを構築し、JSON として抽出
- クライアント側でキャッシュを復元し、即座に UI に反映
- Mutation 実行時はキャッシュを直接更新して、UI を同期
LocalStorage を使った永続化の例
キャッシュを LocalStorage に保存し、ページリロード後も復元する実装例です。
typescript// lib/persistCache.ts
import {
ApolloClient,
NormalizedCacheObject,
} from '@apollo/client';
const CACHE_KEY = 'apollo-cache-persist';
// キャッシュを LocalStorage に保存
export function saveCache(
client: ApolloClient<NormalizedCacheObject>
) {
try {
const data = client.cache.extract();
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
console.log('キャッシュを保存しました');
} catch (error) {
console.error('キャッシュ保存エラー:', error);
}
}
// キャッシュを LocalStorage から復元
export function loadCache(): NormalizedCacheObject | null {
try {
const data = localStorage.getItem(CACHE_KEY);
if (data) {
console.log('キャッシュを復元しました');
return JSON.parse(data);
}
} catch (error) {
console.error('キャッシュ復元エラー:', error);
}
return null;
}
// 古いキャッシュをクリア
export function clearCache() {
try {
localStorage.removeItem(CACHE_KEY);
console.log('キャッシュをクリアしました');
} catch (error) {
console.error('キャッシュクリアエラー:', error);
}
}
この永続化機能をアプリケーションに統合します。
typescript// pages/_app.tsx
import { useEffect } from 'react';
import { ApolloProvider } from '@apollo/client';
import { initializeApollo } from '../lib/apolloClient';
import { saveCache, loadCache } from '../lib/persistCache';
import type { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
// 保存されたキャッシュを読み込む
const savedCache =
typeof window !== 'undefined' ? loadCache() : null;
// Apollo Client を初期化(保存されたキャッシュと SSR の状態をマージ)
const client = initializeApollo({
...savedCache,
...pageProps.apolloState,
});
useEffect(() => {
// ページ遷移時にキャッシュを保存
const handleBeforeUnload = () => {
saveCache(client);
};
window.addEventListener(
'beforeunload',
handleBeforeUnload
);
// 定期的にキャッシュを保存(5分ごと)
const intervalId = setInterval(() => {
saveCache(client);
}, 5 * 60 * 1000);
return () => {
window.removeEventListener(
'beforeunload',
handleBeforeUnload
);
clearInterval(intervalId);
};
}, [client]);
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default MyApp;
ガベージコレクションの実装例
メモリ管理を最適化するための GC 実装です。
typescript// hooks/useCacheManagement.ts
import { useEffect, useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
interface CacheManagementOptions {
gcInterval?: number; // GC 実行間隔(ミリ秒)
maxCacheSize?: number; // 最大キャッシュサイズ(バイト)
enableLogging?: boolean; // ログ出力の有効化
}
export function useCacheManagement(
options: CacheManagementOptions = {}
) {
const {
gcInterval = 60000, // デフォルト: 1分
maxCacheSize = 5 * 1024 * 1024, // デフォルト: 5MB
enableLogging = false,
} = options;
const client = useApolloClient();
// キャッシュサイズを計算
const getCacheSize = useCallback(() => {
const cacheData = client.cache.extract();
const size = new Blob([JSON.stringify(cacheData)]).size;
return size;
}, [client]);
// ガベージコレクションを実行
const runGC = useCallback(() => {
const beforeSize = getCacheSize();
const removed = client.cache.gc();
const afterSize = getCacheSize();
if (enableLogging) {
console.log('GC実行結果:', {
removedCount: removed.length,
beforeSize: `${(beforeSize / 1024).toFixed(2)} KB`,
afterSize: `${(afterSize / 1024).toFixed(2)} KB`,
freed: `${((beforeSize - afterSize) / 1024).toFixed(
2
)} KB`,
});
}
return removed;
}, [client, getCacheSize, enableLogging]);
// キャッシュサイズをチェックして必要なら GC
const checkAndCleanCache = useCallback(() => {
const currentSize = getCacheSize();
if (currentSize > maxCacheSize) {
if (enableLogging) {
console.warn(
`キャッシュサイズが上限を超過: ${(
currentSize /
1024 /
1024
).toFixed(2)} MB`
);
}
runGC();
}
}, [getCacheSize, maxCacheSize, runGC, enableLogging]);
useEffect(() => {
// 定期的に GC を実行
const gcTimerId = setInterval(() => {
runGC();
}, gcInterval);
// 定期的にキャッシュサイズをチェック
const checkTimerId = setInterval(() => {
checkAndCleanCache();
}, gcInterval / 2);
return () => {
clearInterval(gcTimerId);
clearInterval(checkTimerId);
};
}, [gcInterval, runGC, checkAndCleanCache]);
return {
getCacheSize,
runGC,
checkAndCleanCache,
};
}
このカスタムフックをアプリケーションで使用します。
typescript// components/Layout.tsx
import { useCacheManagement } from '../hooks/useCacheManagement';
export default function Layout({
children,
}: {
children: React.ReactNode;
}) {
// キャッシュ管理を有効化
const { getCacheSize, runGC } = useCacheManagement({
gcInterval: 60000, // 1分ごと
maxCacheSize: 5 * 1024 * 1024, // 5MB
enableLogging: process.env.NODE_ENV === 'development',
});
return (
<div className='layout'>
<header>
<nav>{/* ナビゲーション */}</nav>
{process.env.NODE_ENV === 'development' && (
<div className='debug-panel'>
<button
onClick={() => {
const size = getCacheSize();
alert(
`現在のキャッシュサイズ: ${(
size / 1024
).toFixed(2)} KB`
);
}}
>
キャッシュサイズ確認
</button>
<button
onClick={() => {
const removed = runGC();
alert(
`${removed.length}個のオブジェクトを削除しました`
);
}}
>
手動 GC 実行
</button>
</div>
)}
</header>
<main>{children}</main>
</div>
);
}
まとめ
Apollo Client のキャッシュ初期化戦略について、実践的な内容を解説してきました。
既存データの注入では、writeQuery と writeFragment を使ってキャッシュにデータを書き込む方法を学びました。これにより、アプリケーションの起動時やテスト時に必要なデータを事前に準備できます。
SSR における rehydration では、Next.js のようなフレームワークでサーバーサイドのキャッシュをクライアントに引き継ぐ方法を実装しました。cache.extract() と cache.restore() を組み合わせることで、シームレスなデータ受け渡しが実現できますね。
ガベージコレクション設定では、evict と gc メソッドを活用したメモリ管理の最適化手法を紹介しました。定期的な GC 実行やキャッシュサイズの監視により、アプリケーションのパフォーマンスを維持できます。
これらのテクニックを適切に組み合わせることで、以下のような効果が得られます。
| # | 項目 | 効果 |
|---|---|---|
| 1 | 初回表示速度 | SSR と rehydration により、初回レンダリング時からデータを表示 |
| 2 | ネットワーク削減 | キャッシュの活用により、不要な API リクエストを削減 |
| 3 | オフライン対応 | LocalStorage との組み合わせで、オフライン時もデータ表示が可能 |
| 4 | メモリ効率 | GC による適切なメモリ管理で、長時間の使用でも安定動作 |
| 5 | 開発効率 | テスト時のモックデータ注入が容易になり、開発速度が向上 |
Apollo Client のキャッシュ機能を最大限に活用することで、ユーザー体験の向上とアプリケーションのパフォーマンス最適化を同時に実現できるのです。
関連リンク
articleApollo Client のキャッシュ初期化戦略:既存データ注入・rehydration・GC 設定
articleApollo のキャッシュ思想を俯瞰する:正規化・型ポリシー・部分データの取り扱い
articleApollo GraphOS を用いた安全なリリース運用:Schema Checks/Launch Darkly 的な段階公開
articleApollo で“キャッシュが反映されない”を 5 分で直す:ID/ポリシー/write 系の落とし穴
articleApollo で BFF(Backend for Frontend)最適化:画面別スキーマと Contract Graph の併用
articleApollo Router と Node 製 Gateway の実測比較:スループット/遅延/運用性
articleLangChain 再ランキング手法の実測:Cohere/OpenAI ReRank/Cross-Encoder の効果
articleJotai 非同期で Suspense が発火しない問題の切り分けガイド
articleJest moduleNameMapper 早見表:パスエイリアス/静的アセット/CSS を一網打尽
articleComfyUI ワークフロー設計 101:入力 → 前処理 → 生成 → 後処理 → 出力の責務分離
articleGitHub Copilot でリファクタ促進プロンプト集:命名・抽象化・分割・削除の誘導文
articleCodex で既存コードを読み解く:要約・設計意図抽出・依存関係マップ化
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来