Cloudflare cho Frontend Developer · Phần 8 — Cloudflare Pages: host frontend miễn phí
Deploy SPA hay site tĩnh lên Cloudflare Pages: kết nối Git, cấu hình build, preview URL cho từng PR, file _redirects và _headers, Pages Functions giấu API key, gắn tên miền riêng và giới hạn gói Free.
Bạn có một SPA React hay một site Astro, npm run build ra thư mục dist/, và câu hỏi còn lại là host nó ở đâu. Upload FTP lên Hostinger thì mỗi lần deploy là một lần thao tác tay; Vercel hay Netlify thì tốt nhưng tên miền của bạn đã nằm ở Cloudflare rồi, chồng thêm một lớp CDN nữa lại sinh chuyện như Phần 4 đã bàn. Cloudflare Pages là lựa chọn gọn nhất cho tình huống này: nối repo Git, mỗi lần push là tự build và deploy, băng thông không giới hạn, HTTPS có sẵn, không tốn đồng nào.
Đọc xong bài này bạn deploy được một SPA lên Pages với preview URL cho từng pull request, F5 ở route con không còn 404, asset cache một năm còn HTML thì không, khoá API bên thứ ba nằm trong Pages Functions chứ không lộ ra bundle, và tên miền riêng chạy HTTPS mà không phải đụng vào bảng DNS.
Pages phù hợp với gì
Pages là dịch vụ host file tĩnh: nó lấy đúng thư mục output sau khi build, đẩy lên mạng edge và phục vụ từ PoP gần người dùng, giống hệt cách asset có hash được cache trong Phần 6. Không có máy chủ nào “chạy liên tục” cho bạn. Phần động, nếu có, chạy dưới dạng Pages Functions, tức là Worker được gắn sẵn vào dự án, thức dậy theo từng request rồi biến mất.
| Phù hợp | Không phù hợp |
|---|---|
| SPA React, Vue, Svelte, Angular | Ứng dụng cần máy chủ chạy liên tục: WebSocket giữ lâu, tác vụ nền, cron trong tiến trình |
| Trang tĩnh sinh sẵn: Astro, Hugo, Eleventy, VitePress | Ứng dụng cần ghi vào hệ thống file cục bộ |
| Next.js, Nuxt, SvelteKit chạy SSR qua Functions (dùng adapter của framework) | Phần mềm cần cài lên máy chủ riêng: WordPress, PHP, Laravel |
| Trang tài liệu, portfolio, landing page | Ứng dụng có backend nặng, nhiều kết nối database truyền thống |
Cách tự trả lời nhanh: sau npm run build, nếu thư mục output chỉ gồm HTML, JS, CSS và ảnh, mở bằng bất kỳ static server nào cũng chạy, thì Pages là chỗ của nó. Nếu bạn phải gõ node server.js mới có trang, bạn đang cần một runtime: với Astro là @astrojs/cloudflare, SvelteKit là @sveltejs/adapter-cloudflare, Nuxt là preset Cloudflare của Nitro, Next.js là adapter do Cloudflare duy trì. Các adapter này đóng gói phần server thành Functions; chúng chạy được nhưng chịu giới hạn CPU và API của runtime Workers, nên hãy đọc trang docs của adapter trước khi cam kết, đừng đoán.
Còn WordPress, PHP, hay bất cứ thứ gì cần MySQL cạnh bên: đó vẫn là việc của Hostinger hay VPS. Pages không thay được, và cũng không nên cố.
Tạo dự án Pages
- Dashboard Cloudflare → Workers & Pages → Create → tab Pages → Connect to Git.
- Cho phép Cloudflare truy cập repo GitHub hoặc GitLab của bạn, chọn repo. Với GitHub, bạn có thể giới hạn quyền ở đúng một repo thay vì cả tài khoản.
- Khai báo cấu hình build: production branch, build command, output directory.
- Thêm biến môi trường nếu cần (Settings → Environment variables). Có hai môi trường riêng: Production và Preview.
- Bấm Save and Deploy. Vài phút sau bạn có URL dạng
my-app.pages.dev.
Bảng cấu hình build theo framework
| Framework | Build command | Output directory |
|---|---|---|
| Vite (React, Vue, Svelte) | npm run build | dist |
| Create React App | npm run build | build |
Next.js (static export, output: 'export') | npm run build | out |
| Nuxt 3 (static) | npm run generate | .output/public |
| Astro | npm run build | dist |
| SvelteKit (adapter Cloudflare) | npm run build | .svelte-kit/cloudflare |
| VitePress | npx vitepress build | .vitepress/dist (tính từ thư mục docs) |
| Eleventy | npx @11ty/eleventy | _site |
| Hugo | hugo | public |
Pages có preset cho từng framework, chọn đúng preset là hai ô này tự điền. Nhưng preset chỉ là mặc định của framework: nếu bạn đã đổi build.outDir trong vite.config.ts hay outDir trong astro.config.mjs, phải sửa ô output cho khớp. Lỗi “deploy xong trang trắng” hoặc “404 ở trang chủ” gần như luôn là sai ô này.
Những ô cấu hình ít người để ý
- Production branch: nhánh duy nhất sinh ra bản production (thường là
main). Mọi nhánh khác chỉ sinh preview. - Root directory: với monorepo, trỏ vào thư mục chứa
package.jsoncủa app (ví dụapps/web). Không đặt thì Pages coi gốc repo là gốc dự án và sẽ không tìm thấy script build. - Phiên bản Node: đặt biến môi trường
NODE_VERSION(ví dụ22) hoặc commit file.nvmrc. Không khai báo thì Pages dùng phiên bản mặc định của nó, có thể khác máy bạn và làm build fail ở một dependency native nào đó. - Package manager: Pages nhìn lockfile trong repo để chọn
npm,pnpmhayyarn. Commit lockfile, và chỉ commit một lockfile. - Biến môi trường theo môi trường: Preview có thể trỏ
VITE_API_URLsang API staging, còn Production trỏ sang API thật. Với giá trị nhạy cảm, chọn kiểu secret (mã hoá, chỉ hiện một lần) và chỉ đọc nó từ Functions.
Pages cũng tự bơm vài biến hữu ích vào lúc build: CF_PAGES=1, CF_PAGES_BRANCH, CF_PAGES_COMMIT_SHA, CF_PAGES_URL. Muốn in số commit lên footer để QA biết đang xem bản nào, đọc CF_PAGES_COMMIT_SHA trong bước build là xong.
Nguy hiểm: biến môi trường ở frontend không phải là bí mật. Biến dạng
VITE_*hayNEXT_PUBLIC_*được nhúng thẳng vào bundle JavaScript lúc build; ai mở DevTools cũng đọc được. Đừng bao giờ đặt khoá API riêng tư, token, hay chuỗi kết nối database vào đó. Secret thật phải nằm ở backend hoặc trong Pages Functions, nơi code chạy phía máy chủ. Cách tự kiểm tra sau mỗi lần build:grep -rl "vài-ký-tự-đầu-của-secret" dist/— nếu có kết quả, secret đã lọt vào bundle. Cơ chế build-time và runtime của Vite được giải thích kỹ trong bài về env var và mode của Vite.
Preview deployment — tính năng đáng giá nhất
Mỗi nhánh và mỗi pull request được deploy thành một URL riêng. Có hai dạng địa chỉ:
- URL theo hash của deployment, ví dụ
abc123.my-app.pages.dev, tồn tại vĩnh viễn, mỗi commit một cái. - Alias theo tên nhánh, ví dụ
fix-api.my-app.pages.dev(tên nhánh được viết thường, ký tự lạ đổi thành gạch ngang), luôn trỏ tới commit mới nhất của nhánh đó.
Designer và QA xem được thay đổi trước khi merge, không cần cài gì, không cần hỏi “chạy ở máy anh chưa”. Bản production chỉ sinh ra từ nhánh bạn chỉ định. Với GitHub, Pages còn ghi trạng thái build và link preview thẳng vào PR.
Vài thứ đáng bật ngay:
- Biến môi trường riêng cho Preview: trỏ preview sang API staging để QA không ghi dữ liệu thử vào production.
- Bảo vệ preview: Settings → General → Enable access policy. Từ đó chỉ người đăng nhập qua Cloudflare Access (Zero Trust) mới xem được URL preview; tên miền production không bị ảnh hưởng. Rất đáng nếu preview chứa tính năng chưa công bố.
- Rollback: mỗi deployment là bất biến. Deploy hỏng thì vào danh sách Deployments, chọn bản trước, Rollback. Không cần revert commit rồi chờ build lại.
File _redirects — SPA fallback và chuyển hướng
Đặt file tên _redirects (không có phần mở rộng) trong thư mục output. Với Vite, đặt ở public/_redirects để nó được copy nguyên vào dist/ khi build; Astro, CRA, Next.js static export cũng dùng thư mục public/ cho việc này.
# SPA fallback: mọi đường dẫn chưa khớp file thật đều trả index.html với mã 200
/* /index.html 200
# Chuyển hướng vĩnh viễn, giữ phần đuôi
/old-blog/* /blog/:splat 301
/tai-lieu /docs 301
# Chuyển hướng tạm thời (không ghi mã thì mặc định là 302)
/khuyen-mai /sale
Mỗi dòng gồm ba phần: nguồn, đích, mã trạng thái. Dấu * khớp tham lam phần còn lại và được tham chiếu lại bằng :splat; :ten khớp đúng một đoạn đường dẫn và dùng lại được ở đích (/user/:id /profile/:id 301). Rule được xét từ trên xuống, request khớp rule đầu tiên là dừng, và một redirect không được nối tiếp vào redirect khác: có /a /b 200 và /b /c 200 thì /a chỉ tới /b.
Ghi chú: dòng đầu tiên giải quyết lỗi F5 bị 404 của SPA. Khi người dùng đang ở
/aboutvà nhấn F5, máy chủ tĩnh đi tìm fileaboutvà không thấy. Dòng/* /index.html 200bảo Pages: đường dẫn nào không khớp file thật thì trảindex.htmlvới mã 200, để React Router hay Vue Router đọc URL và render đúng trang. Mã 200 rất quan trọng — nếu để 404, công cụ tìm kiếm không index trang và một số thư viện coi là lỗi. Đây là phiên bản Cloudflare Pages của “SPA fallback”; phiên bản.htaccesscho Hostinger nằm ở Phần 6.
Có một chi tiết Pages làm thay bạn: nếu thư mục output không có file 404.html, Pages tự coi dự án là SPA và trả index.html với mã 200 cho mọi đường dẫn không khớp file. Nếu có 404.html, Pages coi đó là site tĩnh và trả trang 404 đó. Vì vậy với SPA Vite thuần, không có dòng fallback web vẫn chạy; dòng đó là cách viết tường minh để ai đọc repo cũng hiểu ý bạn. Sau khi thêm, hãy kiểm tra một asset vẫn trả đúng loại nội dung: curl -sI https://my-app.pages.dev/assets/index-xxxx.js | grep -i content-type phải ra javascript, không phải text/html. Nếu asset bỗng trả HTML, bỏ dòng fallback và dựa vào hành vi mặc định.
Proxy trong _redirects chỉ đi được trong cùng site
Netlify cho phép dòng dạng /api/* https://api.example.com/:splat 200 để proxy sang tên miền khác. Trên Cloudflare Pages, mã 200 (rewrite) chỉ hỗ trợ đường dẫn tương đối trong chính site của bạn, ví dụ /app/* /app/index.html 200; bạn không proxy được sang domain ngoài bằng file này. Muốn example.com/api/* chuyển tiếp xuống backend ở tên miền khác mà trình duyệt vẫn thấy same-origin, dùng Pages Functions (mục dưới) hoặc Worker ở Phần 9.
Giới hạn của file: tối đa 2.000 redirect tĩnh và 100 redirect động (có * hoặc :placeholder), mỗi dòng dưới 1.000 ký tự. Cần nhiều hơn thế thì đó là việc của Bulk Redirects trong dashboard, không phải của file này.
File _headers — đặt Cache-Control và security header
# Asset có hash — cache một năm
/assets/*
Cache-Control: public, max-age=31536000, immutable
# HTML — luôn kiểm tra lại
/*.html
Cache-Control: no-cache, must-revalidate
/
Cache-Control: no-cache, must-revalidate
# Cấu hình runtime — không cache
/config.json
Cache-Control: no-store
# Security header cho toàn bộ site
/*
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Dòng đầu mỗi khối là mẫu đường dẫn, các dòng thụt vào là header. Một request khớp nhiều khối thì nhận header của tất cả các khối đó, nên khối /* ở cuối cộng thêm security header cho asset lẫn HTML mà không xoá Cache-Control đã đặt ở trên. Muốn gỡ một header mà nền tảng tự thêm, viết dấu chấm than trước tên: ! X-Powered-By. Tối đa 100 khối, mỗi dòng dưới 2.000 ký tự. Đặt file này cùng chỗ với _redirects.
Vì sao chia ba nhóm như vậy đã được giải thích ở Phần 6: asset có hash trong tên nên đổi nội dung là đổi URL, cache một năm hoàn toàn an toàn; HTML là “bản đồ” trỏ tới asset nên phải luôn mới; config.json hay service-worker.js mà bị cache thì sinh lỗi rất khó gỡ. Thực tế Pages đã cache asset ở edge cho tới lần deploy kế tiếp, nhưng Cache-Control là thứ trình duyệt đọc — không có nó, mỗi lần mở trang trình duyệt vẫn hỏi lại từng file.
Cạm bẫy: header trong
_headerskhông áp dụng cho response do Pages Functions sinh ra. Nếu bạn viết/api/*bằng Functions và muốn nó cóCache-Control: no-storehay header CORS, phải đặt ngay trong code của Function. Nhiều người thêm/api/*vào_headers, thấy không có tác dụng, rồi quay sang nghi ngờ cache.
Security header ở đây làm cùng việc với Response Header Transform Rule ở Phần 7. Chọn một nơi: dự án Pages thì _headers đi theo repo, review được trong PR; site trên Hostinger thì Transform Rule. Riêng Content-Security-Policy là header cần thiết kế riêng cho từng app, đừng copy từ mẫu — xem bài về CSP.
Pages Functions — thêm backend nhẹ
Tạo thư mục functions/ ở gốc repo (không phải trong public/ hay dist/). Mỗi file thành một endpoint theo quy ước đường dẫn:
functions/
_middleware.js → chạy trước mọi request tới Functions
api/
hello.js → /api/hello
users/[id].js → /api/users/123 (context.params.id = "123")
docs/[[path]].js → /api/docs/a/b/c (context.params.path = ["a", "b", "c"])
// functions/api/hello.js
export async function onRequestGet(context) {
const { request, env } = context;
// env chứa biến môi trường và secret — KHÔNG lộ ra frontend
const apiKey = env.THIRD_PARTY_API_KEY;
return new Response(
JSON.stringify({
message: 'Xin chào từ edge',
country: request.headers.get('CF-IPCountry'),
time: new Date().toISOString(),
}),
{ headers: { 'Content-Type': 'application/json' } }
);
}
Tên hàm quyết định method: onRequestGet, onRequestPost, onRequestPut, onRequestDelete, hoặc onRequest cho mọi method. context gồm request (Request chuẩn web), env (biến môi trường, secret và các binding như KV, D1, R2), params (đoạn động trong đường dẫn), waitUntil (chạy nốt việc sau khi đã trả response, ví dụ ghi log) và next (dành cho middleware).
Ứng dụng thực tế: giấu khoá API bên thứ ba
Đây là cách gọn nhất để giấu khoá API. Frontend gọi /api/weather của chính mình; Function ở giữa thêm khoá bí mật rồi gọi sang dịch vụ thật. Khoá nằm ở phía máy chủ, không bao giờ xuống trình duyệt. Và vì cùng tên miền, không có CORS, không có preflight.
// functions/api/weather.js
export async function onRequestGet({ request, env }) {
const city = new URL(request.url).searchParams.get('city') ?? 'Hanoi';
const upstream = new URL('https://api.weather.example/v1/current');
upstream.searchParams.set('q', city);
// secret đặt trong Settings → Environment variables (kiểu secret)
upstream.searchParams.set('key', env.WEATHER_API_KEY);
const res = await fetch(upstream, {
// để edge cache 5 phút, đỡ tốn quota của dịch vụ bên thứ ba
cf: { cacheTtl: 300, cacheEverything: true },
});
if (!res.ok) {
return new Response(JSON.stringify({ error: 'upstream failed' }), {
status: 502,
headers: { 'Content-Type': 'application/json' },
});
}
const data = await res.json();
return new Response(JSON.stringify({ city, temp: data.temp_c }), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store', // nhớ: _headers không áp cho Functions
},
});
}
Phía React chỉ còn fetch('/api/weather?city=Hanoi'). Không có VITE_WEATHER_KEY nào trong bundle để mà lộ.
Middleware và _routes.json
functions/_middleware.js chạy trước mọi Function cùng thư mục (đặt ở functions/api/_middleware.js thì chỉ bao /api/*). Dùng nó để bắt lỗi chung, kiểm tra header xác thực, hay thêm header cho mọi response:
// functions/_middleware.js
export async function onRequest(context) {
try {
const response = await context.next();
response.headers.set('X-Robots-Tag', 'noindex'); // ví dụ: không cho index API
return response;
} catch (err) {
return new Response(JSON.stringify({ error: err.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
Mặc định Pages chỉ gọi Function khi đường dẫn khớp một file trong functions/; asset tĩnh được phục vụ thẳng, không tính vào quota. Nếu bạn dùng adapter SSR (mọi request đều qua Function), file _routes.json trong thư mục output cho phép loại trừ asset ("exclude": ["/assets/*"]) để không đốt quota và CPU cho file tĩnh.
Chạy thử ở máy
# Build frontend như thường
npm run build
# Chạy dist/ kèm functions/ bằng runtime giống production
npx wrangler pages dev dist
# Deploy thẳng từ máy hoặc từ CI của bạn, không cần nối Git
npx wrangler pages deploy dist --project-name my-app
wrangler pages dev cho bạn localhost với đúng runtime Workers; biến môi trường cho lúc dev đặt trong file .dev.vars ở gốc repo (thêm vào .gitignore). wrangler pages deploy hữu ích khi bạn muốn tự kiểm soát pipeline trong GitHub Actions: chạy test trước, build xong mới deploy, thay vì để Pages build hộ.
Ghi chú: Functions được tính như Workers — gói Free có 100.000 request mỗi ngày cho cả tài khoản. Trong Settings của dự án có tuỳ chọn khi vượt quota: fail open (vẫn phục vụ asset tĩnh, chỉ Functions ngừng) hoặc fail closed (chặn hết). Chọn fail open cho site công khai; và nếu bạn dùng SSR cho mọi trang thì 100.000 request mỗi ngày là con số phải nhìn kỹ.
Gắn tên miền riêng
- Trong dự án Pages → Custom domains → Set up a custom domain.
- Nhập
example.com. - Nếu tên miền đã ở cùng tài khoản Cloudflare, bản ghi CNAME được tạo tự động (
example.comtrỏ tớimy-app.pages.dev, đám mây cam, CNAME Flattening lo phần tên miền gốc) và chứng chỉ cấp tự động. Chờ vài phút. - Thêm tiếp
www.example.comnếu muốn, rồi tạo Redirect Rule như Phần 7 để gom về một địa chỉ. Bản ghiwwwphải tồn tại và để đám mây cam thì rule mới có chỗ để chạy.
Nếu tên miền nằm ở tài khoản Cloudflare khác hoặc ở nhà cung cấp DNS khác, Pages sẽ yêu cầu bạn tự tạo CNAME trỏ tới my-app.pages.dev và xác minh. Chi tiết từng màn hình có trong bài gắn tên miền cho Cloudflare Pages.
Kiểm tra:
dig example.com +short # IP của Cloudflare (104.x hoặc 172.67.x)
curl -sI https://example.com | grep -iE "^(HTTP|server|cf-ray)"
curl -sI https://www.example.com | grep -iE "^(HTTP|location)" # 301 về example.com
Giới hạn của gói Free
| Hạng mục | Giới hạn |
|---|---|
| Băng thông | Không giới hạn |
| Số lần build | 500 mỗi tháng |
| Build đồng thời | 1 |
| Thời gian một lần build | 20 phút |
| Số file mỗi lần deploy | 20.000 |
| Kích thước mỗi file | 25 MiB |
| Số dự án mỗi tài khoản | 100 |
| Tên miền riêng mỗi dự án | 100 |
| Preview deployment | Không giới hạn số lượng |
| Functions | Tính chung quota Workers: 100.000 request mỗi ngày |
Giới hạn có thể thay đổi theo thời gian — kiểm tra trang tài liệu chính thức của Cloudflare trước khi phụ thuộc vào một con số cụ thể.
Hai con số hay chạm phải trong thực tế:
- 500 build mỗi tháng nghe nhiều, nhưng mỗi push lên bất kỳ nhánh nào là một build. Một team năm người push đều tay là hết. Cách tiết kiệm: giới hạn nhánh được preview (Settings → Builds & deployments), hoặc chuyển sang
wrangler pages deploytừ CI để chỉ deploy khi test đã pass. - 20.000 file mỗi lần deploy thường bị vượt bởi thư mục ảnh sinh ra từ image optimization hay một site tài liệu quá lớn. Kiểm tra bằng
find dist -type f | wc -ltrước khi đổ lỗi cho Pages.
Sai lầm hay gặp và cách nhận biết
| Triệu chứng | Nguyên nhân thường gặp | Cách kiểm tra |
|---|---|---|
| Deploy thành công nhưng trang trắng hoặc 404 ngay trang chủ | Sai output directory | Xem log build, dòng “Found N files in output directory” — N phải khác 0 |
F5 ở /about bị 404 | Có 404.html trong output (Pages chuyển sang chế độ site tĩnh) hoặc sai đường dẫn base | Kiểm tra thư mục output; thêm /* /index.html 200 vào _redirects |
_redirects hay _headers không có tác dụng | File nằm ở src/ thay vì public/, hoặc bị thêm phần mở rộng .txt | ls dist/_redirects sau khi build ở máy |
| Asset trả về HTML thay vì JS | Rule fallback bắt cả asset, hoặc asset không tồn tại (hash cũ từ HTML cache) | curl -sI URL-asset xem content-type |
| Build ở máy chạy, trên Pages fail | Khác phiên bản Node, hoặc tool build nằm ở devDependencies mà môi trường build bỏ qua | Đặt NODE_VERSION; đưa tool build vào dependencies |
| Function trả lỗi 500 | Thiếu biến môi trường ở môi trường đó (Preview khác Production) | Xem log Functions của deployment; kiểm tra biến ở cả hai môi trường |
| Gọi API bên thứ ba từ trình duyệt bị CORS | Gọi thẳng thay vì qua Function cùng origin | Network tab: request đi tới domain ngoài thay vì /api/* |
| PR không sinh preview | Nhánh bị loại trong preview branch control, hoặc build đồng thời đang bận | Xem danh sách Deployments và cài đặt preview branches |
Về devDependencies: nếu Vite, TypeScript hay Tailwind nằm trong devDependencies mà môi trường build đặt NODE_ENV=production, npm sẽ bỏ qua chúng và build fail với “vite: command not found”. Cách bền nhất là để tool build trong dependencies của dự án frontend, vì với site tĩnh khái niệm “dev dependency” không có nhiều ý nghĩa: không có runtime production nào cài package cả.
Lab 8.1 — Deploy SPA lên Pages (45 phút)
- Tạo project:
npm create vite@latest my-cf-app -- --template react, càireact-router-dom, đẩy lên GitHub. - Thêm React Router với ít nhất hai route (
/và/about), có link qua lại giữa hai trang. - Tạo
public/_redirectsvới dòng/* /index.html 200. - Tạo
public/_headerstheo mẫu ở mục_headersphía trên. - Kết nối repo với Pages: preset Vite, build command
npm run build, outputdist. Deploy. - Mở URL
.pages.dev, vào/aboutrồi nhấn F5 — phải chạy, không 404. - Kiểm tra header:
curl -sI https://my-app.pages.dev/assets/index-xxxx.js | grep -i cache-controlphải thấymax-age=31536000. Gọicurl -sI https://my-app.pages.dev/,Cache-Controlphải làno-cache. - Tạo một nhánh mới, sửa gì đó, mở PR — quan sát Cloudflare tự tạo URL preview và ghi vào PR.
- Gắn tên miền riêng của bạn vào dự án, thêm
wwwvà Redirect Rule gom về một địa chỉ. - Thêm: tạo
functions/api/hello.jsnhư ví dụ, push, gọicurl https://my-app.pages.dev/api/hello— phải thấy JSON có mã quốc gia của bạn.
Kết quả mong đợi: website chạy trên tên miền riêng, F5 ở route con không lỗi, asset được cache một năm còn HTML thì không, mỗi PR sinh ra một URL xem trước, và
/api/hellotrả JSON từ edge mà không có khoá nào trong bundle.
Tóm tắt
- Pages host file tĩnh từ edge: đúng cho SPA và site tĩnh, sai cho WordPress hay app cần máy chủ chạy liên tục; SSR chạy được qua adapter nhưng chịu giới hạn của Workers.
- Cấu hình build gồm production branch, build command, output directory; sai output directory là nguyên nhân số một của “deploy xong trang trắng”.
- Biến
VITE_*vàNEXT_PUBLIC_*nằm trong bundle — không phải bí mật. Secret đặt ở Functions, đọc quaenv. - Mỗi nhánh và PR có preview URL riêng; bật access policy nếu preview chứa thứ chưa công bố; deploy hỏng thì rollback.
_redirectslo SPA fallback (mã 200) và chuyển hướng, không proxy được sang domain ngoài._headersloCache-Controlvà security header, nhưng không áp cho response của Functions.- Pages Functions là Worker gắn vào dự án: đường dẫn theo file, secret trong
env, cùng origin nên không CORS; quota 100.000 request mỗi ngày ở gói Free. - Tên miền trong cùng tài khoản gắn vào Pages chỉ mất vài phút, DNS và chứng chỉ tự lo.
Đọc thêm
- Cloudflare Pages docs
- Cloudflare Workers docs — runtime mà Pages Functions chạy trên đó
- Cloudflare Rules docs — Redirect Rule cho www và tên miền gốc