随记

Cloudflare 连接 GitHub 仓库并部署静态网站

最近把一个原本运行在微信小程序里的项目迁移成了 Web/PWA 版本,前端使用 Vue 3 + Vite。后端仍然运行在自己的服务器上,前端则希望找一个不需要额外维护 Nginx、支持 HTTPS、还能自动跟 GitHub 同步的平台。最后选择了 Cloudflare Pages。

实际用下来,这套流程非常适合个人项目:代码推到 GitHub,Cloudflare 自动拉取、构建、部署,之后每次更新 main 分支都可以自动发布。

整体结构

最终的部署结构其实很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GitHub Repository
│
│ push
▼
Cloudflare Pages
│
│ npm run build
▼
dist
│
▼
https://xxx.pages.dev
│
├── 静态 HTML / CSS / JS
│
└── 请求 API
│
▼
FastAPI Backend

Cloudflare Pages 只负责前端静态资源,并没有把 FastAPI 后端一起搬过去。这一点比较重要。像 Vue、React、Vite 这类项目经过构建后,本质上都会得到一套 HTML、CSS 和 JavaScript 静态文件,因此非常适合放到 Pages 上。


先把前端放到 GitHub

项目本地目录大致如下:

1
2
3
4
5
6
web/
├── src/
├── public/
├── package.json
├── vite.config.ts
└── ...

先初始化 Git:

1
2
3
4
git init
git add .
git commit -m "feat: initial web version"
git branch -M main

然后在 GitHub 创建一个仓库,再添加远程地址:

1
2
git remote add origin https://github.com/<username>/<repo>.git
git push -u origin main

这里之前踩过一个很小的坑。一开始使用的是 SSH 地址:

1
git@github.com:xxx/xxx.git

但本地没有配置 GitHub SSH Key,因此出现:

1
Permission denied (publickey)

如果不打算专门配置 SSH,直接换成 HTTPS 地址即可:

1
git remote set-url origin https://github.com/<username>/<repo>.git

对于个人项目来说会省事很多。另外,下面这些文件不要提交:

1
2
3
4
node_modules/
dist/
.env
.env.local

尤其是 .env,里面如果存在服务器配置或者密钥,不应该进入 GitHub。


在 Cloudflare Pages 连接 GitHub

进入 Cloudflare 控制台后创建 Pages 项目,选择:

1
2
3
4
Workers & Pages
→ Create
→ Pages
→ Connect to Git

授权 Cloudflare 访问 GitHub 后,就可以看到 GitHub 仓库。选择需要部署的仓库即可。这里有一个很容易弄混的地方:

Vue/Vite 静态网站应该创建 Cloudflare Pages 项目,不是直接使用 Workers 部署。

之前曾经尝试过:

1
npx wrangler deploy

结果走成了 Workers 的部署方式。虽然 Cloudflare Pages 和 Workers 现在在控制台里结合得越来越紧,但如果只是部署一个 Vue 静态网站,直接使用 Pages + Git Integration 会简单得多。


配置 Vue / Vite 构建参数

连接仓库后,Cloudflare 会要求填写构建配置。对于普通 Vue 3 + Vite 项目,可以这样配置:

1
2
3
4
5
6
7
8
9
10
11
Production branch:
main

Framework preset:
Vue / Vite

Build command:
npm run build

Build output directory:
dist

如果 GitHub 仓库本身就是前端项目:

1
2
3
4
repo/
├── package.json
├── src/
└── vite.config.ts

那么:

1
Root directory

可以直接留空。

如果前端放在仓库里的子目录:

1
2
3
repo/
├── server/
└── web/

那 Root directory 才需要填写:

1
web

Cloudflare 在部署时实际上做的事情和本地差不多:

1
2
npm install
npm run build

然后把:

1
dist/

部署到 CDN。

构建成功以后,会得到一个默认地址:

1
https://项目名.pages.dev

到这里,一个免费的 HTTPS 静态网站就已经上线了。


前端调用后端 API

静态网站通常还会调用自己的后端。例如:

1
2
3
4
5
Vue
↓
https://<API域名>/api/records
↓
FastAPI

Vite 项目一般使用环境变量:

1
VITE_API_BASE_URL=https://<API域名>

在代码中:

1
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL

Cloudflare Pages 中可以在:

1
2
Settings
→ Environment variables

添加:

1
VITE_API_BASE_URL

值填写正式 API 地址。

VITE_* 环境变量并不是服务器密钥。

Vite 会在:

1
npm run build

的时候直接把它写进浏览器最终下载的 JavaScript 中。

因此下面这种配置没问题:

1
VITE_API_BASE_URL=https://api.example.com

但绝对不能写:

1
2
3
VITE_SECRET_KEY=xxx
VITE_TENCENT_SECRET_KEY=xxx
VITE_OPENAI_API_KEY=xxx

真正的 Secret 应该始终留在 FastAPI 后端。


修改环境变量后为什么没有生效?

这里也踩过一次坑。Cloudflare Pages 修改:

1
VITE_API_BASE_URL

以后,已经部署出去的网页不会自动变化。因为 Vite 的环境变量是在构建阶段写进去的。流程实际上是:

1
2
3
4
5
6
7
VITE_API_BASE_URL
↓
npm run build
↓
JavaScript Bundle
↓
浏览器

所以修改环境变量后需要:

1
Redeploy

重新构建一次。否则网页仍然会使用上一次构建时的值。当时就出现过前端错误地请求:

1
https://xxx.pages.dev/api/...

而不是:

1
https://<API域名>/api/...

重新配置 VITE_API_BASE_URL 并部署后就恢复正常了。


后端还需要处理 CORS

前端和 API 不在同一个域名下:

1
2
3
4
5
Frontend:
https://xxx.pages.dev

Backend:
https://api.example.com

对于浏览器来说,这是两个不同的 Origin。因此 FastAPI 需要允许 Pages 域名访问。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://xxx.pages.dev",
"https://app.example.com",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

不太建议为了方便直接:

1
allow_origins=["*"]

正式环境最好明确允许自己的前端域名。如果还会使用 Cloudflare 的 Preview Deployment,例如:

1
https://xxxxx.project.pages.dev

也可以根据实际情况配置规则匹配 Preview 域名。


Vue Router 还需要 SPA 回退

Vue Router 使用 History 模式时,还有一个典型问题。例如直接访问:

1
https://example.pages.dev/mine

服务器实际上找不到:

1
/mine

这个 HTML 文件。因为真正存在的只有:

1
index.html

所以需要告诉 Cloudflare:

不管访问哪个前端路由,都先返回 index.html,然后交给 Vue Router 处理。

可以在:

1
public/_redirects

加入:

1
/* /index.html 200

构建完成后:

1
dist/_redirects

也会存在。

这样:

1
2
3
4
/
/mine
/record
/review

这些路由直接刷新就不会出现 404。


GitHub 和 Cloudflare 最方便的地方:自动部署

完成 GitHub Integration 后,日常开发流程就非常简单了。

例如:

1
2
3
git add .
git commit -m "feat: add new feature"
git push

Cloudflare 会检测到 GitHub 更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
Git push
↓
Cloudflare 收到 webhook
↓
拉取最新代码
↓
npm install
↓
npm run build
↓
部署 dist
↓
全球 CDN 更新

不再需要:

1
scp dist服务器

也不需要:

1
上传FTP

更不用手动维护前端 Nginx。

对于一个个人项目来说,这一点体验很好。


Preview Deployment 也很实用

Cloudflare Pages 并不是只有正式环境。例如开发一个新功能:

1
2
git switch -c feature/new-page
git push -u origin feature/new-page

Cloudflare 可以给这个分支创建一个独立 Preview 地址。这样可以先测试:

1
2
3
4
5
6
7
8
9
feature branch
↓
Cloudflare Preview
↓
确认没问题
↓
Merge main
↓
Production

比直接往正式网站上改安全很多。现在比较习惯的流程也是:

1
2
3
4
5
6
7
8
9
feature/*
↓
Cloudflare Preview
↓
测试
↓
main
↓
正式部署

最后再绑定自己的域名

默认的:

1
xxx.pages.dev

其实已经可以正常使用。如果希望网站看起来更正式,可以在 Pages:

1
Custom domains

添加:

1
app.example.com

如果 DNS 本身不托管在 Cloudflare,而是在其他 DNS 服务商,可以选择:

1
My DNS provider

然后按照 Cloudflare 提示添加 CNAME。通常类似:

1
2
3
类型:CNAME
主机记录:app
记录值:xxx.pages.dev

等待 DNS 生效后:

1
https://app.example.com

就会指向 Cloudflare Pages。HTTPS 证书也由 Cloudflare 自动处理。


这套部署方式适合什么项目

折腾完之后,会发现 Cloudflare Pages 并不是什么复杂的“云原生部署平台”,如果只是从个人开发角度理解,可以把它看成:

GitHub 仓库的自动构建 + 静态网站托管 + CDN + HTTPS。

对于:

Vue、React、Vite、个人博客、文档站、PWA、项目官网

都很合适。这次迁移之后,整个项目也变成了比较清晰的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GitHub
│
└── Vue / Vite
│
▼
Cloudflare Pages
│
▼
用户浏览器
│
▼
FastAPI API
│
├── SQLite
├── AI 服务
└── 邮件服务

前端更新交给 GitHub + Cloudflare,后端继续运行在自己的服务器上,两边互不影响。

相比把所有东西都部署在一台服务器上,这种方式最大的感受不是“性能提升了多少”,而是前端部署变得非常省心:提交代码之后,剩下的事情基本都由 Cloudflare 自动完成。