ソースコードをビルドしてオブジェクトストレージ経由で配信する流れと、異なるバージョンの画面を表示している2台のモニターのイラスト

CDN のキャッシュを削除しても直らない ── 静的エクスポート構成でフロントエンドだけが古いまま残る問題

リリース直後に「本棚が開けない」という問い合わせが届きました。本棚は購入済み作品の一覧を表示する画面です。リリース前から開いたままのタブで操作を続けたケースでした。ブラウザが読み込んでいるフロントエンドは古く、バックエンドだけが新しい状態です。 CDNのキャッシュはデプロイ時に削除済みです。それでも直りませんでした。 原因はキャッシュが1層ではなかったことです。CDNのパージが届かないキャッシュを、ブラウザ側に2種類残していました。加えて、フロントエンドとバックエンドのバージョンがそろわない状態への備えも足りていませんでした。 先に結論を3点書きます。 CDNのキャッシュ削除が消せるのはエッジまでで、各ユーザーのブラウザHTTPキャッシュには効かない 静的エクスポートしたHTMLにCache-Controlを付けないと、ブラウザが独自の基準でキャッシュする キャッシュ制御とバージョン不整合の検知は、片方だけ実装しても機能しない 前提とする構成 担当しているサービスのフロントエンドは、次の構成で動いています。 Next.js 13.4.7の静的エクスポート(TypeScript、Pages Router) 成果物をオブジェクトストレージ(Alibaba Cloud OSS)に配置し、CDNで配信 APIはGraphQL(graphql-requestと自動生成SDK) バックエンドはフロントエンドと別リポジトリで、デプロイも別 flowchart LR Build["ビルド<br/>next build → 静的エクスポート"] --> OSS["オブジェクトストレージ<br/>Alibaba Cloud OSS"] OSS --> CDN["CDN<br/>エッジキャッシュ"] CDN --> Browser["ブラウザ<br/>HTTPキャッシュ / sessionStorage"] Browser -->|"GraphQL + Client-Version"| API["バックエンド<br/>別リポジトリ・別デプロイ"] 重要なのは、HTMLとJavaScriptがサーバー側でレンダリングされない点です。ブラウザが取得したHTMLと、そこから読み込むJavaScriptチャンクの組み合わせが、そのユーザーにとっての「アプリのバージョン」です。この組み合わせが古いまま固定されると、リリース内容が届きません。 何が起きたか 報告された症状は3つです。 リリース前から開いていたタブで本棚を開くと、画面が壊れる ログアウトして再ログインするまで、リリース内容が反映されない 既存のタブから派生した別のタブで、前のバージョンのデータが表示される 3つには共通点がありました。全ユーザーには起きておらず、CDNのキャッシュを削除しても直りませんでした。そしてログアウトすると直りました。 CDNのキャッシュ削除で直らない事実は、原因の切り分けに使えます。CDNのパージはエッジのキャッシュを消す操作です。エッジを消しても直らないなら、原因はエッジより先、つまりクライアント側にあります。 ブラウザ側にもキャッシュが2種類あった 整理すると、キャッシュはCDNエッジだけではありません。ブラウザ側にも2種類あります。 層 主に保持するもの 消えるタイミング CDNのパージで消えるか CDNエッジ HTML・JavaScript・画像 TTL満了、またはパージ 消える ブラウザHTTPキャッシュ HTML・JavaScript Cache-Controlの指定次第 消えない sessionStorage 画面表示用のデータ タブを閉じる、または明示的な削除 消えない ブラウザHTTPキャッシュ側の問題は2つありました。 1つは、静的エクスポートしたHTMLにCache-Controlが付いていなかったことです。レスポンスに明示的な有効期限がない場合、ブラウザはヒューリスティックキャッシュを行います。つまりLast-Modifiedなどから独自に鮮度を推測して、一定時間は再取得しません。開発者が指定していないだけで、キャッシュしない設定にはなりません。 もう1つは、アップロード方法です。ossutil cp --updateは更新されたファイルを上書きしますが、以前のビルドで生成された古いチャンクを削除しません。結果として、古いHTMLをキャッシュしているブラウザは、そのHTMLが参照する古いJavaScriptチャンクをそのまま読み込み続けます。 ...

2026年7月31日 · 読了時間: 3分 · 宮下 由妃
X(Twitter)のシェアカードに書影が表示されているブラウザウィンドウのイラスト

X(Twitter)シェアで書影が出ない問題を動的 OGP エンドポイントで解決した記録

問題と対応のまとめ 開発を担当しているサービスでは、作品詳細ページにX(旧Twitter)のシェアボタンを設置しています。ボタンを押すと書影(表紙画像)付きのTwitter Cardとしてツイートに表示されることを想定していました。しかしシェアボタンの動作確認中に実際にシェアしてみたところ、書影が表示されずテキストのみのカードになっていることを発見しました。 当初のシェアボタンは作品詳細ページのURLを直接参照しており、書影はページを開いた後にJavaScriptが内部APIを呼び出して動的に取得・表示する実装でした。 原因はこの構成にあります。フロントエンドはNext.jsの静的エクスポートで動いているため、初回のHTMLレスポンスには書影が含まれていません。Twitter Cardはページの静的HTMLに埋め込まれたメタタグを読み取る仕組みのため、CSR(クライアントサイドレンダリング)で取得した書影をOGPタグとして渡せません。 SSR(サーバーサイドレンダリング)への移行も検討しましたが、ホスティング構成の大幅な変更が必要なため見送りました。詳しくは後述の「なぜ専用エンドポイントを追加したか」で説明します。 解決策として、api-frontend にTwitterbot向けのOGP生成エンドポイントを新設しました。フロントエンドのXシェアURLをそこへ向けることで、書影付きのTwitter Cardが表示されるようになりました。この対応は調査・実装・動作確認を含めて約2週間で完了しました。 OGP と Twitter Card について OGP(Open Graph Protocol) とは、Webページの情報をSNSやメッセージアプリが正しく読み取れるよう構造化するための仕様です(ogp.me)。OGPタグを設定していない場合、SNSでシェアしてもURLがそのままテキストとして表示されます。HTMLの <head> に og:title・og:description・og:image などのタグを記述します。これにより、URLシェア時にタイトル・説明・画像のプレビューが自動生成されます。Facebookが策定し、現在は多くのSNS・チャットツールで対応しています。 Twitter Card はX(旧Twitter)が提供するURLシェア時のリッチリンクプレビュー機能です。OGPタグをベースに twitter:card・twitter:title・twitter:image などの専用タグを加えることで、ツイート内に画像・タイトル・説明文を含むカードが生成されます。 サービスの構成 このサービスのフロントエンドは2つのリポジトリに分かれています。ユーザー向け画面を担当する frontend と、決済・外部連携など動的なサーバー処理が必要なAPIを担当する api-frontend です。 本記事の実装は api-frontend の以下の環境で行いました。 技術 バージョン Next.js 13.4.12 TypeScript 5.1.6 graphql-request 6.1.0 Node.js 20.x frontend はNext.jsの静的エクスポートでHTML・CSS・JSをCDNで配信しています。書影の画像ファイルも別のCDN上に置かれており、Next.jsのコードには含まれていません。 api-frontend は決済・外部連携を担うAPIルートをホスティングしているNext.jsアプリです。サーバーサイドで動作するため、リクエスト時に動的な処理が可能です。 OGPエンドポイントはリクエスト時に内部APIから書影データを動的に取得する必要があります。静的エクスポートの frontend はビルド済みファイルを配信するだけでリクエスト時にコードを実行できないため、api-frontend に実装します。 対応前後のリクエスト経路の違いを示します。 【対応前:通常ユーザー・Twitterbot 共通】 シェア URL ─→ frontend(CDN)─→ HTML 配信(OGP タグに書影なし) ↑ Twitterbot はここで OGP を読む → 書影なし 【対応後:User-Agent で処理を分岐】 シェア URL ─→ api-frontend /api/ogp/titles/{slug} ├─ 一般 UA ────→ 302 ─→ /titles/{slug}/detail/(frontend) └─ Twitterbot ─→ 内部 GraphQL API で書影データ取得 ─→ CDN URL + origin_key で og:image 組み立て ─→ OGP タグ入り HTML を 200 で返す 静的エクスポートと Twitterbot の相性問題 静的エクスポートとは Next.jsには主に2つの出力方式があります。 ...

2026年5月31日 · 読了時間: 4分 · 鈴木愛麗