我用 Cloudflare Pages 搭了这整个站 — 新闻聚合、API 端点、数据库、自定义域名 — 总成本 $0。不是「免费试用期 $0」,是真正的、永久的、$0。这篇教程把从建仓库到上线运行的每一步都写清楚,包括我自己踩过的坑。

为什么选 Cloudflare Pages

我对比过几个主流的免费托管方案,最后选了 Cloudflare Pages,原因很简单:它不只是托管静态文件,而是给你一整套后端能力

对比一下:

给完全不懂技术的读者:可以把 Cloudflare Pages 想成一个免费的网站托管服务,它不仅帮你把网页放上去让全世界能访问,还额外送你一个 API 后端和一个数据库。传统方案里这三样东西(托管 + 后端 + 数据库)要分别购买和部署,这里全免费打包了。

Step 1 — 准备 GitHub 仓库

Cloudflare Pages 通过 GitHub 仓库拉取代码。第一步就是创建一个仓库并推送你的网站文件。

# 创建项目目录
mkdir my-site && cd my-site
git init

# 创建一个最简单的首页
cat <<'EOF' > index.html
<!doctype html>
<html>
  <head><title>My Site</title></head>
  <body><h1>Hello World</h1></body>
</html>
EOF

# 推送到 GitHub
git add .
git commit -m "init: first page"
git remote add origin https://github.com/your-name/my-site.git
git push -u origin main

就这么简单。不需要 package.json,不需要构建脚本,不需要任何框架。一个 HTML 文件就能部署。

你不需要 React、Vue、Next.js 或任何框架就能开始。这个站(cyrustyj.xyz)用的就是原生 HTML + CSS + JS,没有构建步骤。框架是锦上添花,不是前置条件。

Step 2 — 连接 Cloudflare Pages

接下来把 GitHub 仓库和 Cloudflare Pages 连起来:

  1. 注册 Cloudflare 账号(免费)
  2. 进入 Dashboard → Workers & PagesCreate
  3. 选择 PagesConnect to Git
  4. 授权 GitHub,选择你的仓库
  5. 构建设置:
    • Build command:留空(纯静态站不需要构建)
    • Build output directory/(项目根目录就是输出目录)
  6. 点击 Save and Deploy

大约 30 秒后,你的站就上线了。Cloudflare 会分配一个 *.pages.dev 的临时域名,比如 my-site-abc.pages.dev,可以直接访问。

从现在开始,每次你 push 代码到 main 分支,Cloudflare Pages 都会自动部署。不需要手动操作。

Step 3 — 绑定自定义域名

*.pages.dev 域名也能凑合,但自定义域名更专业。步骤:

  1. 在 Cloudflare Pages 项目的 Custom domains 标签页点击 Set up a custom domain
  2. 输入你的域名(比如 cyrustyj.xyz
  3. 如果你的域名 DNS 已经托管在 Cloudflare(推荐),CNAME 记录会自动创建
  4. 等待 DNS 生效(通常几分钟),SSL 证书自动签发
我踩过的坑:部署成功但访问 403
我第一次部署时,Cloudflare Pages 显示部署成功,*.pages.dev 域名也能正常访问,但自定义域名 cyrustyj.xyz 返回 403 Forbidden。排查了半天,原因很简单:部署成功不代表域名自动绑定。你必须在 Pages 项目的 Custom Domains 里手动添加你的域名。Cloudflare 不会自动把你的域名关联到 Pages 项目,即使这个域名的 DNS 就在 Cloudflare 上。
如果你的域名还没有注册,直接在 Cloudflare 买。它卖域名是成本价(不加利润),而且 DNS 天然就托管在 Cloudflare 上,省去了在第三方注册商那边改 nameserver 的步骤。

Step 4 — Pages Functions(免费 API)

这是 Cloudflare Pages 最让我惊喜的部分:不需要单独部署后端,在项目里放一个 JS 文件就能创建 API 端点

文件路由

Pages Functions 用文件路径决定 API 路由:

目录结构 → API 路由functions/
  api/
    visits.js      →  /api/visits
    favorites.js   →  /api/favorites
    news.js        →  /api/news

放进去,push 到 GitHub,API 就自动上线了。

实际例子:访问计数器

这是我这个站真实在用的访问计数 API(简化版):

functions/api/visits.js(简化版)export async function onRequest(context) {
  const { request, env } = context;
  const url = new URL(request.url);
  const path = url.searchParams.get("path") || "/";
  const db = env.DB;  // D1 数据库绑定

  if (!db) {
    return Response.json({ ok: true, totalVisits: 0 });
  }

  // 记录访问
  const today = new Date().toISOString().slice(0, 10);
  await db.prepare(
    "INSERT OR IGNORE INTO page_visits (path, visit_date) VALUES (?, ?)"
  ).bind(path, today).run();

  // 查询统计
  const row = await db.prepare(
    "SELECT COUNT(*) AS count FROM page_visits WHERE path = ?"
  ).bind(path).first();

  return Response.json({
    ok: true,
    path,
    totalVisits: row?.count || 0
  });
}

核心就这么多。onRequest 是 Pages Functions 的入口函数,context 里包含 request(HTTP 请求)和 env(环境变量和数据库绑定)。

Pages Functions 运行在 Cloudflare Workers 的 V8 isolates 上,不是 Node.js。大多数标准 Web API(fetchRequestResponseURLcrypto)都能用,但 fspath 等 Node.js 专有模块不可用。好消息是,写 API 端点基本不需要那些模块。

Step 5 — D1 数据库

光有 API 还不够,数据总得存在某个地方。Cloudflare D1 是一个 Serverless 的 SQLite 数据库,免费版给 5GB 存储。

创建 D1 数据库

  1. 在 Cloudflare Dashboard → Workers & PagesD1 SQL DatabaseCreate
  2. 给数据库起个名字,比如 my-site-db
  3. 创建完成后,记住数据库的 ID

绑定到 Pages 项目

回到 Pages 项目 → SettingsBindingsAdd → 选择 D1 Database

绑定后必须重新部署
D1 绑定添加后,已部署的版本不会自动获得绑定。你需要触发一次新的部署(push 一个 commit 或手动在 Dashboard 点 Retry deployment)。否则 env.DB 会是 undefined,你的 Functions 找不到数据库。

表结构示例

你可以在 D1 的控制台直接执行 SQL,也可以在 Functions 里自动建表。这是我这个站用的访问记录表:

SQL — 建表CREATE TABLE IF NOT EXISTS page_visits (
  id         INTEGER PRIMARY KEY AUTOINCREMENT,
  path       TEXT NOT NULL,
  visit_date TEXT NOT NULL,
  visitor_hash TEXT NOT NULL,
  created_at TEXT NOT NULL,
  UNIQUE(path, visit_date, visitor_hash)
);

CREATE INDEX IF NOT EXISTS idx_visits_path
  ON page_visits(path);
CREATE INDEX IF NOT EXISTS idx_visits_path_date
  ON page_visits(path, visit_date);

D1 用的是标准 SQLite 语法,如果你写过 SQL,上手零成本。

Step 6 — GitHub Actions 自动更新

到这里你已经有了一个带 API 和数据库的静态站。但如果你想要「动态内容」呢?比如这个站的 AI 新闻聚合,每天自动更新两次。

秘密很简单:用 GitHub Actions 定时跑脚本更新数据文件,commit 并 push,Cloudflare Pages 自动重新部署

.github/workflows/update-news.yml(简化版)name: Update AI News

on:
  schedule:
    # UTC 01:00 / 13:00 → 北京时间 09:00 / 21:00
    - cron: "0 1,13 * * *"
  workflow_dispatch:  # 手动触发

jobs:
  update-news:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Build news data
        run: python scripts/update_news.py

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add data/news.json
          # 只在有变更时提交
          git diff --staged --quiet || git commit -m "chore: auto-update AI news"
          git push

这个工作流每天在北京时间 09:00 和 21:00 执行。脚本从各个 API 拉取新闻数据,生成 JSON 文件,提交到仓库。GitHub push 触发 Cloudflare Pages 自动部署 — 用户看到的是最新的内容。

这就是让静态站拥有「动态感」的技巧 — 内容本身是静态的 JSON 文件,但通过定时构建实现了自动更新。不需要服务器、不需要数据库查询、不需要后端渲染。GitHub Actions 免费版每月提供 2000 分钟构建时间,每天跑两次完全够用。

成本清单

把这个站所有用到的服务列出来:

服务 用途 免费额度 实际费用
Cloudflare Pages 静态托管 + CDN 无限带宽,500 次构建/月 $0
Pages Functions Serverless API 10 万次请求/天 $0
Cloudflare D1 数据库 5GB 存储,500 万行读取/天 $0
GitHub Actions 定时构建 2000 分钟/月 $0
自定义域名 cyrustyj.xyz Cloudflare 成本价注册 ~$9/年
SSL 证书 HTTPS 自动签发 $0

除了域名注册费(严格来说不到 $1/月),运行成本是零。如果你不介意用 *.pages.dev 域名,连域名费都省了。

踩坑记录

除了前面提到的 403 和 D1 绑定问题,再补充几个:

Functions 日志不好看
Pages Functions 的日志在 Dashboard 里不太直观。调试 API 时建议在本地用 wrangler pages dev 启动开发服务器,可以看到完整的 console.log 输出。安装方式:npm install -g wrangler
D1 的 SQL 方言问题
D1 底层是 SQLite,不是 MySQL 或 PostgreSQL。一些习惯性写法可能不兼容,比如 UPSERT 语法、日期函数、JSON 函数的行为都跟 MySQL 不同。遇到报错先查 SQLite 文档,不要照着 MySQL 教程写。
GitHub Actions 的 cron 时间不精确
GitHub Actions 的 schedule 触发通常会延迟 5-30 分钟(官方说法:「负载高时可能延迟更多」)。不要依赖它做精确定时任务。对新闻更新来说,延迟半小时完全无所谓,但如果你需要精确调度,考虑用 Cloudflare 自带的 Cron Triggers。

总结

整个流程回顾:

  1. GitHub 仓库 — 放你的 HTML/CSS/JS 文件
  2. Cloudflare Pages — 连接仓库,自动部署,全球 CDN
  3. Custom Domains — 绑定自定义域名(记得手动添加,不会自动关联)
  4. Pages Functionsfunctions/ 目录下写 JS = Serverless API
  5. D1 数据库 — 创建后绑定到 Pages 项目,通过 env.DB 使用
  6. GitHub Actions — 定时更新数据 + 自动部署 = 静态站的动态内容

对个人站来说,这套组合基本是目前能找到的最优免费方案。不用管服务器、不用管扩容、不用管 SSL — push 代码就上线。