这是一篇个人网站的完整部署记录:作者用 Astro + Supabase + Cloudflare Workers 的全托管组合,零成本搭起一个支持中英日三语的博客。文章分两半——前半是从零开始的部署步骤,照着做能得到一模一样的站;后半是九条真实踩坑记录,讲清每一步的原因和解法。核心卖点是「零运维、发布即上线、月成本 0 元」。
从零到上线:个人网站的完整部署记录
写在前面
这篇文章是这个网站的「自白书」——完整记录它是怎么从一台本地电脑里的一行行代码,变成你此刻正在浏览的这套页面的。内容分两部分:前半是从头开始的完整部署步骤(照着做,你能得到一个一模一样的站);后半是真实的踩坑记录(每一步坑我都踩过,并写下了原因和解法)。
适合谁读:想零成本搭一个个人博客的人;或者未来的我自己——当某天我又想把一切推倒重来时,希望不用再从零摸索。
成果速览:这是一个什么样的网站
先看建成后的样子,有个整体印象:
| 项目 | 现状 |
|---|---|
| 框架 | Astro 4(SSR 服务端渲染模式) |
| 数据库 | Supabase(Postgres,文章/评论/分类/站点配置全在这里) |
| 托管 | Cloudflare Workers(免费额度:10 万请求/天、无限带宽、3000 构建分钟/月) |
| 域名 | 自购域名,DNS 托管在 Cloudflare,HTTPS 自动签发 |
| 语言 | 中文 / English / 日本語 三语同源 |
| 写作后台 | 自带 /admin 后台,浏览器里直接写、直接发 |
| 文件管理 | 后台统一管理图片与附件,删除文章时自动清理关联文件,孤儿文件一键清理 |
| 摘要 | 按正文自动生成(本地规则兜底,或调 AI 生成),驱动列表卡片 / 搜索 / RSS / 分享卡片 |
| 翻译 | 后台一键把文章译成英/日版(走智谱 GLM,免费档),长文结构感知分段、并发翻译 |
| 月成本 | 0 元(域名是唯一历史支出,其余全部免费档) |
它的工作方式用一句话说清:读者访问的永远是 Cloudflare,Cloudflare 在每一个请求发生时实时去 Supabase 查文章内容返回给你。所以后台点「发布」的那一刻文章就上线了,不需要等待构建、不需要重新部署——这是整套设计里我最满意的一点。
读者浏览器 ──► Cloudflare Worker(SSR 渲染 + 静态资源)
│ 每个请求实时查询
▼
Supabase(文章 / 评论 / 分类 / 配置)
▲
后台 /admin ──────────┘ (浏览器直连 Supabase,发布即生效)
方案对比:为什么选这套,以及它的不足
选型不能只看优点。把主流方案摆在一起如实比较,你才能判断这套是否适合你。
与主流方案的横向对比
| 维度 | 本方案(Astro + Supabase + CF Workers) | WordPress | Next.js + Vercel | Hexo / Hugo(纯静态) |
|---|---|---|---|---|
| 服务器运维 | 零,全托管 | 要(或买托管服务) | 零 | 零(但发文要本地构建) |
| 发文流程 | 浏览器后台直接发,即时生效 | 后台直接发 | 同左 | 本地写 → 构建 → 推送 |
| 在线写作 | ✅ 自带后台 | ✅ | 需另接 CMS | ❌ |
| 数据库 | Supabase(Postgres,完整 SQL) | MySQL | 随缘(常接外部 CMS) | 无(文件即内容) |
| 免费额度 | CF 10 万请求/天 + Supabase 免费档 | 无(需主机费) | 有(商用受限) | 静态托管几乎无限 |
| 国内访问 | CF 边缘偏慢,可用 | 看主机 | 同样不稳 | 可放国内 CDN,最快 |
| 生态/资料 | Astro 生态较小,中文资料少 | 最大,插件主题随便挑 | Next.js 生态最大 | 静态生成生态成熟 |
| 数据在手 | 在 Supabase,可随时导出 | 在自己库 | 看接什么 | 就是本地文件 |
这套组合的真实优势
- 零成本且零运维:没有服务器要补丁、没有数据库要备份脚本、证书自动续期。个人站最大的隐性成本其实是「怕它挂了」,这套方案把这项也归了零。
- 发布即上线:SSR 每请求现查,写完点发布的那一刻读者就能看到。纯静态方案「本地构建 → 推送 → 等部署」的链条在这里不存在。
- 自带后台 + 数据库能力完整:文章、评论、分类注册表、站点配置全在 Postgres 里,RLS 把「谁能读什么」管得明明白白,还能直接写 SQL 做统计——这是文件型方案给不了的。
- 三语同源、SEO 内置:RSS、sitemap、canonical、hreflang 都是架构自带的,不是插件拼的。
也必须说清楚的劣势
- Astro 生态比 Next.js 小:遇到冷门问题时,能搜到的中文方案有限,更多时候得读英文文档或直接问 AI。
- 国内访问 Cloudflare 边缘「能用但偏慢」:这是 CF 大陆网络的客观现状,读者侧体感比 Vercel/Netlify 或国内 CDN 差一截。我对这个站的定位是「记录自己」,没有追求国内极速;如果你做的是面向国内读者的内容站,这条要认真权衡。
- 后台发文看网络脸色:后台页面在浏览器里直连 Supabase,国内网络不稳时登录/保存可能转圈,换个顺畅的网络即可,读者侧无感知。
- Supabase 免费项目 7 天无活动会暂停:需要进后台点一下唤醒。对周更的博客无影响,但「年更」选手要记得。
- 默认无页面级缓存:发布零延迟的代价是每个请求都实时渲染(好在 SSR 通常远低于 10ms 上限)。真需要缓存时自己加
s-maxage即可,个人站当前没必要。 - 长尾风险:免费档的额度政策(Cloudflare / Supabase / 智谱)未来若调整,可能需要迁移或付费——这是所有「白嫖流」方案的共同风险,要有心理预期。
一句话总结:如果你要一个「自己的、能长期写的、不折腾的」个人网站,这套很合适;如果你追求国内访问速度或需要丰富插件生态,WordPress 或静态方案可能更省心。
我的建站简史(为什么是第三次)
这不是我第一次拥有个人网站。
2018 年,我在 GitHub 上第一次接触到开源世界,跟着一个博客项目的 README 在本地一步步复现,成功跑起来的那一刻,新世界的大门被打开了。后来我在阿里云买了自己的域名和服务器,薅着免费的云存储和 CDN,把网站部署了上去。因为监管要求,完成工信部 ICP 备案和公安网安备案之后,网站才终于正式上云。那几年我写着代码之外的生活,直到因为成本原因,在五年后关闭了网站,注销了备案。
2026 年 10 月,AI 编程能力已经成熟到普通人可以凭对话构建系统。我决定再搭一次——但这次彻底换思路:传统的 WordPress、Hexo 那一套并不契合我的需求(一个要服务器运维,一个每次发文都要本地构建)。我最终选了一套「全托管、零服务器」的组合:代码放 GitHub,Cloudflare 托管,Supabase 存数据,前端静态页面 + 后端管理页直接发文。对于一个访问量不大的个人网站,这些服务的免费额度绰绰有余。
于是有了你正在看的这个站。下面进入正题。
第一步:准备工作
账号清单
| 服务 | 用途 | 收费情况 |
|---|---|---|
| GitHub | 代码仓库,部署触发器 | 免费 |
| Cloudflare | 托管 Worker、DNS、HTTPS | 免费 |
| Supabase | 数据库 + 登录鉴权 | 免费 |
| 智谱开放平台 | 翻译 API(可选) | GLM-4.7-Flash 模型级免费 |
| 一个域名 | 门牌号 | 已购则无新增费用 |
本地环境
- Node.js 22(仓库有
.nvmrc锁定,nvm use即可) - git、npm
- 一个能顺畅访问 GitHub 与 Supabase 的网络环境(后面会说为什么)
第二步:搭数据库(Supabase)
- 注册 Supabase,New project 新建项目。区域随便选(个人站距离不敏感),数据库密码自己记住。
- 项目就绪后进入 SQL Editor,把仓库
supabase/目录下的五个文件按顺序各执行一遍(都可重复执行,报错「已存在」是正常的):schema.sql:核心。建posts(文章)、comments(评论)、site_settings(站点配置)、category_aliases(分类旧链接 301 映射)四张表,配置好 RLS 行级安全策略、评论相关的三个 RPC 函数,以及一个公开读的images图床桶。categories.sql:建分类注册表。分类以注册表为权威来源,支持新增/重命名/合并/删除,重命名后旧链接自动 301,不会 404。posts_author.sql:给文章表补一个author作者列。别跳过这步,否则后台发布文章会报错。posts_trash.sql:文章垃圾箱(软删除)。删除文章改为「移入垃圾箱」保留 5 天再彻底清除,另含前台 RLS 过滤与定时清理函数。别跳过,否则删除功能不可用。media_library.sql:文件管理(媒体库)。建media表记录每个文件与文章的归属关系、新增attachments附件桶(支持 pdf/word/ppt/excel 等)、以及「孤儿文件」检测视图。别跳过,否则后台「文件管理」模块不可用。三个 SQL 和五个 SQL 的差别,是本文最初发布后才补上的功能——你现在照着执行,得到的是完整版。
- 进入 Project Settings → API Keys,复制两样东西,等会要用(Supabase 新版控制台把这一项放在这里):
Project URL:形如https://xxxx.supabase.coanon publickey:一长串eyJ...开头的密钥
- 创建管理员账号:Authentication → Users → Add user,填你的邮箱和密码,勾选自动确认。这就是后台
/admin的登录凭证——本站后台没有独立注册体系,直接用 Supabase 账号体系,一个人站不需要更复杂的了。
关于
anonkey 的安全性:它本就设计为公开,随前端下发。真正的安全边界是 RLS——读者只能读到「已发布文章」和「已审核评论」,写操作也被策略层强制校验(昵称/邮箱/内容长度与格式)。敏感操作(翻译 API key 等)另放 Cloudflare Secret,不下发浏览器。
第三步:把网站在本地跑起来
git clone https://github.com/<你的用户名>/<仓库名>.git
cd <仓库名>
nvm use # 或确保 Node 22
npm install
复制 .env.example 为 .env,填入第二步拿到的两样:
PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
PUBLIC_SUPABASE_ANON_KEY=eyJ...你的anon key
# 站点对外地址(影响 sitemap / RSS / canonical);本地开发可先留空
PUBLIC_SITE_URL=
然后:
npm run dev # http://localhost:4321
没配 .env 也能跑——站点会降级用内置的三语示例文章,方便你先看效果。想验证「连真实数据库」的完整效果,再填上 .env。
本地预览生产构建产物用 npm run build && npx wrangler dev(本地模拟 Workers 运行时,和线上一致;wrangler 会读仓库里的 wrangler.toml)。注意不要用 wrangler pages dev——本项目走的是 Workers 而非 Pages,命令与产物都对不上(见第五步的坑)。
第四步:推上 GitHub
git add .
git commit -m "init"
git remote add origin https://github.com/<你的用户名>/<仓库名>.git
git push -u origin main
后面所有「改代码」的流程都一样:本地改 → commit → push。推送这个动作本身就是部署触发器。
第五步:部署到 Cloudflare Workers(核心步骤)
⚠️ 这一步有一个大坑前置提醒:创建项目时要选 Workers,不是 Pages。本站是 SSR 应用(依赖构建生成的 Worker 运行时),必须走 Workers。选错了后面会报
Workers-specific command in a Pages project,得删了重建。
- Cloudflare Dashboard → Workers & Pages → Create → Connect to Git,授权 GitHub,选仓库,Production branch 选
main。 - 构建设置(Framework preset 选 Astro 通常会自动填好):
- Build command:
npm run build - Build output directory:
dist - Deploy command:
npx wrangler deploy← 由仓库根目录的wrangler.toml驱动,不要改
- Build command:
- Settings → Builds → Build environment variables(注意是构建变量区,不是 Runtime 变量区)添加四个:
变量 值 说明 PUBLIC_SUPABASE_URLhttps://xxxx.supabase.co必填 PUBLIC_SUPABASE_ANON_KEYeyJ...必填 PUBLIC_SITE_URL先填 https://<项目名>.workers.dev建议,绑域名后再改 NODE_VERSION22与 .nvmrc双保险这三个
PUBLIC_*是构建时内联变量——值在npm run build那一刻写进产物。所以建站阶段改了它们,必须重新部署才生效。 - Save and Deploy,等 1–3 分钟,得到
https://<项目名>.workers.dev。 - 以后:只有改代码才需要 push。发文章不触发任何构建。
wrangler.toml 仓库里已带好,内容就这么多,无需手动配:
name = "mystyle"
main = "dist/_worker.js/index.js"
compatibility_date = "2025-07-18"
compatibility_flags = ["nodejs_compat"]
[assets]
directory = "./dist"
其中 nodejs_compat 是 SSR 的生命线——缺了它,Worker 里任何 Node 内建调用都会直接 500。
第六步:绑定自定义域名
域名是我早年在阿里云买的(.cn)。接入流程:
- 把域名托管到 Cloudflare(2025 年控制台改版后的新路径):Dashboard 左侧 Domains → Onboard a domain → 输入 apex 域名 → Continue。套餐选 Free。
- Cloudflare 会给两个 NS 服务器(
xxx.ns.cloudflare.com)。到域名注册商处,把域名的 DNS 服务器(NS)修改为这两个地址——各注册商后台都有这个入口,一般在「域名管理 → DNS 修改 / DNS 服务器」一类的菜单里。 - 等 CF 面板里域名状态变 Active(通常几分钟到几小时,NS 生效时间)。
- Workers 项目 → Settings → Triggers → Custom Domains → Add,填你的域名。DNS 在 CF 则记录自动配好,HTTPS 证书自动签发,全程免费。
- 最后一步别忘:把构建环境变量里的
PUBLIC_SITE_URL改成https://你的域名,然后 Retry deployment(Deployments 页最新部署 →···→ Retry)。这样 sitemap、RSS、canonical 里的域名才全部正确。文章内容不受影响。
第七步:登录后台,发第一篇文章
浏览器打开 <域名>/admin,用第二步建的 Supabase 账号登录。后台四个模块:
- 文章管理:写标题、选语言(中/英/日)、分类、正文(Markdown 实时预览),点「发布并上线」。发布即生效——去前台刷新,文章已经在等你。编辑器「更多设置」里还能改发布时间(前台展示、排序、RSS 同步更新)、Slug、封面图与摘要(留空则发布时按正文自动生成)。删除文章是「移入垃圾箱」的软删除,保留 5 天后自动彻底清除,期间可以随时恢复。
- 文件管理:统一管理正文插图与附件(pdf/word/ppt/excel 等)。删除文章时会连带清理它用过的文件;没被任何文章引用的「孤儿文件」能一键清理,不用怕存储堆积。
- 评论管理:审核、回复(管理员回复有专属标记,嵌套展示)、删除。
- 其他功能:首页倒计时、摘要自动生成设置(长度上限与生成风格)、分类管理(重命名自动 301)。
顺带说一句评论的设计:评论表收集邮箱但不公开,仅博主后台可见;匿名评论者会拿到一个删除令牌,能管理自己发过的评论——不需要人人注册。
第八步:可选增强
一键翻译:后台编辑器里写中文,点「翻译」直接生成英/日版。它走 OpenAI 兼容协议,当前接的是智谱 GLM-4.7-Flash(模型级永久免费,个人用量无上限焦虑)。换任何兼容服务(DeepSeek、Moonshot、Gemini)只改两个变量,代码零改动:
| 变量 | 位置 |
|---|---|
TRANSLATE_BASE_URL / TRANSLATE_MODEL |
wrangler.toml 的 [vars] |
TRANSLATE_API_KEY |
Cloudflare 后台 → Settings → Variables and Secrets → Add(Type: Secret) |
翻译长文有个专门的门道:分段必须结构感知。早期版本按行数硬切,结果一张 12 列的表格被从中间腰斩,拼回来时一张表碎成四张,译文根本没法看。现在改成先识别结构再打包——代码块整块进出,表格整表进出(超大表按行切,且每片都重复表头,保证每一片自己就是一张合法表格),超长段落只在句界处切。段与段之间用 2 路并发翻译(段间留 1.5 秒间隔防限流),并把上一段原文的结尾作为「上文参考」一起送进去,压住跨段指代断裂和术语漂移。失败仍是指数退避重试。
这套改动解决了一个真实的失败:近 12000 字的长文串行翻译需要约 233 秒,而前端等待上限是 200 秒,必然超时失败;并发后压到约 140 秒,落进窗口内。
AI 摘要:文章的摘要驱动列表卡片、文章首段导语、搜索结果、RSS 与社交分享卡片五处。写作时摘要框留空,发布那一刻会按正文自动截取(跳过标题、代码块和表格,只取叙述正文,停在句界并补省略号);也可以在编辑器里点「✨ AI 生成」让模型写一段。长度上限与风格(陈述式 / 要点式 / 悬念式)在后台「其他功能」里可配,默认 200 字。AI 不可用时会自动回退到本地截取,并在界面上明确告知这是降级结果——不会静默塞一段看起来像 AI 输出的文字。
三语与 SEO:每语言独立 RSS、整站 sitemap、canonical、hreflang 都已内置。文章缺译文时前台显示「暂无译文」横幅并保留原文,不会开天窗。
社交分享卡片:分享到微信/微博/Facebook 时的缩略图与描述走 Open Graph 协议。og:image 优先用文章封面,没填封面就用站点默认图兜底;描述则取文章摘要。所以摘要留空不光影响站内,也会让分享卡片没有描述——这也是上面那套自动生成的意义之一。
踩坑实录(本文最值钱的部分)
以下每条都是我实际踩过的,按「现象 → 原因 → 解法」记录。
1. 创建项目时选了 Pages,部署直接报错
现象:Workers-specific command in a Pages project。
原因:本站是 SSR(产物含 Worker 运行时),且部署命令是 npx wrangler deploy,这是 Workers 的部署方式;Pages 项目的构建模型不兼容。
解法:创建时选 Workers → Connect to Git,别选 Pages。
2. 全站 500,日志提到 Node 内建模块
原因:缺 nodejs_compat 兼容标志,SSR 用到的 Node 能力在 Worker 运行时不可用。
解法:wrangler.toml 里带上 compatibility_flags = ["nodejs_compat"](仓库已带)。
3. sitemap / RSS 里是错域名
现象:产物里出现 your-domain.com 占位符,或线上 canonical 指向 example.com。
原因:PUBLIC_SITE_URL 没设;更隐蔽的一层——Astro 不会把 .env 注入 astro.config.mjs 加载期的 process.env,只有 import.meta.env 能拿到,所以配置文件里直接读 process.env.PUBLIC_SITE_URL 会静默拿到空值,回退默认站点地址。
解法:astro.config.mjs 改用 Vite 的 loadEnv 显式读文件环境变量,同时兼容 CI 里 process.env 的构建变量,两条路都兜住;改完变量 Retry deployment。
4. 后台发布文章报错(缺 author 列)
原因:文章表早期没有 author 列,后台发布逻辑写库时必填。
解法:执行 supabase/posts_author.sql 补列。这就是三个 SQL 文件一个都不能少的原因。
5. 翻译服务切换后「神秘回滚」
现象:在 Cloudflare 后台把翻译服务从 Gemini 换成智谱 GLM,过段时间又变回 Gemini。
原因:部署命令是 npx wrangler deploy,而 wrangler.toml 的 [vars] 会随每次部署同步上线——后台改的普通变量可能被仓库里的值覆盖(Secret 类型的 API key 不受影响,所以现象是「模型换了、key 没换」的诡异组合)。
解法:仓库 wrangler.toml 与后台变量保持一致。我最终把 toml 也改成了智谱值,两边对齐,从此没有任何一方能悄悄覆盖另一方。
6. 国内访问后台要「看网络脸色」
原因:后台页面在浏览器里直连 Supabase(*.supabase.co),国内直连不稳定;但读者侧走的是 Cloudflare 边缘,完全不受影响。
解法:发文时用顺畅的网络登录后台即可,读者无感知。这也是架构设计的结果——读写路径分离,读者侧不依赖 Supabase 可达性。
7. Workers 免费额度用不用操心?
实测口径:构建 1–3 分钟/次,3000 分钟/月对个人站等于用不完;运行时 10 万请求/天、单次 CPU 10ms,SSR 渲染通常远低于 10ms。唯一要注意的是「改代码才构建」——发文不消耗构建分钟。
8. AI 摘要有时候报「服务返回为空」,但明明没报错
现象:点「✨ AI 生成」,等几秒后提示摘要生成失败,错误写着「翻译服务返回为空」,可翻译功能一切正常。
原因:这条坑最反直觉——接口 HTTP 200,没报任何错,但内容是空的。我当时接的智谱 GLM-4.7-Flash 是推理型模型,会先把思考过程写在 reasoning_content 字段里,而 max_tokens 是「思考 + 回答」共享的总预算,不是只给答案的。摘要请求只给了 1024,而这个模型的思考实测能到 1300~1600 字符,预算在思考阶段就被吃光,轮到写答案时余额归零。判据是响应里的 finish_reason 为 length(被长度截断),而不是正常的 stop。
解法:给推理模型留足预算。一是把摘要请求的 max_tokens 从 1024 提到 4096;二是在调用层加了自愈——识别到「内容为空 + finish_reason 是 length」就自动把预算翻倍重试(实测第一次翻倍就能拿到答案)。同时把错误提示改成能指向问题的说法(区分「预算耗尽」「只回了思考没回答案」「真的空内容」三种),不再笼统地说「返回为空」。最后再加一道兜底:AI 不可用时自动回退到本地截取摘要,保证写作不被卡住。
9. 长文翻译总是失败,短文章却没事
现象:一千字以内的文章翻译正常,上万字的长文必失败,而且等很久之后才报错。
原因:早期是串行翻译每一段,每段超时上限 45 秒,段间还要等 2 秒。一篇近 12000 字的文章切成 5 段,最坏情况 5×45 + 4×2 ≈ 233 秒,而前端等待上限是 200 秒——在架构上就注定失败,跟网络好不好无关。另外分段器还会把表格从中间腰斩,译文拼回来是坏的。
解法:分段改为结构感知(代码块与表格整块进出,超大表切分后每片重复表头),翻译改为 2 路并发,耗时压到约 140 秒;并给每段附上「上一段原文结尾」作上下文,减少跨段指代错乱。
费用清单
| 项目 | 费用 |
|---|---|
| 域名 | 唯一历史支出(早年购买,按年续费) |
| Cloudflare Workers / DNS / HTTPS | 0 |
| Supabase(数据库+鉴权+图床) | 0(免费档) |
| GitHub | 0(公开仓库) |
| 智谱 GLM(翻译 + AI 摘要) | 0(GLM-4.7-Flash 模型级免费) |
| 合计月成本 | 0 元 |
日常维护
- 发文/改文章:
/admin后台操作,即时生效,零部署。 - 改代码/样式/功能:本地改 → push → CF 自动构建(1–3 分钟)→ 完成。
- 改配置(环境变量、翻译服务商):Cloudflare 后台改 → Retry deployment。
- 数据备份:Supabase 后台随时可导出;文章本身是 Markdown 文本,库在手,天下我有。
- 证书与续期:Cloudflare 自动,不用管。
结语
从 2018 年那台需要手动运维的阿里云服务器,到 2026 年这套「推送即部署、发文即上线」的全托管组合,变化的不只是技术栈,更是拥有一个个人网站所需的门槛——它从「得是个运维」,降到了「有个 GitHub 账号就行」。而这次重建全程是借 AI 之力完成的:我对 Supabase 并不精通,但我知道该问什么问题。
如果你照着这篇文章也搭起了一个,欢迎来评论区还愿。
一如既往,一以贯之。
评论0
还没有评论,来抢沙发吧。