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 は取得したデータを __typename と id(または _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 は __typename と id でデータを識別しますが、API が id フィールドを返さない場合や、別名(userId、productId など)を使っている場合、正規化が正しく行われません。
課題 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 があります。
| # | ポリシー名 | 説明 | 使用場面 |
|---|---|---|---|
| 1 | cache-first | キャッシュ優先、なければネットワーク | 更新頻度が低いマスタデータ |
| 2 | cache-only | キャッシュのみ使用 | オフライン対応、必ずキャッシュがある場合 |
| 3 | network-only | 常にネットワークリクエスト、キャッシュ更新 | リアルタイム性が重要なデータ |
| 4 | no-cache | 常にネットワークリクエスト、キャッシュ使用なし | センシティブなデータ |
| 5 | cache-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.evict と cache.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 でキャッシュを更新することを忘れないでください。
関連リンク
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 の実測比較:スループット/遅延/運用性
articleNext.js を Bun で動かす開発環境:起動速度・互換性・落とし穴
articleObsidian Properties 速見表:型・表示名・テンプレ連携の実例カタログ
articleNuxt useHead/useSeoMeta 定番スニペット集:OGP/構造化データ/国際化メタ
articleMermaid で描ける図の種類カタログ:flowchart/class/state/journey/timeline ほか完全整理
articleMCP サーバーを活用した AI チャットボット構築:実用的な事例と実装
articleNginx 変数 100 選:$request_id/$upstream_status/$ssl_protocol ほか即戦力まとめ
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 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来