Hướng Dẫn Chạy Công Việc Payload Trên Vercel (Serverless)
Giới Thiệu
Gần đây, tôi đã thực hiện một video hướng dẫn về việc sử dụng các công việc và hàng đợi trong PayloadCMS. Tuy nhiên, giải pháp tôi cung cấp không hoạt động trong môi trường triển khai Vercel. Nó chạy tốt trên máy tính cá nhân và có thể hoạt động trên Railway vì đây là các máy chủ thực sự.
Bài viết này sẽ giải thích lý do vì sao và hướng dẫn bạn cách cập nhật dự án để chạy trên Vercel.
Tại Sao: Serverless So Với Quy Trình Chạy Dài Hạn
Mã trong video sử dụng hàng đợi công việc tích hợp sẵn của Payload, dựa vào một quy trình máy chủ Node.js chạy dài hạn. Thiết lập autoRun: true khởi động một "đồng hồ" liên tục (setInterval) bên trong máy chủ của bạn, kiểm tra mỗi phút xem có công việc nào cần chạy hay không.
Tuy nhiên, với Vercel, vì đây là một nền tảng serverless, mã của bạn không chạy trên một máy chủ "luôn bật". Thay vào đó, nó được đóng gói thành các hàm serverless, tự động khởi động để xử lý một yêu cầu đến và sau đó tắt ngay sau khi phản hồi được gửi đi.
Vì các hàm tắt, không có quy trình liên tục nào để giữ cho "đồng hồ" của bộ lập lịch chạy trong nền. Công việc của bạn sẽ không bao giờ được kích hoạt sau khi triển khai ban đầu.
Các hàm Vercel có giới hạn thời gian thực thi (ví dụ: từ 10 đến 60 giây trên các gói hobby/pro). Vì hàng đợi công việc của Payload được thiết kế để chạy vô thời hạn, nó sẽ bị chấm dứt.
Giải Pháp: Cách Chạy Công Việc Định Kỳ Trên Vercel
Bạn có thể chạy các tác vụ định kỳ với ứng dụng Payload CMS của mình trên Vercel, nhưng bạn cần thay đổi cơ chế kích hoạt. Thay vì để ứng dụng tự lên lịch, bạn sẽ sử dụng một dịch vụ bên ngoài để gọi ứng dụng của bạn theo lịch.
Vercel có Vercel Cron Jobs có thể được sử dụng để giải quyết vấn đề này.
Bước 1: Vô Hiệu Hóa autoRun Trong Cấu Hình Payload
Đầu tiên, ngăn Payload cố gắng khởi động bộ lập lịch nội bộ của nó.
Trong payload.config.ts, vô hiệu hóa tự động lên lịch mặc định trên môi trường serverless. Bật lại trên máy tính cá nhân hoặc trên máy chủ truyền thống với ENABLE_PAYLOAD_AUTORUN=true.
typescript
// payload.config.ts
jobs: {
tasks: [processTrackersTask],
/**
* Bật hoặc tắt autoRun dựa trên môi trường
* @description Các môi trường serverless (ví dụ: Vercel) nên vô hiệu hóa bộ lập lịch trong quy trình.
* Đặt ENABLE_PAYLOAD_AUTORUN="true" cục bộ hoặc trên máy chủ truyền thống để bật điều này.
*/
autoRun:
process.env.ENABLE_PAYLOAD_AUTORUN === 'true'
? [
{
cron: '* * * * *', // Xử lý công việc mỗi phút
queue: 'tracker-queue',
},
]
: [],
jobsCollectionOverrides: ({ defaultJobsCollection }) => {
// giữ công việc hiển thị trong Admin
if (!defaultJobsCollection.admin) {
defaultJobsCollection.admin = {}
}
defaultJobsCollection.admin.hidden = false
return defaultJobsCollection
},
},
Bước 2: Tạo Điểm Cuối API Bảo Mật Để Kích Hoạt Công Việc
Làm cho lịch trình nội bộ của tác vụ có điều kiện và xuất một hàm runProcessTrackers mà bạn có thể gọi từ một tuyến API, đây là những gì cron job của Vercel sẽ gọi.
typescript
export const processTrackersTask: TaskConfig<'process-trackers'> = {
slug: 'process-trackers',
/**
* Gắn lịch một cách có điều kiện khi ENABLE_PAYLOAD_TASK_SCHEDULE === 'true'
* @description Trên serverless, giữ rỗng điều này và dựa vào Vercel Cron gọi đến một tuyến API.
*/
schedule:
process.env.ENABLE_PAYLOAD_TASK_SCHEDULE === 'true'
? [
{
cron: '* * * * *', // Chạy mỗi phút
queue: 'tracker-queue',
},
]
: [],
handler: async (): Promise<TaskHandlerResult<'process-trackers'>> => {
return runProcessTrackers()
},
}
/**
* Bộ chạy có thể tái sử dụng cho process-trackers
* @description Trích xuất để chúng ta có thể gọi từ tuyến API hoặc bộ xử lý tác vụ.
*/
export const runProcessTrackers = async (): Promise<TaskHandlerResult<'process-trackers'>> => {
console.log(`[TASK] Đang xử lý tất cả các theo dõi giá hoạt động...`)
// ... phần còn lại của triển khai không thay đổi
}
Bước 3: Tạo Điểm Cuối API Bảo Mật Để Kích Hoạt Công Việc
Bạn cần tạo một tuyến trong ứng dụng của mình mà một dịch vụ bên ngoài có thể gọi. Khi tuyến này được truy cập, nó sẽ kích hoạt công việc một cách thủ công.
Điều này là quan trọng để bảo vệ điểm cuối này, cách tiêu chuẩn là sử dụng CRON_SECRET được lưu trữ trong biến môi trường. Nếu bạn không triển khai cách bảo vệ điểm cuối, bất kỳ ai cũng có thể spam điểm cuối và làm gián đoạn trang web của bạn.
Tạo một tệp mới, ví dụ: tại src/app/api/payload-cron/route.ts:
typescript
import { NextResponse } from 'next/server'
import { runProcessTrackers } from '@/tasks/process-trackers'
/**
* Xác thực yêu cầu cron job bằng cách sử dụng xác thực CRON_SECRET
* @description Triển khai mẫu bảo mật cron job chính thức của Vercel:
* - Chính: Kiểm tra tiêu đề Authorization cho "Bearer {CRON_SECRET}" (sản xuất)
* - Dự phòng: Kiểm tra tham số truy vấn "secret" để thử nghiệm thủ công
*/
const isAuthorized = (req: Request): boolean => {
const configured = process.env.CRON_SECRET || ''
if (!configured) {
console.log('[CRON] Không có CRON_SECRET được cấu hình')
return false
}
const authHeader = req.headers.get('authorization') || ''
// Kiểm tra nếu tiêu đề Authorization khớp với CRON_SECRET (theo tài liệu của Vercel)
if (authHeader === `Bearer ${configured}`) {
return true
}
// Dự phòng cho tham số truy vấn để thử nghiệm thủ công
try {
const url = new URL(req.url)
const qsSecret = url.searchParams.get('secret') || ''
if (qsSecret === configured) {
return true
}
} catch (e) {
console.log('[CRON] Lỗi phân tích URL:', e)
}
console.log('[CRON] Xác thực không thành công')
return false
}
export const dynamic = 'force-dynamic'
export async function GET(req: Request) {
console.log('[CRON] Đang xử lý yêu cầu cron job')
if (!isAuthorized(req)) {
return NextResponse.json({ error: 'Không được phép' }, { status: 401 })
}
console.log('[CRON] Xác thực thành công, đang chạy tác vụ...')
const result = await runProcessTrackers()
console.log('[CRON] Tác vụ đã hoàn thành:', result)
return NextResponse.json(result)
}
export async function POST(req: Request) {
if (!isAuthorized(req)) {
return NextResponse.json({ error: 'Không được phép' }, { status: 401 })
}
const result = await runProcessTrackers()
return NextResponse.json(result)
}
Bước 4: Cấu Hình Biến Môi Trường
Thêm các biến môi trường mới
plaintext
ENABLE_PAYLOAD_AUTORUN=false
ENABLE_PAYLOAD_TASK_SCHEDULE=false
CRON_SECRET=thay-thế-bằng-xâu-ngẫu-nhiên-dài
Bước 5: Cấu Hình Vercel Cron Jobs
Cuối cùng, bạn chỉ định cho Vercel gọi điểm cuối mới này theo lịch. Bạn thực hiện điều này bằng cách thêm một phần crons vào tệp vercel.json trong thư mục gốc của dự án.
Vercel sẽ tự động gửi biến môi trường CRON_SECRET dưới dạng tiêu đề Authorization: Bearer {CRON_SECRET} khi gọi đến điểm cuối của bạn. Điều này tuân theo tài liệu chính thức của Vercel về bảo mật cron job.
json
// vercel.json
{
"crons": [
{
"path": "/api/payload-cron",
"schedule": "* * * * *"
}
]
}
path: Đây là tuyến API bạn đã tạo ở Bước 2.schedule: Đây là cú pháp cron tiêu chuẩn.* * * * *có nghĩa là "chạy mỗi phút", giống như trong video.
Lưu Ý:
- Điều này thiết lập lịch chạy một lần mỗi phút. Điều chỉnh cron theo nhu cầu.
- Vercel tự động gửi CRON_SECRET dưới dạng tiêu đề Authorization - không cần cấu hình tiêu đề thủ công.
- Để thử nghiệm thủ công, bạn có thể truyền secret qua chuỗi truy vấn: /api/payload-cron?secret=$CRON_SECRET. Tuyến này hỗ trợ cả tiêu đề và ?secret= để thuận tiện.
Kiểm Tra Tại Địa Phương
Vercel Cron chỉ chạy trong môi trường đám mây của Vercel. Lệnh
vercel devkhông mô phỏng các kích hoạt cron.
Chạy máy chủ dev và kích hoạt tuyến một cách thủ công:
plaintext
pnpm run dev
curl -i -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/payload-cron
curl -i "http://localhost:3000/api/payload-cron?secret=$CRON_SECRET" # lựa chọn tham số truy vấn
Bạn sẽ thấy các nhật ký:
plaintext
[TASK] Đang xử lý tất cả các theo dõi giá hoạt động...
[TASK] Tìm thấy N theo dõi hoạt động để xử lý.
[TASK] Đang lấy giá cho các đồng tiền: ...
[TASK] Hoàn thành xử lý các theo dõi.
Phản Hồi Mong Đợi:
- Thành công (200):
{ "output": { "success": true } } - Không được phép (401):
{ "error": "Không được phép" }(kiểm tra tiêu đề/chuỗi truy vấn secret của bạn)
Mẹo:
- Đảm bảo rằng
ENABLE_PAYLOAD_AUTORUN=falsevàENABLE_PAYLOAD_TASK_SCHEDULE=falsetại địa phương để tránh thực thi gấp đôi khi kiểm tra tuyến. - Nếu bạn muốn lên lịch trong quy trình trong phát triển cục bộ, hãy đặt
ENABLE_PAYLOAD_AUTORUN=truevà bỏ qua việc gọi tuyến.
Khi mọi thứ được triển khai đúng cách, bạn có thể đi đến Cài Đặt Dự Án / Công Việc và bạn sẽ thấy điều gì đó tương tự như thế này.
Tóm Tắt
Bài viết này cho thấy cách cập nhật hệ thống Hàng Đợi Công Việc tích hợp sẵn của Payload CMS cho triển khai serverless trên Vercel. Bằng cách thay thế các bộ lập lịch trong quy trình bằng Vercel Cron, chúng tôi đạt được:
- Lên Lịch Đáng Tin Cậy: Các công việc cron chạy nhất quán trong các môi trường serverless
- Xác Thực Đúng Cách: Sử dụng mẫu tiêu đề Authorization
CRON_SECRETchính thức của Vercel - Linh Hoạt Môi Trường: Cùng một mã nguồn hoạt động cục bộ (với lập lịch trong quy trình) và trên Vercel (với lập lịch HTTP)
- Sẵn Sàng Sản Xuất: Tuân theo các thực tiễn tốt nhất đã được tài liệu hóa của Vercel về bảo mật cron job
Chìa khóa ở đây là các hàm serverless có thời gian tồn tại ngắn, vì vậy các bộ hẹn giờ liên tục không hoạt động. Giải pháp là kích hoạt công việc thông qua các yêu cầu HTTP theo lịch mà thực hiện các tác vụ khi cần, duy trì cùng một chức năng trong khi thích ứng với mô hình serverless.
Liên Kết Tham Khảo
Tài Liệu Chính Thức
- Tài liệu Vercel Cron Jobs - Hướng dẫn chính thức về cách thiết lập cron jobs
- Quản lý Cron Jobs của Vercel - Quản lý và bảo mật cron jobs
- Hệ thống Hàng Đợi Công Việc của Payload CMS - Hệ thống công việc tích hợp sẵn của Payload
- Tuyến API của Next.js - Tạo các điểm cuối API
Tài Nguyên Liên Quan
- Tài liệu Vercel Functions - Hiểu các hàm serverless của Vercel
- Cấu hình Payload CMS - Cấu hình chung của Payload
- Ví dụ về Vercel Cron - Các mẫu Vercel với cron jobs