なぜウィジェットを貼らないのか
よくある作り方は、フィード配信サービスが用意した JavaScript のタグを貼る「埋め込みウィジェット方式」です。 貼るだけで動くので手軽ですが、代わりに次のものを引き受けることになります。
- 閲覧者のブラウザが、ページ表示のたびに第三者のサーバーへ通信する
- その通信が終わるまで枠が空で、後から中身が入ってレイアウトがずれる
- 提供元が止まれば、そのままサイトの一部が欠ける
- 表示回数がそのまま利用料に効いてくる料金体系が多い
このサイトでは、ウィジェットを貼らずに 公開する前に取得を終えておく 方式を採っています。 閲覧者が見るのは、ただの静的な HTML と自サイトの画像だけです。 全体アーキテクチャで書いた 「公開する前に全部作り終えておく」という方針を、SNS連携にも通した形になります。
仕組み:毎朝すくって、リポジトリに貯める
- 1
毎朝すくう
朝6時、GitHub Actions が Instagram の最新6件を取りに行きます。サイトのビルドとは切り離してあります。
- 2
画像をダウンロードして貯める
まだ持っていない投稿だけ画像を落とし、リポジトリに保存します。最大30件まで貯め、溢れた分は古いものから消します。
- 3
新しい投稿があった日だけ公開
差分が無い日は何もしません。投稿があった日だけコミットが生まれ、それを合図にサイトが組み直されます。
- 4
静的HTMLに焼き込み
貯めた画像とキャプションがHTMLに組み込まれます。閲覧者のブラウザは外部に何も取りに行きません。
- 5
取れなかった日は、前回のまま
取得に失敗しても止まりません。リポジトリに貯めた分がそのまま表示され続けます。
要点は 「閲覧者のブラウザには一切の外部通信をさせない」 ことです。 Instagram と通信するのは朝6時の自動処理だけで、しかも公開の前に終わっています。 そのため表示は速く、レイアウトのずれも起きず、Instagram 側が不調でもページは何事もなく表示されます。
罠その1:画像のURLは数日で切れる
Instagram の API が返す画像URLは、有効期限つきの署名がついた一時的なものです。 これを HTML にそのまま書き込むと、公開直後は正常に見えるのに、数日後には キャプションだけが残って画像が全部消えます。 データの取得は成功し続けているので、エラーもログも出ません。気づくのは目視した時だけです。
この対策が、上の手順2「画像をダウンロードして貯める」です。 画像そのものを落として自サイトから配信すれば、期限つきURLはどこにも残りません。
どこから取ってくるか
ここまでが「器」の話です。器を決めたうえで、投稿データの取得元をどこにするかという別の問題が残ります。 当初は Meta(Facebook)の Graph API を自分で直接叩いていましたが、これには アクセストークンが約60日で失効し、そのたびに手作業で更新する必要がある という弱点があります。 失効しても画面上はエラーにならず、フィードが静かに止まるので、気づきにくいのも厄介です。
そこで3案を比較し、C案(Behold)に移行しました。
| 比較項目 | A. Graph API 直 旧方式 自前のMetaアプリと自前トークンで、Meta の API を直接叩く | B. Graph API 直+自動更新 A に、トークンを自動で再発行する仕組みを足す | C. Behold JSON 採用 取得とトークン管理を外部サービスに任せ、その JSON を読む |
|---|---|---|---|
| 取得元 | Graph API 直 | Graph API 直 | Behold JSON |
| 画像 | 自前ホスト /ig/ | 同左 | 同左(ダウンロード元が変わるだけ) |
| トークンの更新 | 60日ごとに手動 | 自動 | 不要(サービス側が管理) |
| Meta開発者アプリの維持 | 必要 | 必要 | 不要 |
| 費用 | 0円 | 0円 | 0円(無料枠:6投稿/月1,200リクエスト) |
| 外部への依存 | Meta のみ | Meta のみ | Meta + Behold |
| 実装の差分 | — | 約40行+月1回の定期実行 | 約30行(取得URLと項目名の対応づけ) |
| 副次的な効果 | — | — | 代替テキスト・ハッシュタグ除去済みキャプション・WebP最適化 |
| 主なリスク | 失効すると無言で止まる | 60日以上デプロイが無いと失効する | 無料枠の縮小やサービス終了 |
色を敷いた2行が、判断を分けた軸です。
決め手はトークンではなく Meta開発者アプリを維持し続けるかどうか でした。 B案でトークンを自動更新しても、APIバージョンの強制的な切り替え、権限まわりの仕様変更、 Facebookページとの連携切れといった「いつか壊れる要因」は残ります。 年に一度そういう対応が発生すると考えると、そこを専業のサービスに預けるほうが、 自前のコードで守り続けるより安く付く、と判断しました。
この記事を公開した翌日に、旧方式が実際に壊れた
ここからは、書いた直後に現実になった話です。 上の比較表で、A案のリスクを「失効すると無言で止まる」と書きました。 その翌日、トップページから Instagramのフィードが丸ごと消えていました。
エラーページも、空の枠も出ません。セクションごと、何事もなかったかのように無くなります。 アクセス解析にも異常は出ません。気づいたのは、たまたま自分のサイトを見ていたときでした。
罠その2:「失敗しても前回のデータを使う」が、効いていなかった
取得スクリプトには、最初から「取得に失敗してもビルドを止めず、前回のデータをそのまま残す」 という安全装置を書いてありました。ところが、これがまったく機能していませんでした。
理由は2つが噛み合っていたためです。ひとつは、ダウンロードした画像とデータを 「毎回作り直す一時ファイル」とみなして、リポジトリの管理対象から外していたこと。 もうひとつは、ビルドが毎回まっさらな状態から始まること。 つまり「残すべき前回のデータ」が、そもそも存在しなかったのです。 1回でも取得に失敗すれば、その時点で全損する構造でした。
フォールバックを書いたことと、フォールバックが効くことは別物だった、という話です。 これは Instagram に限らず、外部から取ってきて静的化する処理すべてに当てはまります。
生簀(いけす)方式 ― 6件しか取れないのに、30件出せる理由
乗り換え先のBeholdは、無料プランで取得できるのが 最新6件だけ です。 しかも7件目に押し出された投稿は、あとから取りに行くことができません。 窓は常に「最新6件」の位置にあり、過去に向かって開くことはないからです。
そこで、毎朝その6件をすくってリポジトリに貯めます。 すでに持っている投稿は飛ばし、新しいものだけを足していく。 こうして日を追うごとに手持ちが増え、最大30件まで貯まります。 生簀が満杯になったら、古いものから逃がします。
そして、この「貯める」がそのまま罠その2の対策になっています。 貯める先はリポジトリなので、画像もデータも当然コミット対象です。 取得に失敗した日は、前回までに貯めた分がそのまま表示され続けます。 今度は「残すべき前回のデータ」が本当に存在します。
ついでに、有料プラン(月$10・50件)に上げずに表示できる母数が増えました。 無料枠の制約を、設計で回した形です。
無料枠は「取得回数」で数える
Beholdの無料枠は月1,200リクエストですが、これはデータを取りに行った回数で数えます。 ウィジェットを貼ると訪問者1人につき1回消費するので、 1日40人見に来ただけで月末を待たずに止まります。 一方この方式なら、1日1回すくうだけなので月30回程度。桁が2つ違います。 無料枠に収まっているのは、けちっているからではなく叩く回数が根本的に少ないからです。
現在の構成:C案(Behold)+ 生簀方式。毎朝6時に最新6件をすくい、最大30件まで蓄積。 新しい投稿があった日だけサイトを組み直します。トークンの手動更新は不要になりました。