最終確認日

VSCodeからCodexを使ってみる(2026年4月)

背景

ブログの ISRWrites が多すぎる問題の解消をしてほしい。

ChatGPTに聞きながらやってみる。

導入

  1. VSCodeCodex – OpenAI’s coding agentをインストールする。
  2. サイドバーの Codex を開く
    VSCodeからCodexを使ってみる_2026年4月_-1775919235814
  3. ログインする

アクセスしてほしくないファイルを設定

VSCode側で設定

.vscode/settings.json に追加

{
  "files.exclude": {
    "**/.env": true,
    "**/.env.*": true
  },
  "search.exclude": {
    "**/.env": true,
    "**/.env.*": true
  }
}

Codex側の設定

プロジェクトルートに .codex/config.toml を追加

[policies]  
deny = [  
".env",  
".env.*"  
]

最初の依頼

ChatGPTに考えてもらったプロンプトを投げてみる。

この Next.js プロジェクトで Vercel の ISR Writes が異常に多くなっています。
原因候補を調査してください。

特に次を確認してください:
- export const revalidate の設定箇所
- revalidatePath / revalidateTag の呼び出し箇所
- generateStaticParams の実装
- 動的ルートで未生成ページが大量発生しそうな箇所
- ISR と on-demand revalidation の二重運用

まずはコードベースを調査し、怪しい箇所を一覧で報告してください。
まだ修正はしないでください。

VSCodeからCodexを使ってみる_2026年4月_-1775919927588

結果

ISR Writes 増加の原因候補として、コード上かなり怪しい箇所は次の通りです。修正はまだしていません。

src/app/(main)/[category]/[slug]/page.tsx の動的ルートが、未生成ページを大量に増やせる構造です。
page.tsx (line 18)
dynamicParams = true なので、generateStaticParams() に含まれない URL でも初回アクセス時に生成されます。
さらにこのページは category を検証していません。slug だけで getPost() を引いており、実ページ描画でも category が使われていません。
page.tsx (line 57)
post.tsx (line 8)
そのため、実在する slug に対して /posts/foo だけでなく /aaa/foo /bbb/foo のような任意カテゴリ URL でも成功ページが生成されうる状態です。
generateMetadata() でも category ではなく DB 上の post.category を使って canonical 相当の URL を組んでいるので、誤ったパスでも成立してしまう設計です。
page.tsx (line 40)
これは bot や誤リンクで別パスが踏まれるだけでも ISR Write が増える、かなり強い候補です。

revalidate と on-demand revalidation の二重運用があります。
home page (line 12)
category page (line 13)
slug page (line 19)
revalidate route (line 1)
主要ページはすべて export const revalidate = 7776000(90日)です。
一方で /api/revalidate では revalidatePath() と revalidateTag() の両方を受け付けています。
つまり「長寿命 ISR を持つページ」を「手動 invalidation」でも再生成する運用になっており、ISR と on-demand revalidation の二重運用です。
これ自体は必ずしも不正ではありませんが、更新頻度が高いと ISR Writes は増えやすい構成です。
revalidatePath と revalidateTag('articles') を同時に叩く設計だと、再生成範囲が広くなりやすいです。
revalidate route (line 15)
post.tsx (line 11)
postIndex.tsx (line 9)
単一記事更新時に revalidatePath('/{category}/{slug}') だけでなく tags: ['articles'] まで revalidate すると、記事本文だけでなく一覧系データ取得もまとめて失効します。
getPost() にも "articles" タグが付いているため、記事個別ページのデータまで広域タグ invalidation の対象です。
本来細粒度に使えそうな post:${slug} タグはあるのに、/api/revalidate 側では使われていません。
post.tsx (line 11)
コードだけ見ると、「1記事更新なのに path invalidation と広域 tag invalidation を両方やっている」可能性があります。これは再生成数を押し上げやすいです。

generateStaticParams() はあるものの、dynamicParams = true により未生成ページの on-demand 生成を許しています。
category page (line 12)
slug page (line 18)
[category] はカテゴリ数が固定4件なのに dynamicParams = true です。こちらは isCategory() で弾いているので大きな被害は出にくいですが、不要な広さです。
category.ts (line 8)
[category]/[slug] は generateStaticParams() で既存記事を全件列挙していますが、未生成の新規記事や想定外パスの初回アクセスを許容する設定です。
特に前述の「category 未検証」と組み合わさることで、未生成ページの増殖条件がかなり緩くなっています。
レイアウト配下で一覧データを広域タグ付き fetch しており、tag invalidation 時の影響範囲が広いです。
Navigation.tsx (line 8)
postIndex.tsx (line 6)
Navigation はレイアウト内で毎回4カテゴリ分の getPostIndexes() を参照します。
それらはすべて tags: ['articles'] 付きです。
articles タグを invalidation すると、一覧系を使う複数ページで次回アクセス時の再生成が連鎖しやすい構造です。
低優先度ですが、getPriorityPostIndexes() は /api/posts をカテゴリなしで叩いており、常に fallback 側に流れていそうです。
priorityPostIndex.tsx (line 11)
posts route (line 7)
/api/posts は category 必須なのに、getPriorityPostIndexes() は /api/posts をそのまま叩いています。
このため production では fetch が 400 になって fallback の Supabase 直読みへ流れる想定です。
これは ISR Writes の主因というより、「タグベースの fetch キャッシュ設計が一部期待通り効いていない」疑いです。
要点

最も怪しいのは src/app/(main)/[category]/[slug]/page.tsx (line 18) です。
dynamicParams = true と category 未検証の組み合わせで、実在 slug に対する別 URL が無制限に生成されうるため、ISR Writes 異常増加の説明としてかなり自然です。

次点で、revalidate = 90日 を持つ ISR ページ群に対して、revalidatePath と広域 revalidateTag('articles') を併用している点が、再生成範囲を広げている可能性が高いです。

必要なら次に、

どの URL パターンで write が増えうるか
どの revalidate 呼び出しが過剰か
修正方針の優先順位
まで整理して提案できます。

Enter で送信するタイプね。

実行してもらう

実行してと伝えて実行した。

その他

インデックスされない問題も同時に調査してもらった。

別の問題として、ブログ記事がGoogleなどの検索エンジンにインデックスされない問題があります。Google Search Console ではほとんどの新規ページがインデックスされず、URLを検査してリクエストをすることで初めてインデックスされます。サイトマップも用意していますが、Google Search Console 上では取得できませんでしたとなります。

レスポンス(抜粋)

Search Console で sitemap が「取得できませんでした」になる主因候補は、かなり高い確率で src/app/sitemap.xml/route.ts (line 3) です。  
この実装は毎回 Supabase に依存し、失敗時に 500 を返すので、Search Console の取得タイミングで一度でも失敗するとそのまま落ちます。

新規記事が自動でインデックスされにくい件は、上の sitemap 不安定に加えて、[category]/[slug] の重複 URL 構造がクロール効率を落としている可能性があります。  
前回の ISR 問題とも同じ箇所が絡んでいます。

**次に見るとよい点**

- 本番の https://uruly.xyz/sitemap.xml が常に 200 application/xml を返しているか
- 本番の NEXT_PUBLIC_SITE_URL が正しく https://uruly.xyz か
- Search Console に登録しているプロパティが https://uruly.xyz/ で一致しているか
- 誤カテゴリ URL が実際に 200 で返っていないか

必要なら次に、修正優先度つきで「SEO と ISR を両方改善する最小修正案」をまとめます。

誤カテゴリ URL が実際に 200で返っていないかを確認

$ curl -s -L -o /dev/null -w "%{url_effective} %{http_code}\n" https://uruly.xyz/wrong-category/real-slug

https://uruly.xyz/wrong-category/real-slug 200

200 で返されている。

誤ったカテゴリで200が返されていることが確認できた。 まずは、この問題についての修正方針をまとめて。

修正方針を確認したあと、実装してもらった。

まずは[category]/[slug] で post.category と URL category の一致確認を入れて 404 化 、generateMetadata() でも同様の整合性を確保 の実装。

VSCodeからCodexを使ってみる_2026年4月_-1775922016500

レビューする を押すと差分が表示された。

VSCodeからCodexを使ってみる_2026年4月_-1775921825730

その後コミットをしてみてと言ったが、権限がないので弾かれた。

Gitは触らせない方が安心できる気もするので、権限は与えないままにすることにした。

おわりに

わざわざコードをコピペして貼らないでいいっていうだけで非常に楽。

ISRWriteの話を無視して、先にサイトマップの問題をやってもらった。

SupabaseOn-demand ISR(Incremental Static Regeneration)もやめてPagefindを使って検索を分離するというのはいいかもしれない。

サイトアイコン
公開日
更新日