Thực hành tốt
Hiệu năng: cache, revalidation và Core Web Vitals
Cache ở frontend, để CMS báo qua webhook khi nội dung đổi, lấy dữ liệu gọn và phục vụ ảnh qua CDN.
Cập nhật lần cuối:

KDIGI CMS chỉ phục vụ admin và API; tốc độ trang công khai phụ thuộc cache ở frontend của bạn. Lưu nội dung xếp một job revalidate và không bao giờ chờ frontend phản hồi.
Tóm tắt nhanh
- Job chạy mỗi 10 giây, tối đa 25 job mỗi lượt.
- Webhook timeout 5 giây, thử lại 3 lần.
- Chỉ REST trả HTML đã render; GraphQL trả dữ liệu thô.
- WebP khi tải lên chỉ có ở site dùng R2.
Ba chỗ cần tối ưu
- Truy vấn Frontend lấy dữ liệu gọn bằng
depthvàselect. Xem API headless. - Cache và làm mới ISR hoặc tương đương, kèm webhook revalidate.
- Media Phục vụ qua CDN, đặt
publicBaseUrl. Xem thư viện media.
Truy vấn REST gọn
curl -s 'https://cms.example.com/api/posts?where[tenant][equals]=1&where[status][equals]=published&sort=-publishedAt&limit=12&depth=1&select[title]=true&select[slug]=true'
depth=1cho danh sách (mặc định REST là 2)select[...]bỏcontentMd,seo.jsonLdkhỏi danh sách- Luôn lọc
where[tenant][equals] - Phân giải site một lần rồi cache
- Dùng REST để render trang
Webhook revalidate
Khi nội dung đổi, CMS xếp job revalidateFrontend rồi gọi POST https://{Sites.domain}/api/cms/revalidate với { "collection", "id", "slug" }.
| Thuộc tính | Giá trị |
|---|---|
| Đổi slug | Gửi thêm request cho slug cũ |
| URL | https:// + Sites.domain; không có domain thì không gửi |
| Timeout / retry | 5 giây; lỗi thì thử lại 3 lần |
| Xác thực | Đặt REVALIDATE_SECRET để có header x-revalidate-secret |
| Đánh giá sao | Không kích hoạt revalidate |
// app/api/cms/revalidate/route.ts
import { revalidateTag } from 'next/cache'
export async function POST(req: Request) {
const secret = process.env.REVALIDATE_SECRET
if (secret && req.headers.get('x-revalidate-secret') !== secret) {
return new Response('forbidden', { status: 403 })
}
const { collection, slug } = await req.json()
revalidateTag(`cms:${collection}`)
if (slug) revalidateTag(`cms:${collection}:${slug}`)
return Response.json({ ok: true })
}
Bật job runner
ENABLE_JOBS_AUTORUN=true # chỉ trên MỘT instance
Cảnh báo Payload không có khóa job giữa các instance; bật ở nhiều nơi gây xử lý trùng. Không bật thì job được xếp nhưng không chạy. Job lỗi nằm ở bảng
payload_jobs, kiểm tra bằng SQL.
Nội dung hẹn giờ
Bài có publishedAt ở tương lai bị ẩn khỏi truy vấn ẩn danh, nhưng cache chỉ làm mới khi có lưu.
- Đặt TTL cho cache Ví dụ
revalidate: 300ở các trang danh sách. - Hoặc lưu lại bài Lưu lại hoặc revalidate thủ công ngay sau mốc giờ.
Media nhanh và ổn định
| Chế độ | Hành vi |
|---|---|
| Ổ đĩa (mặc định) | Cache-Control: public, max-age=3600, ETag yếu, nosniff |
R2 + publicBaseUrl |
URL CDN; WebP khi tải lên nếu bật compressToWebp |
R2 không có publicBaseUrl |
Redirect tới URL ký 1 giờ, khách ẩn danh nhận 401 |
Mẹo Luôn đặt
publicBaseUrlcho media công khai, đặtwidth/heightcho ảnh đại diện và giữ nguyênPAYLOAD_PUBLIC_SERVER_URL.
Core Web Vitals
| Chỉ số | CMS giúp | Frontend làm |
|---|---|---|
| LCP | Ảnh có kích thước, publicBaseUrl, ETag |
Ưu tiên ảnh LCP, CDN, ISR/SSG |
| INP | HTML đã làm sạch, không script nội tuyến | Giảm JavaScript client |
| CLS | Tự chèn width/height vào <img> khi src khớp media |
Đặt kích thước ảnh, nhúng, font |
Hãy đo trên trang của frontend bằng Search Console hoặc PageSpeed Insights.
Giới hạn vận hành
- Kết nối DB Pool tối đa 20 kết nối PostgreSQL mỗi instance.
- GraphQL Production giới hạn độ phức tạp 500 và tắt playground.
- Health check
GET /api/healthchỉ kiểm tra tiến trình sống, không hỏi DB.
Muốn xuất trace, đặt OTEL_EXPORTER_OTLP_ENDPOINT để dùng OpenTelemetry.
Checklist hiệu năng
- Handler
/api/cms/revalidatetrả 2xx dưới 5 giây, idempotent ENABLE_JOBS_AUTORUN=trueđúng một nơi;domainđã điền- Danh sách dùng
depth=1+select, có lọctenant - Media công khai có
publicBaseUrlhoặc CDN - Có kế hoạch cho bài hẹn giờ
Tiếp theo: SEO on-page hoặc tự host bằng Docker Compose/Coolify.
Câu hỏi thường gặp
Đăng bài xong frontend không cập nhật thì kiểm tra gì?
Bài hẹn giờ có tự làm mới cache khi đến giờ không?
Webhook revalidate có xác thực không?
REST hay GraphQL nhanh hơn cho frontend?
Ảnh tải lên có tự tối ưu không?
Đọc tiếp
Headless API
Lấy nội dung qua REST hoặc GraphQL, Markdown chuyển sang HTML ở khóa gốc, hợp đồng revalidate cho ISR và ví dụ tích hợp Next.js chạy được ngay.
Thư viện media
Quản lý ảnh theo thư mục, sửa ảnh trực tiếp, nhập ảnh từ URL có chống SSRF, lưu trên ổ đĩa hoặc Cloudflare R2, xuất WebP để trang tải nhanh hơn.
SEO on-page
Checklist SEO on-page thực hành trong KDIGI CMS: title, meta description, heading, canonical, internal link, JSON-LD, sitemap và kiểm tra SEO có sẵn.
Tự host
Cài KDIGI CMS trên VPS của bạn bằng Docker Compose hoặc Coolify: yêu cầu hệ thống, biến môi trường, sao lưu, nâng cấp và các bước tăng cường bảo mật.