从零到上线:个人网站的完整部署记录

这是一篇个人网站的完整部署记录:作者用 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)

  1. 注册 Supabase,New project 新建项目。区域随便选(个人站距离不敏感),数据库密码自己记住。
  2. 项目就绪后进入 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 的差别,是本文最初发布后才补上的功能——你现在照着执行,得到的是完整版。

  3. 进入 Project Settings → API Keys,复制两样东西,等会要用(Supabase 新版控制台把这一项放在这里):
    • Project URL:形如 https://xxxx.supabase.co
    • anon public key:一长串 eyJ... 开头的密钥
  4. 创建管理员账号:Authentication → Users → Add user,填你的邮箱和密码,勾选自动确认。这就是后台 /admin 的登录凭证——本站后台没有独立注册体系,直接用 Supabase 账号体系,一个人站不需要更复杂的了。

关于 anon key 的安全性:它本就设计为公开,随前端下发。真正的安全边界是 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,得删了重建。

  1. Cloudflare Dashboard → Workers & Pages → Create → Connect to Git,授权 GitHub,选仓库,Production branch 选 main。
  2. 构建设置(Framework preset 选 Astro 通常会自动填好):
    • Build command:npm run build
    • Build output directory:dist
    • Deploy command:npx wrangler deploy ← 由仓库根目录的 wrangler.toml 驱动,不要改
  3. Settings → Builds → Build environment variables(注意是构建变量区,不是 Runtime 变量区)添加四个:
    变量 值 说明
    PUBLIC_SUPABASE_URL https://xxxx.supabase.co 必填
    PUBLIC_SUPABASE_ANON_KEY eyJ... 必填
    PUBLIC_SITE_URL 先填 https://<项目名>.workers.dev 建议,绑域名后再改
    NODE_VERSION 22 与 .nvmrc 双保险

    这三个 PUBLIC_* 是构建时内联变量——值在 npm run build 那一刻写进产物。所以建站阶段改了它们,必须重新部署才生效。

  4. Save and Deploy,等 1–3 分钟,得到 https://<项目名>.workers.dev。
  5. 以后:只有改代码才需要 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)。接入流程:

  1. 把域名托管到 Cloudflare(2025 年控制台改版后的新路径):Dashboard 左侧 Domains → Onboard a domain → 输入 apex 域名 → Continue。套餐选 Free。
  2. Cloudflare 会给两个 NS 服务器(xxx.ns.cloudflare.com)。到域名注册商处,把域名的 DNS 服务器(NS)修改为这两个地址——各注册商后台都有这个入口,一般在「域名管理 → DNS 修改 / DNS 服务器」一类的菜单里。
  3. 等 CF 面板里域名状态变 Active(通常几分钟到几小时,NS 生效时间)。
  4. Workers 项目 → Settings → Triggers → Custom Domains → Add,填你的域名。DNS 在 CF 则记录自动配好,HTTPS 证书自动签发,全程免费。
  5. 最后一步别忘:把构建环境变量里的 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

    ← 返回首页