Mình cân Payload CMS hay WordPress trong đúng một buổi tối. Payload CMS là CMS viết bằng TypeScript, chạy chung trong chính app Next.js của bạn, dữ liệu nằm trong database bạn tự giữ. Mình chọn nó vì một luật mà WordPress không cho đặt: con bot viết bài được, nhưng không được publish. Dựng luật đó mất ba lần sửa, và hai lần đầu mình tưởng đã xong.
Payload CMS hay WordPress, chọn cái nào
Chọn Payload CMS nếu phần quản trị nội dung phải nằm chung trong dự án web. Lý do thứ hai là dữ liệu: nó nằm trong database của bạn, và bạn đặt được luật riêng cho từng thao tác ghi.
WordPress hoặc một CMS đám mây hợp hơn nếu bạn chỉ cần đăng bài, và không muốn nuôi VPS lẫn backup.
Mình rơi vào vế đầu vì đúng một lý do. Cái luật mình cần thì bên kia không có ô nào để gõ vào.
Bốn tiêu chí mình cân lúc chọn
- Dữ liệu nằm ở đâu. Payload CMS: trong database của bạn. Bên kia: trên server nhà cung cấp.
- Đặt luật riêng cho từng thao tác. Payload CMS: tự viết, chạy phía server. Bên kia: chỉ trong khuôn có sẵn.
- Công sức vận hành. Payload: bạn nuôi VPS, nuôi luôn backup. Bên kia: gần bằng không.
- Đổi cấu trúc dữ liệu. Payload: chạy migration. Bên kia: cài thêm rồi bấm.
Với một blog cá nhân, ba dòng đầu nghiêng hẳn về Payload CMS, dòng cuối nghiêng về phía kia. Mình chọn chịu dòng cuối.
Luật mà một CMS thường không cho đặt
Nội dung trên blog này do một con bot AI viết. Bài nó gửi vào nằm ở dạng draft. Mình đọc, sửa, rồi mới bấm publish. Chỉ tài khoản chủ site đổi được trạng thái bài sang published. Nói cách khác: máy làm phần lặp lại, người giữ phần phán đoán, đúng cái ranh giới mình xếp lại trong bài tự động hoá sai chỗ.
Con bot không bấm nút bao giờ. Nó gọi thẳng vào API của site, nên mọi thứ mình giấu đi trên màn hình đều vô nghĩa với nó.
Nó cũng gửi bài bằng một tài khoản riêng, không mượn tài khoản của mình. Nhờ vậy luật mình đặt bám vào tài khoản đó, chứ không bám vào việc bài đang mở ở màn hình nào.
Bot gửi bài vào, hệ thống cất ở dạng draft. Bài hiện trong admin panel kèm nhãn tiếng Việt và ngày tạo. Mình mở ra đọc, sửa chỗ sai, rồi bấm publish. Chặn được đúng bước cuối là chỗ mình ngồi lâu nhất với Payload CMS.
Ba lần sửa mới chặn được bot
Ban đầu mình tưởng chặn ở admin panel là xong. Ẩn nút publish đi, bot không thấy thì bot không bấm được. Hoá ra bot chưa từng nhìn màn hình. Nó gọi API và ghi trạng thái published như thường, y như lúc mình chưa chặn gì.
Lần sửa thứ hai mình chuyển xuống lớp validation. Vẫn thủng.
Chỗ này lắt léo hơn mình nghĩ. Field-level access chạy trước validation, và nó cắt trường trạng thái ra khỏi request vì tài khoản bot không có quyền với trường đó. Đến lượt validation chạy thì trường ấy đã biến mất. Validation báo hợp lệ, và theo dữ liệu nó nhìn thấy thì hợp lệ thật.
Mình phải đọc phần thứ tự chạy trong tài liệu chính thức của Payload mới gỡ ra được chỗ này.

Lần thứ ba mình chặn bằng hook chạy sớm nhất, lúc request còn nguyên và còn thấy bot định ghi trạng thái gì. Rồi mình xếp thêm hai lớp nữa phía dưới, phòng khi sau này có ai sửa hỏng lớp trên cùng.
Ba lớp chồng nhau nghe thừa. Với một blog cá nhân thì đúng là thừa. Mình vẫn giữ, vì lớp trên cùng là thứ mình hay đụng vào mỗi lần sửa admin panel.
Cả ba lần mình đều chặn đúng một hành vi. Hai lần đầu hỏng vì luật của mình đứng sau chỗ Payload CMS cắt trường trạng thái đi, nên tới lượt nó thì chẳng còn gì để chặn.
Hai cái bẫy còn lại
Bật tính năng draft lên là sinh thêm quyền thứ năm mà mình không để ý. Đó là quyền đọc các version cũ của bài. Để mặc định thì bất kỳ tài khoản đã đăng nhập nào cũng đọc được toàn bộ nội dung draft qua API. Quyền đọc bài chính đã chặn đúng, còn draft thì đi lối khác vào.
Mình thấy nó nhờ một buổi ngồi đăng nhập bằng chính tài khoản bot, bấm loanh quanh xem nó nhìn được những gì.
Cái thứ hai rẻ tiền hơn mà khó chịu hơn. Đặt slug trùng với một trang hệ thống sẵn có thì bài đó vĩnh viễn không mở được. Hệ thống ưu tiên trang có sẵn và nuốt mất bài viết, không kèm một dòng lỗi nào. Mình giữ một danh sách slug cấm, chặn ngay lúc lưu bài.

Hai chỗ này đều không có trong phần hướng dẫn nhập môn. Chúng lộ ra khi bạn chạy Payload CMS thật, bằng một tài khoản không phải tài khoản chủ.
Bộ chấm điểm SEO gắn trong admin panel
Payload CMS cho phép gắn thêm màn hình riêng vào admin panel. Mình dựng ở đó một bộ chấm điểm bài viết. Nó soi meta title, meta description, mật độ từ khoá, độ dài bài, và ảnh đã có alt hay chưa.
Nó đọc chính nội dung bài đang mở, nên điểm nhúc nhích ngay lúc mình gõ, khỏi phải bấm lưu rồi mới biết.
Con điểm thì mình ít nhìn. Cái mình cần là nó bắt mình viết meta description trước khi publish, thay vì bỏ trống rồi quên luôn.
Nhãn trong admin panel mình để tiếng Việt hết: bài viết, chủ đề, bản nháp, ngày đăng. Mình không đọc code, nên một màn hình quản trị toàn tiếng Anh là màn hình mình sẽ tránh mở ra.
Payload CMS dở ở chỗ nào
- Bạn tự nuôi VPS. Máy chết thì blog chết, không ai trực thay bạn lúc nửa đêm.
- Bạn tự nuôi database, gồm cả backup và chuyện khôi phục khi hỏng.
- Admin panel của Payload CMS nặng hơn một trang tĩnh, vì nó là cả một app chạy kèm.
- Muốn đổi cấu trúc dữ liệu thì phải chạy migration. Không bấm một nút là xong.
Riêng migration mình vẫn chưa chắc làm đúng cách. Mình chạy thử ở local trước, rồi mới đụng vào database thật. Phần VPS tốn những gì thì mình viết riêng ở bài cách deploy site lên VPS riêng.
Ai không nên dùng Payload CMS
Nếu bạn đang cân Payload CMS hay WordPress mà mỗi tháng đăng vài bài, và không ai lo VPS, đừng dùng Payload CMS. Bạn sẽ dành cho cái máy nhiều thời gian hơn cho bài viết. Một nền tảng dựng sẵn cho bạn kết quả tốt hơn với công sức ít hơn nhiều.
Mình chỉ khuyên ngược lại khi bạn có một luật mà nền tảng dựng sẵn không cho gõ vào đâu cả. Và luật đó phải đáng để bạn nuôi VPS. Luật của mình vỏn vẹn một dòng: con bot không được publish. Một dòng đó đủ để mình chịu hết bốn cái giá phía trên.
Con bot viết draft cho blog này thì mình kể ở bài Claude Code là gì. Các bài cùng nhóm nằm ở trang chủ đề Công cụ. Phần framework web bên dưới thì đọc ở tài liệu Next.js.
Bài bạn đang đọc cũng do con bot đó gửi vào ở dạng draft. Mình sửa tay rồi mới bấm publish, đúng cái luật mất ba lần sửa mới dựng xong.



