Việc chạy một trang web nội dung trên một con VPS truyền thống hoặc một host Node.js quản lý sẵn vẫn rất ổn cho đến khi nó hết ổn. Bạn phải trả tiền cho khối lượng tính toán (compute) nằm chơi xơi nước 95% thời gian, bạn phải tự quản lý việc gia hạn SSL, nơm nớp lo sợ lỗi cold start, và đứng nhìn điểm Lighthouse tụt dốc không phanh chỉ vì origin server của bạn nằm ở Singapore trong khi độc giả lại ở Frankfurt.
Bộ stack vùng biên (edge stack) của Cloudflare giải quyết sạch sẽ mọi vấn đề này. Bài viết này bao quát hai con đường: xây dựng một website mới tinh (greenfield) bằng Astro trên toàn bộ edge stack của Cloudflare — Workers, R2, D1, Pagefind — và đem một website WordPress có sẵn giấu ra phía sau CDN của Cloudflare mà không cần migrate bất cứ thứ gì. Cả hai cách tiếp cận, cấu hình thực tế, và những sự đánh đổi đáng lưu tâm.
Bộ Stack
Trước khi đi sâu vào kiến trúc, dưới đây là chức năng của từng mảnh ghép và lý do tại sao nó xứng đáng có mặt:
| Thành phần | Vai trò | Tại sao không dùng giải pháp thay thế |
|---|---|---|
| Astro | Trình tạo site tĩnh (Static site generator) có các islands | Hugo build nhanh hơn nhưng thiếu component islands cho UI tương tác |
| Cloudflare Pages | Hosting + CDN | Vercel/Netlify thu phí băng thông; Pages là miễn phí ở quy mô này |
| Cloudflare Workers | Các route API (comments, forms, webhooks) | Lambda có độ trễ cold start; Workers chạy trong V8 isolates với thời gian khởi động ~0ms |
| R2 | Lưu trữ hình ảnh và asset | S3 tính phí băng thông đầu ra (egress); R2 hoàn toàn miễn phí egress |
| D1 | Serverless SQLite cho dữ liệu động | Postgres là quá overkill (thừa thãi) chỉ để chứa comment và form liên hệ |
| Drizzle ORM | Truy vấn D1 với type-safe (an toàn kiểu) | Kysely cũng được, nhưng adapter cho D1 của Drizzle trưởng thành hơn |
| Pagefind | Tìm kiếm full-text phía client | Algolia tốn tiền; Pagefind chạy hoàn toàn trên trình duyệt người dùng |
Tổng chi phí hàng tháng cho một trang web có 50k pageviews: **$5-8/tháng**, hầu hết đến từ phí xử lý hình ảnh (image transformations). Mọi thứ khác đều nằm gọn trong gói miễn phí của Cloudflare.
Kiến trúc
flowchart TD
U[Browser người dùng] --> CF[Cloudflare CDN Edge]
CF --> P[Pages — Static HTML/CSS/JS]
CF --> W[Workers — API Routes]
CF --> IT[Image Transformations]
W --> D1[(D1 — SQLite)]
W --> R2[(R2 — Object Storage)]
IT --> R2
subgraph Build["Pipeline Build (GitHub Actions)"]
GH[git push] --> AB[astro build]
AB --> PF[pagefind --site dist]
PF --> WR[wrangler pages deploy]
end
Build --> CF
Mọi request đều đập vào mạng lưới edge của Cloudflare trước tiên. Các asset tĩnh — HTML, CSS, JS, các trang pre-rendered — được phục vụ trực tiếp từ CDN mà không cần phải vòng về origin. Các request động — submit comment, form webhooks, index tìm kiếm — đi vào Workers, thứ đang chạy trong các V8 isolates ngay tại vùng biên, chứ không phải ở một trung tâm dữ liệu chết tiệt nào đó.
Cái insight mấu chốt ở đây là Workers không phải là backend. Chúng là các hàm vùng biên (edge functions) chạy trên cùng một vị trí mạng lưới với CDN. Một Worker xử lý form submit ở Frankfurt sẽ chạy đúng ở Frankfurt, chứ không phải ở một server US-East. Điều đó làm thay đổi hoàn toàn cục diện về độ trễ (latency).
Cài đặt Astro với Cloudflare Adapter
Cloudflare adapter bảo Astro xuất ra một định dạng mà Workers có thể thực thi. Đối với một trang web ưu tiên sự tĩnh lặng (static-first), phần lớn các trang đều được pre-render ngay lúc build. Chỉ những route nào cần dữ liệu runtime — API endpoints, các trang động — mới chạy dưới dạng Workers.
// astro.config.ts
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
export default defineConfig({
output: 'static', // mặc định pre-render mọi thứ
adapter: cloudflare(),
integrations: [
tailwind({ applyBaseStyles: false }),
mdx(),
],
});
Thiết lập output: 'static' rất quan trọng. Nó bắt Astro pre-render mọi trang lúc build và chỉ fallback về Workers cho các route chủ động đòi bật server-side rendering (SSR). Việc này cho bạn điều tốt nhất của cả 2 thế giới: hiệu năng của web tĩnh cho các trang nội dung, và năng lực xử lý động cho các tính năng tương tác.
Đối với các trang cần dữ liệu runtime, thêm dòng export const prerender = false ở ngay đầu file .astro. Mọi thứ còn lại sẽ được build thành HTML tĩnh.
Wrangler Config: Bind Workers với D1 và R2
File wrangler.jsonc là nơi bạn khai báo các bindings kết nối Workers của bạn với các dịch vụ của Cloudflare. Các bindings này được tiêm thẳng vào ngữ cảnh thực thi (execution context) của Worker ngay lúc chạy — không cần biến môi trường, không cần khởi tạo SDK, không cần chuỗi kết nối database.
// wrangler.jsonc
{
"name": "leaseinvietnam",
"compatibility_date": "2026-04-01",
"pages_build_output_dir": "./dist",
"d1_databases": [
{
"binding": "DB",
"database_name": "leaseinvietnam-prod",
"database_id": "your-database-id-here"
}
],
"r2_buckets": [
{
"binding": "IMAGES",
"bucket_name": "leaseinvietnam-images"
}
],
"kv_namespaces": [
{
"binding": "CACHE",
"id": "your-kv-namespace-id"
}
]
}
Bên trong một Worker hoặc một route API của Astro, các bindings này nằm sẵn trên object env:
// src/pages/api/comments.ts
export const prerender = false;
export async function POST({ request, locals }) {
const env = locals.runtime.env;
const { slug, content, author } = await request.json();
await env.DB.prepare(
'INSERT INTO comments (slug, content, author, status) VALUES (?, ?, ?, ?)'
).bind(slug, content, author, 'pending').run();
return new Response(JSON.stringify({ ok: true }), {
headers: { 'Content-Type': 'application/json' }
});
}
Không có connection pooling. Không có án phạt cold start. Binding D1 là một kết nối SQLite trực tiếp ngay tại vùng biên.
D1 Schema và Drizzle ORM
D1 là SQLite chạy tại vùng biên của Cloudflare. Nó KHÔNG phải là Postgres. Nó không hỗ trợ mọi tính năng của Postgres. Nhưng đối với comments, forms liên hệ, và mớ dữ liệu động gọn nhẹ, nó lại là công cụ cực kỳ chính xác.
Schema cho một hệ thống comments:
-- drizzle/schema.sql
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT NOT NULL,
content TEXT NOT NULL,
author TEXT NOT NULL,
parent_id INTEGER REFERENCES comments(id),
status TEXT NOT NULL DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_comments_slug ON comments(slug);
CREATE INDEX IF NOT EXISTS idx_comments_status ON comments(status);
Với Drizzle, cùng một schema đó biến thành TypeScript type-safe:
// src/lib/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const comments = sqliteTable('comments', {
id: integer('id').primaryKey({ autoIncrement: true }),
slug: text('slug').notNull(),
content: text('content').notNull(),
author: text('author').notNull(),
parentId: integer('parent_id'),
status: text('status').notNull().default('pending'),
createdAt: text('created_at'),
});
Query dữ liệu bằng Drizzle qua D1:
import { drizzle } from 'drizzle-orm/d1';
import { eq, and } from 'drizzle-orm';
import { comments } from '~/lib/schema';
export async function getComments(env: Env, slug: string) {
const db = drizzle(env.DB);
return db.select()
.from(comments)
.where(and(eq(comments.slug, slug), eq(comments.status, 'approved')))
.orderBy(comments.createdAt);
}
R2 cho Hình ảnh: Miễn phí Egress
R2 là object storage của Cloudflare. Điểm khác biệt chí mạng so với S3: không phí băng thông đầu ra (zero egress fees). Việc phục vụ hình ảnh từ R2 thông qua CDN của Cloudflare hoàn toàn không tốn thêm đồng nào ngoài cái phí lưu trữ thuần túy ($0.015/GB/tháng).
Chiến lược xử lý hình ảnh rất ngon cho các trang nội dung:
Upload bản gốc → R2 (Full độ phân giải)
Cloudflare Image Transformations → phục vụ các biến thể được resize theo yêu cầu (on demand)
Hãy định nghĩa các preset cố định thay vì cho phép truyền tham số resize tự do. Việc này ngăn chặn lạm dụng và giúp bộ nhớ đệm (cache) dễ đoán định hơn:
// src/utils/images.ts
const IMAGE_PRESETS = {
thumb: { width: 400, height: 225, fit: 'cover' },
card: { width: 800, height: 450, fit: 'cover' },
hero: { width: 1600, height: 900, fit: 'cover' },
og: { width: 1200, height: 628, fit: 'cover' },
} as const;
export function getImageUrl(key: string, preset: keyof typeof IMAGE_PRESETS) {
const { width, height, fit } = IMAGE_PRESETS[preset];
return `https://leaseinvietnam.com/cdn-cgi/image/width=${width},height=${height},fit=${fit},format=auto/${key}`;
}
Upload hình lên R2 trong quá trình build bằng lệnh rclone:
# Upload ảnh mới lên R2 (chỉ lấy các file bị thay đổi)
rclone sync ./public/images r2:leaseinvietnam-images \
--transfers 8 \
--checksum \
--progress
Cờ --checksum đảm bảo rclone chỉ upload các file thực sự có thay đổi về nội dung, chứ không phải các file chỉ mới được cập nhật timestamp.
Pagefind: Tìm kiếm Full-Text với Chi phí Runtime bằng Không
Pagefind là một thư viện tìm kiếm dạng tĩnh (static), nó index trang web của bạn ngay lúc build và chạy hoàn toàn trên trình duyệt. Không cần tài khoản Algolia. Không cần API tìm kiếm. Không có hóa đơn thanh toán hàng tháng. File index được sinh ra từ mớ HTML tĩnh của bạn và nằm chình ình ở đó dưới dạng file tĩnh cùng với source code.
Thêm nó vào build pipeline:
# package.json
{
"scripts": {
"build": "astro build && pagefind --site dist",
"preview": "wrangler pages dev dist"
}
}
Pagefind bò khắp thư mục dist/ sau khi Astro build xong, tự động tạo ra một file search index, và vứt vào dist/pagefind/. Khi deploy, index này được phục vụ như file tĩnh từ CDN của Cloudflare.
Giao diện tìm kiếm (Search UI) là một web component tải chậm (lazy load):
---
// src/components/Search.astro
---
<div id="search"></div>
<script>
async function initSearch() {
const { PagefindUI } = await import('/pagefind/pagefind-ui.js');
new PagefindUI({
element: '#search',
showSubResults: true,
excerptLength: 15,
});
}
// Chỉ load khi thanh công cụ tìm kiếm lọt vào tầm nhìn (hiển thị trên màn hình)
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
initSearch();
observer.disconnect();
}
});
observer.observe(document.getElementById('search'));
</script>
Cơ chế lazy load thông qua IntersectionObserver có nghĩa là đoạn JS của tính năng tìm kiếm chỉ được tải về khi người dùng thực sự cuộn chuột tới chỗ thanh search. Đối với đa số độc giả không bao giờ bấm vào nút search, họ sẽ không bao giờ phải gánh cái script đó cả.
Pipeline Build và Deploy
Toàn bộ pipeline chạy trên GitHub Actions mỗi khi có ai push lên main:
# .github/workflows/deploy.yml
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build Astro
run: npm run build
env:
PUBLIC_SITE_URL: https://leaseinvietnam.com
- name: Generate Pagefind index
run: npx pagefind --site dist
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: pages deploy dist --project-name=leaseinvietnam
Bước deploy sử dụng lệnh pages deploy của Wrangler, upload thư mục dist/ lên Cloudflare Pages và kích hoạt một bản deploy mới. Toàn bộ pipeline — install, build, index, deploy — chạy hết chưa tới 2 phút cho một website khoảng ~200 trang.
Chiến lược Cache
CDN của Cloudflare tự động cache các asset tĩnh, nhưng bạn cần tự cấu hình cache header cho từng loại asset khác nhau. Hãy thêm một file tên _headers vào thư mục public/:
# public/_headers
# Các asset bất biến (tên file bị băm - hashed từ lần build của Astro)
/_astro/*
Cache-Control: public, max-age=31536000, immutable
# Trang HTML — cache thời gian ngắn, cho phép revalidate thường xuyên
/*.html
Cache-Control: public, max-age=3600, stale-while-revalidate=86400
# File index của Pagefind — cache 1 ngày
/pagefind/*
Cache-Control: public, max-age=86400
# Ảnh từ R2
/images/*
Cache-Control: public, max-age=604800
Tham số immutable (bất biến) ở dòng /_astro/* đặc biệt quan trọng. Astro luôn sinh ra các tên file bị băm (content-hashed) cho toàn bộ tài nguyên, vì vậy một file ở /_astro/main.abc123.js sẽ vĩnh viễn không bao giờ thay đổi nội dung. Việc nói cho trình duyệt biết nó có thể cache cái file này vĩnh viễn sẽ triệt tiêu hoàn toàn mớ request xác thực (revalidation) vô nghĩa.
Phân tích Chi phí
Đối với một trang web có ~50k pageviews/tháng với khoảng ~500 tấm ảnh:
| Dịch vụ | Mức sử dụng | Chi phí |
|---|---|---|
| Cloudflare Pages | Không giới hạn requests | Miễn phí |
| Workers | ~10k API requests/tháng | Miễn phí (Giới hạn 100k/ngày) |
| D1 | ~50k reads, ~5k writes | Miễn phí (Giới hạn 5M reads/ngày) |
| R2 | 5GB lưu trữ, 0 phí egress | $0.075/tháng |
| Image Transformations | ~100k lần transform | ~$5/tháng |
| Tổng cộng | ~$5.08/tháng |
Tính năng Biến đổi Hình ảnh (Image Transformations) là thứ duy nhất thực sự cắn vào tiền của bạn. Nếu bạn chủ động tự tạo sẵn các bản resize bằng script trong lúc build thay vì để Cloudflare biến đổi on-demand, bạn có thể đá văng chi phí này về 0 — bù lại thời gian build sẽ lâu hơn và tốn dung lượng R2 hơn đôi chút.
Mục Bonus: Đẩy WordPress ra sau lưng Cloudflare
Không phải website nào cũng may mắn là một dự án Astro mới tinh. Nếu bạn đang gánh một con WordPress chạy trên VPS và khao khát tốc độ cũng như lớp phòng thủ DDoS của Cloudflare mà không muốn phải đập đi xây lại toàn bộ, cách thiết lập dưới đây khá đơn giản — và hiệu quả tốc độ mang lại là thật 100%.
Kiến trúc
flowchart LR
U[Browser người dùng] --> CF[Cloudflare CDN]
CF -->|Cache HIT| CACHE[Edge Cache]
CF -->|Cache MISS| WP[WordPress Origin VPS]
WP --> NGINX[Nginx + PHP-FPM]
NGINX --> DB[(MySQL / MariaDB)]
Cloudflare đứng sừng sững trước mặt server gốc (origin) của bạn. Đa số request không bao giờ đụng được tới con VPS — chúng được đáp trả trực tiếp từ edge cache của Cloudflare. Chỉ những request bị cache misses (hụt cache), truy cập khu vực admin, và các POST request mới được lọt qua để chạy vào origin.
Bước 1: Trỏ DNS về Cloudflare
Thay đổi nameservers của domain về nameserver của Cloudflare. Một khi DNS đã được Proxy (biểu tượng đám mây chuyển sang màu cam), mọi traffic đều bị bắt phải đi xuyên qua Cloudflare. IP thật của con server gốc đã được giấu hoàn toàn khỏi phần còn lại của internet.
Lúc này, hãy cấu hình Origin server của bạn chỉ được phép nhận kết nối từ các dải IP của Cloudflare. Việc này ngăn chặn hacker đâm chọt trực tiếp vào VPS của bạn bằng cách bypass qua Cloudflare:
# /etc/nginx/conf.d/cloudflare-only.conf
# Chỉ cho phép IP của Cloudflare — nhớ update dải này định kỳ
allow 173.245.48.0/20;
allow 103.21.244.0/22;
allow 103.22.200.0/22;
allow 103.31.4.0/22;
allow 141.101.64.0/18;
allow 108.162.192.0/18;
allow 190.93.240.0/20;
allow 188.114.96.0/20;
allow 197.234.240.0/22;
allow 198.41.128.0/17;
allow 162.158.0.0/15;
allow 104.16.0.0/13;
allow 104.24.0.0/14;
allow 172.64.0.0/13;
allow 131.0.72.0/22;
deny all;
Bước 2: SSL Mode — Full (Strict)
Trong cài đặt SSL/TLS của Cloudflare, set chế độ thành Full (Strict). Điều này có nghĩa:
- Browser → Cloudflare: Mã hóa HTTPS (dùng chứng chỉ của Cloudflare)
- Cloudflare → Origin server: Mã hóa HTTPS (dùng chứng chỉ của server gốc)
Tuyệt đối KHÔNG ĐƯỢC dùng chế độ Flexible. Flexible mã hóa đầu Browser-Cloudflare nhưng lại vứt mớ traffic từ Cloudflare về Origin qua kết nối HTTP trần truồng không che chắn. Đó là một cảm giác an toàn giả tạo và sẽ làm nát một số plugin của WordPress.
Hãy cài một Origin Certificate miễn phí từ Cloudflare (hạn dùng 15 năm) lên con VPS của bạn:
# Tạo qua dashboard Cloudflare: SSL/TLS → Origin Server → Create Certificate
# Sau đó cấu hình cho Nginx dùng nó
ssl_certificate /etc/ssl/cloudflare/origin.pem;
ssl_certificate_key /etc/ssl/cloudflare/origin-key.pem;
Bước 3: Cache Rules cho WordPress
Về bản chất WordPress là một web động — request nào cũng đòi chạy vào PHP. Mục tiêu ở đây là ép Cloudflare cache càng nhiều trang càng tốt, đồng thời bỏ qua cache (bypass) cho các tài khoản đang đăng nhập và các POST requests.
Tạo một Cache Rule trên dashboard của Cloudflare (Rules → Cache Rules):
Rule: Cache WordPress pages
When: hostname equals yourdomain.com
AND NOT cookie contains "wordpress_logged_in"
AND NOT cookie contains "wp-postpass"
AND request method is GET or HEAD
AND NOT URI path starts with /wp-admin
AND NOT URI path starts with /wp-login.php
Then:
Cache eligibility: Eligible for cache
Edge TTL: 4 hours
Browser TTL: 1 hour
Rule này sẽ cache toàn bộ các trang công cộng tại vùng biên trong 4 tiếng. Users đã đăng nhập và các request gọi vào admin sẽ luôn luôn đâm thẳng vào origin.
Ở phía WordPress, hãy cài plugin WP Super Cache hoặc W3 Total Cache để tạo ra các file HTML tĩnh trên origin. Nhờ đó, ngay cả khi xảy ra sự cố hụt cache (Cache misses), Nginx vẫn có thể ném ra một file tĩnh thay vì phải xách đít đi chạy PHP:
# Phục vụ file cache tĩnh trực tiếp từ Nginx
location / {
set $cache_uri $request_uri;
# Bypass cache cho users đăng nhập và các request POST
if ($request_method = POST) { set $cache_uri "null"; }
if ($query_string != "") { set $cache_uri "null"; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
set $cache_uri "null";
}
# Bú file cache tĩnh trước, nếu không có mới chịu chạy PHP
try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html
/wp-content/cache/supercache/$http_host/$cache_uri/index-https.html
$uri $uri/ /index.php?$args;
}
Bước 4: Plugin WordPress của Cloudflare
Cài đặt Plugin Cloudflare WordPress. Nó thực hiện 2 nhiệm vụ sống còn:
- Phục hồi IP thực sự của người truy cập — nếu không có nó, mọi traffic trên server của bạn sẽ hiện ra dưới dạng IP của Cloudflare, làm hư hệ thống Analytics, hệ thống giới hạn truy cập (rate limit), và cả bộ lọc spam bình luận.
- Tự động thanh trừng (purge) cache khi xuất bản bài — mỗi khi bạn lên bài mới hoặc sửa bài, plugin này tự động gọi API của Cloudflare để xóa bản cache cũ ngay tắp lự.
// wp-config.php — phục hồi IP thực từ header của Cloudflare
if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
Bước 5: Thêm Security Headers bằng _headers hoặc Transform Rules
Hãy bơm các header bảo mật (Security headers) thông qua tính năng Transform Rules của Cloudflare thay vì cấu hình chật vật trên Nginx. Bằng cách này, nó sẽ có tác dụng ngay lập tức từ vùng biên, trước khi request kịp lết tới origin:
# Cloudflare Transform Rule — Modify Response Headers
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Thứ bạn nhận được
Sau những cấu hình vã mồ hôi trên, một con web WordPress điển hình sẽ lột xác:
| Chỉ số | Trước khi có Cloudflare | Sau khi có Cloudflare |
|---|---|---|
| TTFB (trang đã cache) | 400-800ms (origin) | 15-40ms (edge) |
| TTFB (chưa cache) | 400-800ms | 400-800ms (không đổi) |
| Phòng thủ DDoS | Chờ chết | Tự động hoàn toàn |
| IP Origin bị lộ | Có | Không |
| Quản lý SSL | Đăng ký chay | Tự động |
| Chi phí băng thông | VPS gánh | Cloudflare bao sân |
Cải thiện TTFB ở các trang được cache chính là con số ăn tiền nhất. Một trang trước đây vật vã 600ms mới hiện ra byte đầu tiên giờ đây chỉ mất 20ms, vì nó được đẩy đi từ một cái Node vùng biên của Cloudflare nằm cách nhà bạn chỉ 50km, thay vì từ con VPS nằm tuốt bên kia bán cầu.
Luồng đi không được cache — hụt cache, trang admin, luồng đăng nhập — thì vẫn y nguyên không đổi. Cloudflare KHÔNG làm cho con PHP của bạn chạy nhanh hơn. Nó chỉ đảm bảo rằng 95% khách của bạn sẽ không bao giờ nhìn mặt con PHP đó mà thôi.
Điểm tới hạn của phương pháp này
Đặt Cloudflare trước mặt WordPress là một bộ khuếch đại hiệu năng, không phải là thứ để sửa chữa các lỗi thiết kế hiệu năng. Nếu con WordPress của bạn chạy rề rề vì xài query dỏm, cài một đống plugin thối, hoặc do con VPS quá yếu, Cloudflare sẽ chỉ làm cho bản cache chạy nhanh hơn và mặc xác mọi thứ khác tiếp tục chạy rề rề như bản tính của chúng.
Mô hình tư duy đúng đắn là thế này: Cloudflare cáng đáng 95% lượng traffic đang vào web chỉ để “Đọc” nội dung public. Con VPS của bạn cáng đáng 5% luồng traffic vào để “Ghi”, đăng nhập, và tải những nội dung không được cache. Bạn phải chủ động tối ưu cả 2 mảng này một cách độc lập.
Những Thứ Cần Phải Đề Phòng
D1 Không phải là Postgres. Nó đéo hỗ trợ tìm kiếm full-text search, không có toán tử JSON, và thiếu hụt hàng tá tính năng xịn sò của Postgres. Nếu yêu cầu thao tác dữ liệu động của bạn hơi phức tạp một tí, D1 sẽ làm bạn phát rồ. Còn nếu bạn chỉ làm các thao tác CRUD cơ bản — như comments, điền form, xem lượt view — thì nó là một tuyệt tác.
Workers bị giới hạn ở 128MB RAM. Mức RAM này là quá dư dả cho các luồng gọi API, nhưng nếu bạn làm tác vụ đồ họa hay tính toán nặng trên Worker, bạn sẽ tèo ngay lập tức. Hãy dời những gánh nặng này qua background queue hoặc tách ra một con service riêng.
Pagefind chỉ đánh index trên file HTML, không đánh trên source code. Nếu nội dung của bạn bị che đi bởi một lớp xác thực (Auth), hoặc bị render hoàn toàn ở phía client (Client-side rendering), Pagefind sẽ mù tịt. Nó chỉ hoạt động trơn tru với các nội dung công cộng được render sẵn ở server — và đây cũng chính là thứ mà Astro giỏi nhất.
Tính năng Image Transformations ép bạn phải trả tiền nếu muốn xài tên miền riêng. Gói miễn phí chỉ chịu transform hình ảnh trên mấy cái domain nhảm rí *.pages.dev. Nếu bạn xài domain tự mua, bạn phải cắn răng mua gói Pro (20 đô/tháng), hoặc phải tự thân vận động xài script render trước các bức ảnh đó.
Kết quả
Một cái trang web nội dung chạy trên cục stack này sẽ tải hoàn chỉnh trong vòng chưa tới 200ms trên toàn cầu, dễ dàng giật 100/100 điểm Lighthouse, ngốn chưa tới 10 đô một tháng, và bạn đéo phải nhúng tay vào quản lý cái server nào hết. Toàn bộ chu trình deploy được tự động hóa gói gọn trong một cái GitHub Actions workflow. Quá trình deploy mang tính chất Atomic (nguyên tử) — nếu có gì sai trong lúc build, bản cũ vẫn sẽ nằm trơ ra đó phục vụ khách.
Sự đánh đổi ở đây là bạn đang xây nhà trên đất của Cloudflare, chứ không phải xài một bộ tiêu chuẩn mở. Workers không phải là Node.js. D1 không phải Postgres. R2 không phải là S3 (dù nó được quảng cáo là tương thích với S3). Sẽ đến một ngày bạn muốn chạy khỏi Cloudflare, đợt migration đó sẽ mệt mỏi và đau khổ không chừa một lỗ nào.
Nhưng đối với một website thiên về nội dung, nơi mà Tốc độ, Giá rẻ, và Tối giản hóa sự vận hành quan trọng hơn Tính linh hoạt nền tảng, thì đây rõ ràng là một món hời.
Cấu hình hoàn chỉnh của dự án LeaseInVietnam deploy lên Cloudflare — bao gồm wrangler config, GitHub Actions workflow, và schema migrations cho D1 — có sẵn mã nguồn tại leaseinvietnam repository.