🔴 問題: サーバー上のJSONファイルを更新しても、React Nativeアプリに反映されない
React Nativeアプリで外部JSONファイルからデータを取得する際、サーバー側でファイルを更新しても、アプリ側では古いデータが表示され続ける問題に遭遇しました。RSSフィードは正常に更新されるのに、なぜJSONファイルだけ更新されないのか?その原因と解決方法を共有します。
📋 状況整理
正常に動作していたもの
- ✅ RSSフィード(XML形式) → サーバー更新が即座に反映
更新されなかったもの
- ❌ 外部JSONファイル → サーバー更新しても反映されない
🔍 原因分析
1. データの取得方法に問題があった
当初、データはアプリ内に静的にインポートされていました:
// ❌ 問題のあるコード
import { dataList } from '../data/localfile';
// このデータはビルド時に埋め込まれるため、
// アプリを再ビルドしない限り更新されない
2. RSSとJSONの違い
| 比較項目 | RSS(XML) | JSON(静的ファイル) |
|---|---|---|
| キャッシュ制御 | 配信元が適切に設定 | サーバー設定次第 |
| 更新頻度 | 配信元が自動更新 | 手動更新 |
| ブラウザの扱い | 動的コンテンツ | 静的ファイルとして強くキャッシュ |
結論: JSONファイルは静的ファイルとして強くキャッシュされていたため、更新されなかった
✅ 解決策
ステップ1: 動的なデータ取得に変更
// ✅ 改善後のコード
const [dataList, setDataList] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://example.com/data.json');
const data = await response.json();
setDataList(data);
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
ステップ2: HTTPキャッシュ対策(重要!)
しかし、これだけではまだキャッシュ問題は解決しません。以下の2つの対策が必要です:
① URLにタイムスタンプを追加(キャッシュバスト)
const timestamp = new Date().getTime();
const response = await fetch(
`https://example.com/data.json?_=${timestamp}`
);
仕組み: タイムスタンプを付けることで、毎回「異なるURL」として認識され、キャッシュが回避される
② HTTPヘッダーでキャッシュ制御
const response = await fetch(url, {
method: 'GET',
headers: {
'Cache-Control': 'no-cache', // プロキシキャッシュを無効化
'Pragma': 'no-cache' // HTTP/1.0互換
}
});
完成版のコード
const [dataList, setDataList] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
// キャッシュバスト用のタイムスタンプ
const timestamp = new Date().getTime();
const response = await fetch(
`https://example.com/data.json?_=${timestamp}`,
{
method: 'GET',
headers: {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
}
);
if (!response.ok) {
throw new Error('データ取得失敗');
}
const data = await response.json();
setDataList(data);
setLoading(false);
} catch (err) {
console.error('Error:', err);
setError(err.message);
setLoading(false);
}
};
fetchData();
}, []);
🎯 技術的に重要なポイント
1. importとfetchの根本的な違い
| 方式 | タイミング | 更新方法 |
|---|---|---|
import |
ビルド時 | アプリ再ビルドが必要 |
fetch |
実行時 | サーバー更新のみでOK |
2. キャッシュバストの注意点
デメリット: 毎回新しいリクエストになるため、サーバー負荷が増加する可能性がある
代替案: 更新頻度が低い場合は、時間単位やバージョン番号でキャッシュ管理
// 1時間ごとにキャッシュを更新
const timestamp = Math.floor(Date.now() / (1000 * 60 * 60));
// または、バージョン番号で管理
const version = "v1.0";
fetch(`url?v=${version}`);
3. React Nativeでの非同期データ取得のベストプラクティス
- 3つの状態を管理: データ、ローディング、エラー
- try-catch-finally構文: 必ずfinallyでローディング状態を解除
- 空の依存配列: useEffect第2引数に
[]を渡し、マウント時のみ実行
📊 結果
✅ 成功:
- サーバーのJSONファイルを更新するだけで、アプリに即座に反映
- アプリの再ビルド・再配布が不要に
- 運用コストが大幅に削減
🔧 サーバー側での追加対応(推奨)
アプリ側だけでなく、サーバー側でもキャッシュ制御を設定すると、より確実です:
Apache (.htaccess)
<FilesMatch "\.(json)$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0
</FilesMatch>
Nginx
location ~* \.json$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
}
📝 まとめ
- 静的インポートは避ける: ビルド時に埋め込まれるため、更新にはアプリ再配布が必要
- 動的取得にはキャッシュ対策が必須: タイムスタンプ追加とヘッダー設定の両方を実施
- サーバー側でも対応: より確実にキャッシュを制御できる
- 更新頻度に応じた最適化: 負荷とリアルタイム性のバランスを考慮
この対応により、JSONファイルの更新がリアルタイムでアプリに反映されるようになり、運用が大幅に改善されました。同じ問題に直面している方の参考になれば幸いです。
