← 戻る

OGP 画像のリンク切れをきっかけに AI にサイトのメンテナンスをさせた

注記

以下の記事は骨子を人間が書き Claude Code に書かせました。

以下がプロンプトです。(プロンプト以外にも PR で作業した内容などをコンテキストにロードしています)

data/blog/208.md の中身をすべて消して以下の内容で書き直してください。技術的な正確さなどは補ってください。

* GitHub の OGP 画像がリンク切れしていたのをきっかけにAIに原因調査〜修正までやらせた
* PRを作らせたらClaude CodeのCIでのレビューがコケていたので `/install-github-app` をもう一度叩いた
* パッケージのバージョンをしばらく更新していなかったのも気になったので更新させた
* ググったら Amplify が Next 16 に対応したらしいので Next 16 にあげた
* ついでに TypeScript も速いと噂の v7 にあげた
* 各 PR は Codex にレビューさせた。組み込みのレビューコマンドは確度の高い指摘しかしないようであまり厳しくないので、手動でプロンプトを書いた。「{PR の URL} を厳し目にレビューしてインラインコメントをつけてください」のような感じ
* ESLint 関係の指摘がついたが、CI で使ってないので消しますという Claude の修正対応がコミットされた。良い機会なので次の PR で CI で lint, format, tsc, test を入れさせた
* CI は実行時間に注意するように伝えたところ、Claude は Biome を入れた
* 感想
   * パッケージのアップデートは純粋に無駄な労力だと感じる。月1000~2000円くらいの有料CMS利用料をケチってセルフホストしているわけだが、それでも AWS になんだかんだ毎月数百円はらっているし、今回のように数時間かけてメンテする手間もある
      * 1 プロンプトで AI がいい感じにメンテしてくれたら良いのだがまだそういう段階ではないかも
   * 他のリポジトリでは `/pr` スキルや `cleanup.sh`というスクリプトで細かな作業効率化をしている。便利なのでテンプレート化してもよいかもと思った
      * `cleanup.sh`とは、 `git checkout main` や `git pull` や  `git remote prune origin` などをした上で、remote にないローカルブランチを自動で消してくれるスクリプト。決まりきった手順は Skill にするよりもシェルスクリプトにしたほうが速い

本文

このサイトのブログ記事で、GitHub のリポジトリを貼ったリンクプレビューの OGP 画像がリンク切れになっていた。それをきっかけに、原因調査から修正まで AI(Claude Code)にやらせてみた。ついでに溜まっていたメンテナンスもまとめて片付けたので、その記録と感想を書く。

OGP 画像のリンク切れ

このサイトでは、記事中に URL を単独の行で書くと OGP 情報を取得してカードとして表示している。そのうち GitHub のカードだけ画像が表示されないことがあった。

Claude Code に調査させたところ、原因は opengraph.githubassets.com のレート制限だった。レスポンスヘッダに x-ratelimit-limit: 100、retry-after: 900 が付いていて、IP 単位で制限を超えると画像の代わりに 429 を返す。これまでは OGP 画像を外部サイトから直リンクしていたので、閲覧者のブラウザから直接 GitHub にリクエストが飛ぶ。つまり閲覧者側の IP で制限されるので、こちらのコードをどう工夫しても直リンクをやめない限り防げない。

そこで、ビルド時に OGP 画像を取得して自サイトから配信するように修正させた。

  • yarn fetch-og で全記事の単独 URL の OGP 情報を取得し、画像を public/og-images/ に保存、メタデータを data/og-cache.json にキャッシュする
  • 画像は sharp で横幅 800px 以下の WebP に変換する。合計 4.7MB が 500KB ほどになった
  • predev / prebuild で自動実行し、未取得の URL と前回失敗した URL だけを取りにいく
  • キャッシュにない URL は、直リンクを避けるため画像なしで表示する

Claude Code のレビューがコケていた

修正の PR を作らせたところ、GitHub Actions で動かしている Claude Code のレビューが失敗していた。Claude Code で /install-github-app をもう一度実行すると、ワークフローファイルを更新する PR が作られる。今回はレビュー結果をインラインコメントで投稿するよう、/code-review に --comment を付け、インラインコメント用のツールを --allowedTools で許可する変更が入っていた。これをマージしたら以降はちゃんと動くようになった。

パッケージの更新

パッケージのバージョンをしばらく更新していなかったのも気になったので、ついでに更新させた。だいたいはマイナー・パッチ更新で済んだが、next-seo は v7 で破壊的変更があった。<NextSeo> / <DefaultSeo> コンポーネントが廃止され、Pages Router では next-seo/pages の generateNextSeo / generateDefaultSeo を <Head> の中で呼ぶ形になる。また、_app で指定した titleTemplate が各ページに継承されなくなったので、ページごとに指定させた。

npm audit の脆弱性は、破壊的でない npm audit fix だけ適用して 52 件から 42 件に減らした。残りはほぼ @aws-amplify 系の深い依存なので、上流の更新を待つしかない。

Next.js 16 と TypeScript 7

このサイトは AWS Amplify Hosting でホストしている。パッケージ更新の時点では Amplify が Next.js 16 に対応していないと思って 15 系に留めていたが、ググったら Amplify が Next.js 16 に対応したらしかったので、16 に上げた。

ついでに TypeScript も、Go で書き直されて速いと噂の v7 に上げた。TypeScript 7 では次のような対応が必要だった。

  • target: es5 が削除されたので ES2017 に変更する
  • types の既定値が空になり、@types/* が自動で読み込まれなくなったので、"types": ["node", "jest"] を明示する
  • export module Foo {} 形式の名前空間宣言がエラーになるので export namespace Foo {} に書き換える

Codex にレビューさせた

各 PR は Codex にもレビューさせた。Codex には組み込みのレビューコマンドもあるが、確度の高い指摘しかしないようで、あまり厳しくない。そこで、次のように手動でプロンプトを書いた。

{PR の URL} を厳し目にレビューしてインラインコメントをつけてください

これでかなり細かいところまで指摘してくれる。Claude Code に書かせたコードを Codex にレビューさせ、指摘を Claude Code に直させる、という流れで進めた。

ESLint の指摘から CI を入れることに

TypeScript 7 に上げた PR では、「eslint-config-next が依存している typescript-eslint が TS 7 に対応しておらず、読み込んだ時点でエラーになる」という指摘がついた。これに対して Claude は「ESLint の設定ファイルも lint スクリプトもなく、どこからも使われていないので削除します」と、eslint-config-next を依存ごと消す修正をコミットした。

確かにその通りで、このリポジトリには lint も CI もなかった。良い機会なので、次の PR で GitHub Actions の CI を作り、lint・format チェック・型チェック(tsc --noEmit)・テストを実行させることにした。

CI の実行時間に気をつけてもらったら Biome が入った

CI を作らせるときに「実行時間に注意して」と伝えたところ、Claude は lint と format に Biome を入れた。Biome は Rust 製の単一バイナリで、lint と format を両方こなせる。このリポジトリなら全ファイルのチェックが数十ミリ秒で終わる。ESLint と Prettier を組み合わせて設定するより確かに速いし楽だ。

ほかにも、実行時間を抑えるために次のような工夫をしていた。

  • ジョブを分けず、1 ジョブで順番に実行する(ジョブごとのセットアップ時間と、課金の分単位の切り上げを避ける)
  • node_modules をキャッシュし、ヒットしたら npm ci を省略する
  • npm ci --ignore-scripts で sharp などの postinstall を省略する
  • concurrency で同じブランチの古い実行を取り消す
  • ブログ記事や画像だけの変更では CI を実行しない(paths-ignore)
  • timeout-minutes: 10 を設定する

感想

パッケージのアップデートは純粋に無駄な労力

パッケージのアップデートは、純粋に無駄な労力だと感じる。月 1000〜2000 円くらいの有料 CMS の利用料をケチってセルフホストしているわけだが、それでも AWS にはなんだかんだ毎月数百円払っているし、今回のように数時間かけてメンテナンスする手間もかかる。

1 プロンプトで AI がいい感じにメンテナンスしてくれたら良いのだが、まだそういう段階ではないかもしれない。今回も、Amplify が Next.js 16 に対応したかどうかは自分で調べたし、レビューも Codex に厳しめに見させて、ようやく漏れが拾えた。

細かな作業効率化はテンプレート化してもよいかも

他のリポジトリでは、PR を作る手順をまとめた /pr スキルや、cleanup.sh というスクリプトで細かな作業を効率化している。便利なので、テンプレート化してどのリポジトリにも入れられるようにしてもよいかもしれないと思った。

cleanup.sh は、main に戻って最新を取り込んだうえで、リモートで削除済みのブランチに対応するローカルブランチを自動で消してくれるスクリプトだ。例えば次のような内容になる。

#!/usr/bin/env bash set -euo pipefail git checkout main git pull git remote prune origin # 追跡先のリモートブランチが消えたローカルブランチを削除する # スカッシュマージだと -d では消せないので -D を使う git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads \ | awk '$2 == "[gone]" { print $1 }' \ | xargs -r git branch -D

PR をマージするたびにこれを実行するだけで、ローカルがきれいな状態に戻る。決まりきった手順は、Skill にして AI に実行させるよりもシェルスクリプトにしたほうが速い。