やりたいこと
ブログサイトで「予約公開」を入れると、必ず付随で「予約時刻に Next.js の ISR を再生成する」が要る。これを別系統で組むと、publish と revalidate がズレる リスクが残る。Sidekiq の cron で 両方を 1 つの worker に集約するのが整然。
実装
# backend/config/sidekiq.yml
:scheduler:
schedule:
publish_scheduled_posts:
cron: '* * * * *' # 毎分
class: PublishScheduledPostsWorker
# backend/app/workers/publish_scheduled_posts_worker.rb
class PublishScheduledPostsWorker
include Sidekiq::Worker
def perform
Post.where(status: :scheduled)
.where('published_at <= ?', Time.current)
.find_each do |post|
Post.transaction do
post.update!(status: :published)
RevalidatePostsWorker.perform_async(post.slug)
end
end
end
end
毎分 1 回、scheduled で published_at が過去になったものを published に遷移 + 即 ISR 再生成。トランザクションで囲むので、DB 更新と revalidate が必ずペアで走る。
RevalidatePostsWorker
class RevalidatePostsWorker
include Sidekiq::Worker
def perform(slug)
post = Post.find_by(slug: slug)
return unless post
paths = [
"/posts/#{post.slug}",
'/',
'/posts',
"/categories/#{post.category.slug}",
]
HTTP.headers('Authorization' => "Bearer #{ENV['NEXT_REVALIDATE_SECRET']}")
.post("#{ENV['NEXT_PUBLIC_URL']}/api/revalidate", json: { paths: paths })
end
end
revalidate するのは 記事ページ + トップ + 一覧 + カテゴリページ。新しい記事が出ると、最低この 4 つの URL は更新が必要。
Sidekiq 8 の cron 設定
Sidekiq 8 で cron スケジューラが builtin になった(Sidekiq.options[:scheduler])。これ以前は sidekiq-cron gem を別途入れる必要があった。
# backend/config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = { url: ENV.fetch('REDIS_URL') }
end
config/sidekiq.yml の :scheduler: セクションだけで cron job が動く。
落とし穴
実運用で気付いた点 3 つ:
1. cron の頻度は「毎分」で十分
*/1 * * * * で毎分実行。1 秒以下の精度は不要(ブログの予約公開は分単位で十分)。
2. published_at は 必ず past timestamp に変える
scheduled → published に遷移する時、published_at を現在時刻にも更新するか、scheduled 時に設定した時刻のままにするか、で運用が変わる。
私は scheduled 時の時刻のまま にした。「2026-05-15 10
公開予定」が「実際には 10:01 に publish」だと、表示時刻がブレるのが嫌だったから。3. tz は明示する
cron: '0 9 * * *' のような時刻指定の cron は、Sidekiq の動く環境の TZ で評価される。Docker で TZ=Asia/Tokyo を指定しておかないと UTC 9 時 = JST 18 時で走る。
# docker-compose.yml
sidekiq:
environment:
TZ: Asia/Tokyo
監視
毎分の cron job は 失敗を観測する仕組み が要る。私は Sentry に「PublishScheduledPostsWorker が 5 分以上 enqueue されていない」アラートを仕込んでいる:
# 別の cron job で
PublishScheduledPostsWorker.perform_async # 必ず enqueue できる、失敗したら別の worker から検知
これがないと「気付いたら 1 週間 publish が動いていなかった」事故が起きる。
まとめ
Sidekiq cron で予約公開と revalidate を統合する:
PublishScheduledPostsWorkerが毎分 scheduled をチェック- published に遷移したら同時に
RevalidatePostsWorkerを enqueue - Next.js の
/api/revalidateで ISR キャッシュが更新される
3 つの worker を 1 つのチェーンに繋げるだけで、「予約時刻ぴったりに Next.js の表示も切り替わる」体験が組める。
自動化は 1 連鎖で終わらせる。連鎖の途中に手動が混ざると壊れる。