Payloadのスケジュール公開機能の設定方法と実行の仕組み
はじめに
Payloadには、コンテンツの下書きと公開の機能があり、日時を指定してスケジュール公開する機能もあります。スケジュール公開機能を使用するには、ジョブキューというバックグラウンドで動作する処理についても理解する必要があります。
この記事では、スケジュール公開機能の設定方法と実行の仕組みについて紹介したいと思います。
スケジュール公開機能の有効化設定
スケジュール公開機能を使用したいコレクションやグローバルの設定で、スケジュール公開機能を有効化できます。
下記は任意のコレクションでスケジュール公開機能を有効化する場合の設定です。
// ...
versions: {
drafts: {
schedulePublish: true,
}
},
// ...
設定内容からもわかる通り、スケジュール公開機能(schedulePublish
)を有効化する場合、バージョン管理機能(versions
)と下書き機能(drafts
)も有効化することになります。これは、スケジュール公開機能が、バージョン管理機能と下書き機能を基にして成り立っている機能であるためです。
この設定により、コンテンツ編集画面の「Publish Changes」横の矢印でメニューを開くと、「Schedule Publish」メニューが表示されるようになります。
「Schedule Publish」メニューを開くと、スケジュール公開の日時を指定することができます。
スケジュール公開のためのジョブキュー実行
スケジュール公開機能を使用するには、ジョブキューというバックグラウンドで定期実行する機能の設定が必要です。
ジョブキューを実行するには複数の手段がありますが、ここではPayloadの設定で行う方法とAPIエンドポイントにアクセスして行う方法を紹介したいと思います。
Payloadの設定によりジョブキューを実行する
下記のコードがジョブキュー実行の設定例です。src/payload.config.ts
で行います。
export default buildConfig({
// ...
jobs: {
autoRun: [
{
cron: '*/1 * * * *',
limit: 100,
queue: 'default',
},
],
},
// ...
})
項目 | 説明 |
---|---|
cron | 実行するタイミングを指定します。この例では1分おきの実行としています。 |
limit | 一度の実行で処理するジョブの数を指定します。この例では100としています。 |
queue | 処理するキューの名前を指定します。コンテンツのスケジュール公開設定を行うとdefault というキューにジョブが溜まっていくため、default を指定します。 |
以上の設定を行なった上でNode.jsを起動させると、設定に基づいてNode.jsが定期実行します。コンテンツのスケジュール公開設定で指定した日時を過ぎた後に、ジョブキューの定期実行が行われると、Node.jsのログに下記のような実行結果が出力されます。このログが出力された後に、スケジュール公開設定していたコンテンツを確認すると、下書きから公開に変更されていることを確認できると思います。
[16:25:00] INFO: Running 1 jobs.
new: 1
retrying: 0
もし、スケジュール公開設定していたコンテンツの公開時フック処理でrevalidatePath()
やrevalidateTag()
が使用されているならば、下記のようなエラーが発生すると思います。その場合は、この次に紹介するAPIエンドポイントにアクセスしてジョブキューを実行する方法でエラー回避できますのでお試しください。
[16:41:00] ERROR: Error running task 1
job: {
"id": 26,
"input": {
"doc": {
"value": "4",
"relationTo": "posts"
},
"type": "publish",
"user": 1
},
"completedAt": null,
"totalTried": 0,
"hasError": false,
"error": null,
"log": [],
"taskSlug": "schedulePublish",
"queue": "default",
"waitUntil": "2025-05-15T16:40:00.778Z",
"processing": true,
"updatedAt": "2025-05-15T16:41:00.026Z",
"createdAt": "2025-05-15T16:39:16.670Z",
"taskStatus": {}
}
taskSlug: "schedulePublish"
err: {
"type": "Error",
"message": "Route /admin/[[...segments]] used \"revalidatePath /en/posts/test\" during render which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering",
"stack":
Error: Route /admin/[[...segments]] used "revalidatePath /en/posts/test" during render which is unsupported. To ensure revalidation is performed consistently it must always happen outside of renders and cached functions. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
at revalidate (webpack-internal:///(rsc)/./node_modules/next/dist/server/web/spec-extension/revalidate.js:95:41)
at revalidatePath (webpack-internal:///(rsc)/./node_modules/next/dist/server/web/spec-extension/revalidate.js:66:12)
at eval (webpack-internal:///(rsc)/./src/collections/Posts/hooks/revalidatePost.ts:16:75)
at Array.map (<anonymous>)
at revalidatePost (webpack-internal:///(rsc)/./src/collections/Posts/hooks/revalidatePost.ts:13:23)
at updateDocument (file:///home/node/app/node_modules/payload/dist/collections/operations/utilities/update.js:269:28)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async updateByIDOperation (file:///home/node/app/node_modules/payload/dist/collections/operations/updateByID.js:97:22)
at async handler (file:///home/node/app/node_modules/payload/dist/versions/schedule/job.js:25:17)
at async Object.schedulePublish (file:///home/node/app/node_modules/payload/dist/queues/operations/runJobs/runJob/getRunTaskFunction.js:170:37)
at async handler (file:///home/node/app/node_modules/payload/dist/queues/operations/runJobs/index.js:148:17)
at async runJob (file:///home/node/app/node_modules/payload/dist/queues/operations/runJobs/runJob/index.js:12:9)
at async runSingleJob (file:///home/node/app/node_modules/payload/dist/queues/operations/runJobs/index.js:181:28)
at async Promise.all (index 0)
at async runJobs (file:///home/node/app/node_modules/payload/dist/queues/operations/runJobs/index.js:222:24)
at async Object.run (file:///home/node/app/node_modules/payload/dist/queues/localAPI.js:55:20)
at async P.fn (file:///home/node/app/node_modules/payload/dist/index.js:375:21)
at async P._trigger (file:///home/node/app/node_modules/croner/dist/croner.js:1:16192)
}
APIエンドポイントへのアクセスによりジョブキューを実行する
Payloadには、ジョブキューを実行するためのAPIエンドポイントが用意されています。
まず、src/payload.config.ts
でジョブキュー実行のAPIエンドポイントのアクセス制御を設定します。この例ではPayloadにログインしている場合、もしくは、リクエストヘッダーのauthorization
が環境変数CRON_SECRET
と一致している場合にアクセスを許可するよう設定しています。
export default buildConfig({
// ...
jobs: {
access: {
run: ({ req }: { req: PayloadRequest }): boolean => {
// Allow logged in users to execute this endpoint (default)
if (req.user) return true
// If there is no logged in user, then check
// for the Vercel Cron secret to be present as an
// Authorization header:
const authHeader = req.headers.get('authorization')
return authHeader === `Bearer ${process.env.CRON_SECRET}`
},
},
// ...
})
次に、コンテンツのスケジュール公開設定で指定した日時を過ぎた後に、Payloadへログインした状態で下記のパスにアクセスすると、ジョブキューが実行されます。
/api/payload-jobs/run?limit=100&queue=default
項目 | 説明 |
---|---|
limit | 一度の実行で処理するジョブの数を指定します。この例では100としています。 |
queue | 処理するキューの名前を指定します。コンテンツのスケジュール公開設定を行うとdefault というキューにジョブが溜まっていくため、default を指定します。 |
APIのレスポンスで、下記のとおり実行結果が返ってきます。APIで実行した後に、スケジュール公開設定していたコンテンツを確認すると、下書きから公開に変更されていることを確認できると思います。
{
"message": "Success",
"remainingJobsFromQueried": 0
}
Payloadにログインしていない状態、たとえば外部サービスなどからAPIを定期的にコールしたい場合は、下記のようなかたちで認証のためにAuthorization
ヘッダーを付与した上でAPIコールする必要があります。
await fetch('/api/payload-jobs/run?limit=100&queue=default', {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
})
まとめ
この記事では、スケジュール公開機能の設定方法と実行の仕組みについて紹介しました。スケジュール公開のみ説明しましたが、もちろんスケジュール非公開についても同じ仕組みで機能します。
また、ジョブキューについても少し触れましたが、ジョブキューはスケジュール公開・非公開のためだけにある機能ではありません。時間がかかる処理を夜間に定期実行するなど、あらゆるバッチ処理を行うことができます。ぜひ公式ドキュメントも参照して理解を深めていただければと思います。
参考
Discussion