スクレイピングを画面遷移しても続くようにする
ヤスイミセの管理画面で「全スーパーのチラシをスクレイピングする」ボタンを押すと、店舗 × チラシ枚数で 5-10 分くらいかかるジョブが走る。最初の実装では「ボタン押したらモーダル出してその場で polling」だったんだけど、これだと モーダルを閉じた瞬間に進捗が消える し、リロードしたら走り直しになる。明らかに使いにくい。
ジョブの状態を context に持ち上げて、jobId を localStorage に置いて、リロード後に勝手に polling を再開する形に直した。これだけで運用負荷が一気に下がった。
ジョブ本体は API 側、フロントは ID だけ持つ
ジョブのキューと進捗管理はバックエンド側に置く。フロントが持つのは jobId 1 つだけ。
const STORAGE_KEY = 'yasui-mise.scrape-job-id';
function writeStoredJobId(jobId: string | null) {
try {
if (jobId) window.localStorage.setItem(STORAGE_KEY, jobId);
else window.localStorage.removeItem(STORAGE_KEY);
} catch {
// Safari プライベートブラウズなどで localStorage が落ちても無視
}
}
途中の結果まで localStorage に持つかは悩んだけど、結局やめた。jobId だけにしておけば、リロード後にサーバーから最新を引っ張ってくれば良い。フロントに二重管理させない。
マウント時に進行中ジョブを拾う
Provider がマウントしたら localStorage に jobId があるか見て、あれば polling を再開する。
useEffect(() => {
const storedId = readStoredJobId();
if (storedId) startPolling(storedId);
return () => stopPolling();
}, [startPolling, stopPolling]);
これだけで、画面遷移・リロード・タブを閉じて開き直し、どのパターンでもジョブの状態が「戻ってくる」。
終わったら ID を消す
completed / failed が来た瞬間に localStorage の ID を消す。次回マウント時に「終わったジョブ」を polling し続ける状態を避けたい。
if (status.status === 'completed' || status.status === 'failed') {
stopPolling();
if (status.status === 'completed' && status.results) {
setScrapeResults(status.results);
}
writeStoredJobId(null);
}
ついでに、polling 中に 404 が返ってきた場合(古い jobId が API 側で消えてる)も同じ扱いにする。これを忘れると「永遠に 404 を polling」する罠にハマる。
catch (error) {
console.error('ジョブステータス取得エラー:', error);
stopPolling();
setJobStatus(null);
writeStoredJobId(null);
}
polling 間隔は 2 秒
const POLLING_INTERVAL_MS = 2000;
雰囲気で決めた。長いジョブだから 5 秒でも良い気はしたけど、進捗バーが「止まってる感」を出すと余計なサポート問い合わせが来る予感がしたので、2 秒に短めに振った。サーバー側も管理画面の数アクセスなら問題なし。
「画面閉じたら走り直し」って、開発中は気にならないけど、運用 1 週間で必ず気になる種類の UX 問題。先に潰しておくと、後で「画面遷移して大丈夫?」って気を遣わずに使えるようになる。