SoSoraEndo2026年5月9日1 min460 字
なぜ ISR + Webhook なのか
ブログのようなサイトを Next.js で組むとき、選択肢は概ね 3 つある。
- SSR(毎リクエスト server side で組む)— TTFB が長くなり SEO に不利
- SSG(ビルド時に全 HTML 生成)— 公開即反映が難しい
- ISR(SSG + 期限切れで再生成 + on-demand revalidate)— 両方の中間
個人ブログの規模だと ISR + on-demand revalidate が圧倒的に楽。「公開した瞬間に反映する」流れを 1 日で組める。
全体図
┌─ Admin で記事を published に ─┐
│ │
│ Rails after_commit │
│ │ │
│ ▼ │
│ Sidekiq job │
│ │ POST /api/revalidate │
│ ▼ │
│ Next.js handler │
│ │ res.revalidate(path) │
│ ▼ │
│ ISR キャッシュ更新 │
└────────────────────────────────┘
Rails 側の after_commit から Sidekiq に enqueue。Sidekiq は Next.js の API route を Bearer 認証で叩く。Next 側は受けたパスを res.revalidate() で再生成する。
Rails 側
class Post < ApplicationRecord
after_commit :enqueue_revalidate, if: :saved_change_to_status?
private
def enqueue_revalidate
RevalidatePostsWorker.perform_async(slug)
end
end
status カラムが変わった時だけ enqueue。draft の編集中は実行されない。
class RevalidatePostsWorker
include Sidekiq::Worker
def perform(slug)
paths = ["/posts/#{slug}", '/', '/posts', "/categories/#{Post.find_by(slug: slug)&.category&.slug}"]
HTTP.headers('Authorization' => "Bearer #{ENV['NEXT_REVALIDATE_SECRET']}")
.post("#{ENV['NEXT_PUBLIC_URL']}/api/revalidate", json: { paths: paths.compact })
end
end
Next.js 側
// pages/api/revalidate.ts
export default async function handler(req, res) {
if (req.headers.authorization !== `Bearer ${process.env.NEXT_REVALIDATE_SECRET}`) {
return res.status(401).json({ error: 'unauthorized' });
}
const paths = (req.body?.paths || []) as string[];
await Promise.all(paths.map((p) => res.revalidate(p).catch(() => null)));
res.json({ revalidated: paths });
}
Bearer トークンで Rails ↔ Next の通信を認証。Promise.all で複数パスを同時再生成。
詰まったところ
実装中に詰まった点を 3 つ:
- Bearer 認証なし で組むと CSRF / 不正リクエストの的になる。
NEXT_REVALIDATE_SECRETを必ず環境変数で渡す。 - revalidate の失敗を握りつぶす か明示的に handle するか — 私は catch して null 返すようにした(一部失敗しても他は更新したい)。
- dev では効かない。ISR は production build のみ。
docker compose -f docker-compose.prod.yml upで確認する。
まとめ
ISR + Webhook の組み合わせは、個人ブログ規模の Next.js + 任意 backend で 公開即反映 を実装する標準パターンになりつつある。半日で組める。組んだ後は「公開ボタンが効く」感覚が手に入って気持ちいい。
静的サイト + 動的更新の中間が、いまの個人サイトには一番合う。