Nội dung bài viết
Sau nhiều năm quản trị VPS, cài Nginx, gia hạn chứng chỉ SSL và xử lý sự cố lúc nửa đêm, tôi chuyển phần lớn các trang tĩnh sang Cloudflare Pages. Lý do rất đơn giản: với loại trang này, gần như không còn gì để quản trị nữa.
Gói miễn phí đủ hào phóng cho hầu hết dự án cá nhân và doanh nghiệp nhỏ: không giới hạn băng thông, 500 lượt build mỗi tháng, và mạng lưới edge phủ khắp toàn cầu.
Hai cách để deploy
Tích hợp Git
Cách phổ biến nhất. Bạn kết nối repository GitHub hoặc GitLab, khai báo lệnh build và thư mục kết quả, sau đó mỗi lần push là Cloudflare tự build và phát hành.
Với một dự án Astro, cấu hình thường là:
- Build command:
pnpm build(hoặcnpm run build) - Build output directory:
dist - Root directory: để trống nếu dự án nằm ở gốc repo
Điểm tôi thích nhất ở cách này là preview deployment. Mỗi pull request được deploy lên một URL riêng. Thay vì mô tả thay đổi bằng lời trong lúc review, bạn gửi thẳng link cho khách hàng xem thử. Riêng việc này đã tiết kiệm cho tôi rất nhiều vòng trao đổi qua lại.
Wrangler CLI
Khi cần kiểm soát chặt hơn, hoặc deploy từ pipeline CI của riêng mình:
pnpm add -D wrangler
npx wrangler pages deploy dist
Tôi dùng cách này khi muốn build ở nơi khác rồi chỉ đẩy kết quả lên, hoặc khi cần deploy một thư mục không nằm trong repo.
Cố định phiên bản Node
Đây là chỗ tôi vấp đầu tiên. Máy tôi chạy Node 22 nhưng Cloudflare Pages mặc định dùng một phiên bản cũ hơn, và build thất bại với thông báo rất khó hiểu.
Cách xử lý là khai báo biến môi trường NODE_VERSION trong phần cấu hình dự án:
NODE_VERSION = 22.17.0
Hoặc thêm file .nvmrc vào gốc repo:
22.17.0
Dự án Astro thường khai báo sẵn yêu cầu này trong package.json, và giữ hai chỗ khớp nhau sẽ tránh được nhiều rắc rối:
{
"engines": {
"node": ">=22.12.0"
}
}
Chọn đúng trình quản lý gói
Cloudflare Pages tự nhận diện trình quản lý gói dựa vào file lock trong repo. Có pnpm-lock.yaml thì nó dùng pnpm, có package-lock.json thì dùng npm.
Đừng để nhiều hơn một file lock trong repo. Tôi từng mất khá nhiều thời gian cho một lỗi build chỉ vì repo còn sót lại package-lock.json từ trước khi chuyển sang pnpm, khiến Cloudflare cài đặt bằng npm với cây phụ thuộc khác hẳn máy tôi.
Tên miền riêng và SSL
Thêm tên miền trong tab Custom domains. Nếu tên miền đã trỏ nameserver về Cloudflare thì bản ghi DNS được tạo tự động, chứng chỉ SSL cấp trong vòng vài phút và tự động gia hạn mãi mãi.
Đây là thứ tôi đánh giá cao nhất sau khi chuyển từ VPS sang. Không còn phải nhớ ngày hết hạn chứng chỉ, không còn cron job gia hạn Let’s Encrypt bị hỏng âm thầm.
Nếu tên miền đang ở nhà đăng ký khác, bạn thêm một bản ghi CNAME trỏ về địa chỉ .pages.dev mà Cloudflare cấp.
Chuyển hướng và headers
Hai file đặt trong thư mục public sẽ được Cloudflare đọc sau khi build.
File _redirects cho chuyển hướng:
/blog/bai-viet-cu /blog/bai-viet-moi 301
/dich-vu/* /services/:splat 301
File _headers cho HTTP header:
/*
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/assets/*
Cache-Control: public, max-age=31536000, immutable
Quy tắc cache ở trên đáng chú ý: các file trong assets được Astro đặt tên kèm mã băm nội dung, nên khi nội dung đổi thì tên file cũng đổi. Vì vậy có thể cache chúng vĩnh viễn mà không sợ người dùng nhận bản cũ.
Vài chỗ khác từng làm tôi mất thời gian
Biến môi trường có hai môi trường riêng. Cloudflare phân biệt Production và Preview. Khai báo biến ở Production không tự áp dụng cho các bản preview, và ngược lại. Build chạy tốt trên nhánh chính nhưng hỏng ở pull request thường là do quên chỗ này.
Biến môi trường chỉ được nạp lúc build. Với trang tĩnh thuần, mọi giá trị đã được nhúng cứng vào HTML tại thời điểm build. Đổi biến thì phải build lại mới có tác dụng. Và hệ quả quan trọng hơn: đừng bao giờ đặt bí mật vào biến môi trường của một trang tĩnh, vì chúng sẽ nằm công khai trong mã nguồn trang.
Thư mục output phải khớp. Astro xuất ra dist, nhưng nếu bạn đổi outDir trong astro.config.mjs thì phải sửa cả cấu hình trên Cloudflare. Sai chỗ này thì build thành công mà trang lại trắng trơn.
Cache của Cloudflare khác với cache trình duyệt. Sau khi deploy mà vẫn thấy nội dung cũ, thử xóa cache trong bảng điều khiển Cloudflare trước khi nghi ngờ build bị lỗi.
Khi nào cần nhiều hơn trang tĩnh
Nếu về sau bạn cần xử lý phía máy chủ, ví dụ nhận dữ liệu form hay gọi API có khóa bí mật, Cloudflare Pages hỗ trợ Functions. Đặt file vào thư mục functions và chúng trở thành các endpoint chạy trên edge:
// functions/api/contact.js
export async function onRequestPost({ request, env }) {
const data = await request.formData();
// env.API_KEY chỉ tồn tại phía server, không lộ ra client
return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
}
Đây là con đường nâng cấp tự nhiên khi trang tĩnh bắt đầu cần một chút logic động, mà không phải chuyển toàn bộ dự án sang hạ tầng khác.
Chính trang web này đang chạy trên Cloudflare Pages.