全文约 6000 字,包含 12 轮真实对话。文中「我」指人类作者,「AI」指 WorkBuddy 智能体。
起因
十月中旬的一个周末,我想给折腾了很久的个人笔记找个正经的落脚点。
需求很朴素:写 Markdown,发到自己的域名上,能被搜索引擎收录 。域名是早就买好的 ,makingitbig.cn,服务器是阿里云的轻量应用服务器,备案还没提交。
我打开 WorkBuddy,说了一句话:
帮我搭一个个人静态博客,要有深色模式、标签分类、归档页,风格是治愈手账风。服务器我自己有,最后能部署上去。
然后我就去泡茶了。
三天后,站点上线了,ICP 备案通过,HTTPS 证书签发,公安联网备案完成。
这期间我一行代码都没写。
但我想写这篇文章,因为这三天里发生的事,比"AI 写代码"这件事本身更值得记录。
一、它给我的第一个惊喜:技术选型
我原本以为会看到一堆代码。但 AI 的第一轮回复不是写代码,而是先反问:
需求里有些东西可能不是真需求。要 CMS、用户体系、评论、富文本编辑器吗? 我建议先回答一个问题:访客访问的时候,你的服务器上需不需要有东西在跑?
这个问题的价值在于,它把"要不要静态博客"这个模糊的决定,压缩成了一个可以量化的判据。
答案是「不需要」------于是所有方案收敛到同一条路:文章在本地写成 Markdown,构建成静态 HTML,扔给 Nginx。
AI 接着列了排除过程,每一条都给了理由:
| 方案 | 排除理由 |
|---|---|
| Next.js | 每篇文章都要带 React runtime(压缩后约 85KB)。而我的博客只有一个移动端菜单按钮需要交互------为一个开菜单的需求付 85KB,不合理 |
| Hugo | 构建速度快一个数量级(0.4s vs 3.8s),但 Go 模板反直觉、MDX 非原生是真实的学习成本。我的文章量(几百篇)体现不出优势 |
| Eleventy | 图片优化、sitemap、RSS 都要自己拼插件,而这些恰好是 Astro 的内置能力 |
| Astro 5 ✅ | Content Collections 用 Zod 校验 frontmatter、零运行时 JS、Shiki 构建期代码高亮、内置 MDX------全部命中,没有一项是"用不上的能力" |
我回了一句「就用 Astro」,事情就这么定了。
这个环节的价值不是它选对了框架,是它把"我以为的需求"和"我真正需要的东西"分开了。 如果我不介入,这个项目大概率会多出一个用不上的 CMS 和一堆用不上的插件。
意外的设计处理
我给它一份 HTML 设计稿(治愈手账风),它直接从里面提取了设计令牌:
css
:root {
--paper: #f4ecde; /* 纸张色 */
--ink: #3b3128; /* 墨色 */
--brown: #a9714b; /* 棕色系点缀 */
--r-xl: 34px; /* 大圆角卡片 */
}
纸张纹理、衬线标题栈、手账装订线------直接移植,不重新设计。
但它发现了一个我没想到的事:参考稿里根本没有深色模式 (theme、dark 在全文出现 0 次)。
于是它另设计了一套「夜间书斋」色板,--paper 改成 #211c17,棕色系整体提亮以保证暗底对比度。
二、完整流程总览
在展开对话细节之前,先把整个流程摊开。这是三天里事情推进的顺序:
bash
① 需求描述 一句话 + 一份设计稿
↓
② 技术选型 对话推演,排除 3 个方案,定 Astro 5
↓
③ 代码实现 17 个文件 / 2477 行,含深色模式与代码高亮
↓
④ 本地验证 构建通过 + 浏览器截图逐页检查
↓
⑤ 服务器准备 Docker 安装 + 内存检测加 swap + Gitee 远端
↓
⑥ 部署上线 clone → build → 容器运行 → curl 验证
↓
⑦ 备案与证书 ICP 备案 → Nginx 反代 → HTTPS 双域名
↓
⑧ 站长验证 文件验证 → sitemap 提交
↓
⑨ 细节打磨 Logo 重设计 + 代码高亮修复
↓
⑩ 部署架构重构 本地构建 + rsync 增量同步(事后发现的浪费)
↓
⑪ 内容运营 写第一篇文章(就是这篇)
第 ④ 步和第 ⑩ 步值得多看一眼------
- 第 ④ 步是"看起来已经完成了,但实际还有坑"的环节
- 第 ⑩ 步 是一次事后反思:部署已经跑通了,但当我看到 Docker 还在拉镜像层时,才意识到"静态博客在服务器上构建"本身就是个错误假设
下面十四轮对话里,这两步花的时间最多。
三、十四轮对话
下面是这次建站过程中的关键对话。我按时间顺序整理,标注了每一轮我的输入和 AI 的处理方式。
第 1 轮:项目骨架
我: 「按 Astro + 手账风搭起来,要有标签页、归档页、分页、RSS、sitemap。」
AI: 生成了完整骨架,17 个源文件、2477 行代码。几个关键决策:
- 内容源用 Zod schema 校验 :
publishDate: z.coerce.date(),写错格式时构建那一刻就报错,而不是上线后才发现某篇文章日期显示成 undefined - 分页路由 :第 1 页
/,第 N 页/page/N,getStaticPaths只返回第 2 页起的路径 - 深色模式脚本内联进
<head>:如果放在<body>里,用户会先看到浅色再变深色------那一下闪烁比没有深色模式更糟
我看完骨架,说了句「可以」,它就开始铺页面了。
第 2 轮:深色模式闪烁
我: 「切到深色模式的时候页面会闪一下。」
AI: 确认了原因------主题判定脚本如果不在 <head> 里同步执行,就会晚于首屏渲染。修正为内联同步执行,并强调这段代码不能 defer、不能 async。
代码高亮用了 Shiki 双主题,一次高亮输出两套颜色,CSS 在暗色下接管,切换时不需要重新高亮。
第 3 轮:Docker 部署
我: 「服务器是阿里云的,帮我写部署脚本。」
AI: 给了多阶段构建的 Dockerfile:
dockerfile
FROM node:22-alpine AS builder
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
关键收益:单阶段构建镜像是 1.2GB,多阶段只剩 12MB。而且最终镜像里没有 Node 运行时------对一个只提供静态内容的站点,攻击面直接归零。
同时它加了一条我完全没想到的检测:
bash
if [[ "$(free -m | awk '/Mem:/ {print $2}')" -lt 2048 ]]; then
fallocate -l 2G /swapfile && swapon /swapfile
fi
这个判断在部署时真的生效了------脚本检测到内存不足 2GB,自动加了 2GB swap。当时我只觉得"挺贴心",后来才明白这有多必要:Astro 构建要装 300+ 依赖,1G 内存的机器很容易被 OOM killer 干掉。
第 4 轮:部署架构重构(事后发现的问题)
我: 「每次部署为什么还要下载那么多数据?」
这是整段建站过程里最有价值的一次反思 。我贴了截图:Docker 正在拉 node:22-alpine 的镜像层。
AI: 「静态博客的产物构建完全没必要放在服务器上。」
它把架构改成了:
arduino
本地 服务器
npm run build → dist/ (568K) → rsync 增量 → /var/www/blog/dist/
↓ 卷挂载
astroblog 容器(纯 nginx,无 Node)
配套三处改动:
- 新增
Dockerfile.runtime------只有 nginx,没有 builder 阶段 docker-compose.yml改成卷挂载./dist:/usr/share/nginx/html:ro- 部署脚本改为「本地 build → rsync → 远端 reload」
镜像只在首次构建,之后永不重建。 更新内容只需 nginx -s reload。
实测增量传输的效果:
arduino
改 1 个文件 → Total transferred file size: 8 B
对比原来每次拉 56MB 镜像层 + 300+ npm 依赖------从几十 MB 降到几 KB。
这个问题的价值不在于技术难度,在于它揭示了一个思维惯性:默认「构建就该在服务器上」。静态站点这是个错误假设,而且我一开始根本没意识到。
第 5 轮:三个只有真跑才会暴露的坑
我: 「执行报错。」
这个脚本写完后连续暴露了三个问题,每一个都是"写完没实测"的代价。
坑一:macOS 的 rsync 不支持 --info=progress2
vbnet
rsync: unrecognized option '--info=progress2'
macOS 自带的不是真 rsync,而是 openrsync (rsync version 2.6.9 compatible)。--info=progress2 是 rsync 3.1.0+ 才有的特性。
改用所有版本都支持的 --itemize-changes + --stats,输出反而更清楚:
yaml
>f.s..... f10.js
Number of files transferred: 1
Total transferred file size: 8 B
教训 :写脚本时用到的工具,特性要按最保守的版本考虑。
坑二:中文全角括号紧跟变量
arduino
deploy/deploy.sh: line 242: HTTP_CODE: unbound variable
这个报错特别有意思------第 240 行刚赋值,第 241 行刚判断过,第 242 行却说变量未定义。
真实原因:
bash
log "线上正常(HTTP $HTTP_CODE)"
↑ ↑
变量 全角右括号 U+FF09
bash 把变量名解析成了 HTTP_CODE)------UTF-8 全角字符在 bash 里是合法标识符字符。于是赋值给了另一个名字,读取时自然找不到。
修复是加花括号界定边界:${HTTP_CODE}。
教训 :中文文案里出现 $变量 必须用 ${变量} 。中文标点(,。;:())全是全角,在 bash 里都能当标识符字符用。这是中文开发者的经典坑。
而且 bash -n 查不出这个错------它是合法语法,只是语义不对,必须实际执行才能发现。
坑三:服务器地址和密码每次都要重输
顺手改掉了:
bash
SSH_TARGET="${SSH_TARGET:-root@121.43.250.184}" # 地址内置
密码问题用 SSH ControlMaster 复用连接解决,另外提供一条命令配免密:
bash
bash deploy/deploy.sh --setup-key
这里有个我特意确认过的点 --------setup-key 会不会影响我机器上其他的 Git 仓库密钥?
答案是不会。三条保障:
~/.ssh/config是追加 (>>)而非覆盖,原有配置一字未动ssh-keygen生成新文件id_ed25519,不碰已有的id_rsa/kingdee_rsa- 配置按 Host 精确匹配,两条链路各自独立
sshconfig
Host gitee.helichina.com
IdentityFile .../kingdee_rsa # 金蝶走老密钥
Host 121.43.250.184
IdentityFile .../id_ed25519 # 博客走新密钥
第 6 轮:服务器上的第一次失败
我: 「clone 失败了,报错信息在截图里。」
AI: 看到报错后,它做的第一件事是检查令牌是否泄露------截图里的令牌已经脱敏,它确认了这一点,然后才去看逻辑。
问题出在认证 URL 的拼接上:Gitee 私有仓库需要「用户名:令牌」,而脚本只拼了令牌。
我: 「password 我输入里密码。。。」
AI: 「填登录密码大概率会失败,Gitee 现在默认禁用账号密码推送,只保留令牌通道。」
它没有直接改代码,而是先解释了原理------这个顺序我觉得是对的。知道原因才能判断下一步怎么做。
我: 「但我用密码 clone 成功了啊,61 个对象都拉下来了。」
AI: 「那说明 Gitee 这条路径还开着。但推荐用令牌------账号密码随时可能被风控改掉,令牌可以单独撤销。而且既然密码能用,最后一步改成 www 域名时它提示「301 网页存在跳转」,说明它在做的是另一次验证,那次用的是 www。
第 7 轮:www 证书补签失败
我: 「certbot certificates 显示只有主域名。」
AI: 这次它没给脚本,而是先要了一个探测结果:
bash
curl -o /dev/null -w "%{redirect_url}" \
http://www.makingitbig.cn/.well-known/acme-challenge/probe.txt
# → 301 → https://makingitbig.cn/...
根因是 Nginx 的执行顺序:
vbscript
server 级 if → rewrite 阶段 → 早于 location 选择
我配置里的 www 跳转写成了 server 级的 if,所以任何 www 请求都被先跳走------包括 ACME 验证请求。Let's Encrypt 不跟随跨域名重定向,验证必然失败。
修法 :www 跳转移到 location / 内部,ACME 路径用 location ^~(第 2 优先级,不检查正则)。
它还说了一句:「打补丁不如整体重写------配置结构本来就该统一。」我同意,于是让它把 80/443 两个块整个重写了一遍。
这里有个值得记的教训 :我以为"脚本跑了就是跑了",但证书列表里没有 www,说明脚本根本没执行成功,或者执行到一半失败了。AI 把「验证结果」摆在脚本之前,这个顺序是对的。
第 8 轮:容器 unhealthy
我: 「docker ps 显示 unhealthy,但 curl 是 200。」
AI: 这次的排查链条很完整:
css
docker logs → Configuration complete, ready to start up ✅
start worker process 28/29 → 正常运行 ✅
ls html 目录 → 27 个页面齐全 ✅
curl -I localhost → HTTP/1.1 200 OK ✅
四个证据说明服务完全正常,问题在健康检查本身。
根因 :健康检查用 wget --spider 发 HEAD 请求,而 nginx.conf 里 location / 配了 expires -1 + add_header Cache-Control: no-cache,响应头里 Cache-Control 出现了两次 。BusyBox wget 遇到 no-cache 的 HEAD 响应会误判失败。
它改了两处:健康检查改用 GET,nginx.conf 删掉 expires -1 消除重复头。
它总结的那句话我记住了:
unhealthy不等于服务不可用。判断顺序应该是「先看日志确认进程 → 再从宿主机 curl 验证实际响应 → 最后才看健康检查」。
第 9 轮:备案与端口
我: 「备案通过了,接下来做什么?」
AI: 先探测端口,得出一个关键判断:
bash
80 → Connection refused(不是 timeout)
这两个词的区别很重要:
- timeout = 阿里云拦截,SYN 包被丢弃,请求根本没到服务器
- Connection refused = 包到了服务器,但那个端口没有服务在监听
说明备案拦截已经解除 ,真实问题暴露出来:docker-compose.yml 绑的是 127.0.0.1:8080:80,对外根本没开 80。
之前备案拦截把这个配置错误一起盖住了。这是这类"合规拦截"最隐蔽的副作用------解除之后必须重新验证整条链路。
第 10 轮:HTTPS 重定向循环
我: 「浏览器提示 Load cannot follow more than 20 redirections。」
AI: 这次它第一次给出的是根因分析而不是补丁:
certbot 签发证书时会把 80 端口的 server 块复制一份到 443。而原配置里 80 块的所有 location 都是
return 301 https://...。于是 HTTPS 块里的 location 也变成 301 指向自己------无限循环。
这是很多人踩 HTTPS 循环的真正原因,而我当时完全不知道 certbot 有这个行为。
第 11 轮:站长平台验证
我: 「验证文件放到 /var/www/html/ 了,但验证不成功。」
AI: 这次它先做了一个对照测试:
bash
curl -o /dev/null -w "%{http_code}" https://makingitbig.cn/.well-known/acme-challenge/probe.txt
# 404
curl -o /dev/null -w "%{http_code}" https://makingitbig.cn/nonexistent-xyz
# 404 ← 两者完全一致
两个必然 404 的路径返回完全一样,说明请求压根没走我以为的那条 location。
根因:443 块里只有 location / { proxy_pass ... },所有请求都进容器,而文件在宿主机上。
这个排查思路我学到了:判断"文件放了但访问不到",先拿一个必然 404 的路径做对照。
第 12 轮:Logo 设计
我: 「重新设计下这个 logo。」(附一张浏览器标签页的截图)
AI: 看完截图,它指出那个 favicon 是「通用的青色三横线,跟站点的纸墨风格完全不搭」------确实是从别的项目模板带过来的。
然后它给了四版方案对比图。我说「需要体现纸和山河的画面感」,它又给了三版。
我: 「方案一。」
选定之后它做了一件我很认可的事:在动手前先说明约束------
34px 的页头 logo 能容纳细节,16px 的浏览器标签图标不行。所以 favicon 会是「只留山,不留纸卷」的版本。
结果第一版做出来,我一看轴头在 34px 下几乎看不见、纸面和背景色太接近糊成一团。它自己发现了,重新调了几何尺寸。
我: 「favicon.svg 这个没有改啊,左右两边的竖,可以去掉,简洁些。」
改完之后效果确实更清爽------去掉具象的卷轴,改成纯水墨山形,纸的意象靠浅色底承载。
第 13 轮:代码高亮
我: 「现在日间的模式,代码没有高亮,夜间模式代码是高亮的。」
AI: 这次它一开始判断对了表层原因(Shiki 双主题只写了 :root.dark 规则),但改完发现背景还是不对。
最后它没有继续猜,而是往页面里注入了一段探测脚本:
js
var pre = document.querySelector('.md pre.astro-code');
var span = pre.querySelector('span[style]');
getComputedStyle(pre).backgroundColor // → rgba(0, 0, 0, 0)
getComputedStyle(span).color // → rgb(179, 146, 240)
rgba(0,0,0,0) 就是答案 ------原来那条规则里的 background-color: transparent !important 把 pre 的深色背景一起清掉了。
这个诊断方式比我想象中更靠谱。我原来看到截图是"浅底浅字"就直接改颜色变量了,绕了半小时弯路------如果一开始就读计算样式,五分钟就能定位。
第 14 轮:写文章
我: 「写一篇博客文章,就写这次搭博客的完整过程。」
它写完我让它改了几版:加了副标题、调了章节结构、把太技术化的部分翻译成人话。最后还顺手修了 npm run new 脚本------那个脚本从项目初始版本就没运行过,第一次用就崩(existsSync 从 node:fs/promises 里不存在)。
它自己承认了这件事:
这个脚本之前从没真正跑过,藏了三个问题。
四、哪些环节必须由人把关
回顾这十一轮,有几件事是人做不了或者不该做的:
1. 判断哪些是真需求
第一轮 AI 就反问"要不要 CMS、评论、编辑器"。如果我不介入,项目会多出一堆用不上的东西。
AI 会写代码,但不会替你决定要什么。
2. 技术方案的判据
它给出排除理由的时候,我需要能判断这些理由是否成立。比如"85KB runtime 不值得"------这个判断依赖于"我的博客只有一个菜单按钮需要交互"这个前提,前提是我给的。
3. 线上验证
curl 一下比任何静态检查都可靠。AI 做完静态分析不等于页面真的能用。
具体的例子 :第 5 轮 AI 说"服务正常,只是健康检查误报",我后来自己 curl 了一次确认------它是对的。但如果我不测,我只能选择信或不信。
4. 叫停错误方向
有一次 AI 建议手动给标签 URL 加 encodeURIComponent,结果全站标签 404------Astro 本来就自动编码,手动编码是双重编码。
它后来自己总结:
当"修复"让问题从局部扩大到全局时,先怀疑自己的判断方向,而不是继续加大力度。
这句话适合所有 AI 协作场景。
5. 验收标准
"深色模式不能闪"是我的标准,不是它推导出来的。同理,"代码块要能看清"也是。
6. 审美判断
第 9 轮做 Logo 时,AI 第一版把轴头做得太细、纸面和背景色太接近,34px 下几乎看不见。它自己发现了并调整了几何尺寸。
但"简洁些"这个要求是我提的------去掉左右两竖、让图形更干净,这是审美偏好,不是技术判断。
7. 决定什么时候收手
第 13 轮代码高亮,它一开始判断对了表层原因,但改完发现背景还是不对。如果这时候我嫌麻烦说"差不多就行了",线上就会留一个浅底浅字看不清的代码块。
多花五分钟读一次计算样式,比上线后再返工划算。
8. 发现"能跑"不等于"跑得对"
这一条是第 10 轮学到的,也是最容易被忽略的。
部署脚本第一次跑成功了------容器 healthy,站点 200,所有检查都通过 。但当我看到 Docker 还在拉 node:22-alpine 的镜像层时,我意识到:每次部署都要下载几十 MB、装 300+ 依赖,只为了生成几百 KB 的静态文件。
技术上没错,架构上却是浪费的。
AI 不会主动质疑已经跑通的东西------它的判断基准是"能不能跑通"。而"该不该这么跑"这个问题,只有人提出来。
这一轮改造之后,每次部署的传输量从几十 MB 降到几 KB。
「能跑」和「跑得对」之间的距离,就是人在这个过程中的主要价值所在。
五、最终成果
bash
技术栈 Astro 5.18.2 + @astrojs/rss + @astrojs/sitemap + Tailwind 4
代码量 17 个源文件 / 2477 行(含样式与脚本)
构建产物 568 KB / 30 个静态页面
运行时 纯静态,零 JS 框架
部署方式 本地构建 → rsync 增量同步 → Nginx 容器卷挂载
增量传输 改一个文件 ≈ 8 字节(架构重构前是几十 MB)
服务器 阿里云轻量应用服务器 + Docker + Nginx(无 Node 运行时)
域名 makingitbig.cn,Let's Encrypt 双域名证书,自动续期
合规 ICP 备案 + 公安联网备案
功能清单:文章列表分页、标签筛选、归档页、关于页、深色模式(跟随系统)、Shiki 代码高亮、RSS、sitemap、OpenGraph、JSON-LD 结构化数据、动态 favicon。
六、几个可能有用的经验
1. 验证优先于交付
AI 最容易犯的错是"写完就交"。这次有几个 bug 都是写完没实测:
npm run new脚本从项目初始版本就没运行过,第一次用就崩- 部署脚本写完后连续暴露三个问题(rsync 选项、中文括号、变量边界)
- 三个都只有真跑才会暴露
结果是我成了第一个测试员。 后来 AI 自己在文章里写下了这句:
验证优先于交付。这几个 bug,任何一个只要实测一次就能立刻发现。
给 AI 的明确指令是有效的:"先跑一遍再给我"。
2. 让 AI 自己解释原理,它才能自己找到根因
第 7 轮 www 证书的问题,如果 AI 只是"再改一版脚本",可能一直碰壁。它先解释清楚 server 级 if 早于 location 选择,自己就找到了修法。
3. 让 AI 读真实数据,不要让它猜
代码高亮那次,注入 getComputedStyle 探测脚本比看十张截图都准:
js
getComputedStyle(pre).backgroundColor // → rgba(0, 0, 0, 0)
遇到"看起来不对"的问题,先要可测量的数据。
4. 一次把配置写对,别靠打补丁
Nginx 那个循环,本质是初始配置结构有问题。AI 第三次干脆把两个 server 块整体重写,反而最省事。
打补丁不如重写------前提是你知道正确结构是什么。
5. 别把"跑通了"当成"做完了"
AI 说"部署成功"的时候,容器可能还是 unhealthy 状态,只是 curl 能通。状态字段和实际行为不一致时,以实际行为为准。
6. 用完再跑一遍,别让人当测试员
部署脚本写完时 AI 说"搞定了"。我跑了一次,报错。再跑,又报错。再跑,才跑通。
三次里暴露的三个问题,每一个都是本地一次完整执行就能提前发现的。
这不是"AI 不够聪明",是流程问题------脚本类交付物必须自己先跑一遍完整流程。
7. 工具特性要按最保守的版本考虑
--info=progress2 在 macOS 的 openrsync 上不存在。写脚本时用到的每个命令行工具,都想想"在别人机器上能不能跑"。
8. 中文文案里的 $变量 必须加花括号
$HTTP_CODE) 会被 bash 解析成变量名 HTTP_CODE)。这是中文开发者的经典坑,bash -n 还查不出来。
七、写在最后
有人问我:这算"用 AI 写了个博客"吗?
我觉得不算。更准确的说法是:我用 AI 承担了实现,把时间留给了判断。
那些真正花时间的从来不是敲代码------
- 判断哪些需求是真需求
- 决定技术方案背后的判据
- 在 AI 给出错误方向时及时叫停
- 不把没验证过的东西交出去
工具能加速执行,但判断力永远没法外包。
如果你也想试试:我的提问方式
这三天我逐渐摸出了一些让 AI 表现更好的提问习惯:
① 第一轮把需求和约束说清楚
不要说"帮我搭个博客",要说"个人博客、单作者、只写 Markdown、不要 CMS、服务器在国内、最终要能备案"。约束越具体,返工越少。
② 遇到问题先给证据,不给结论
截图 + "这个不对",比 "帮我修一下" 有用得多。AI 能从截图里读到的信息,远比我用文字描述的多。
③ 明确要求它先说原因再动手
「先告诉我为什么,再改」------这一句话能省掉大量来回。
④ 关键节点要求实测
「你先跑一遍再给我」------这四个字值好几轮返工。
⑤ 它做错了就指出来,但说清哪里不对
「favicon.svg 这个没有改啊,左右两边的竖可以去掉」比「这个不对,重做」有效得多。
⑥ 有些事不用问 AI
命名、目录结构、变量名这类风格问题,一开始就说清楚;让它自由发挥反而要返工。
附:常用命令
bash
# 本地开发
npm run dev
npm run build && npm run preview
# 新建文章
npm run new -- "文章标题"
# 部署(服务器地址已内置,无需参数)
bash deploy/deploy.sh
# 配免密登录(一次性,只需输一次密码)
bash deploy/deploy.sh --setup-key
# 改了 Dockerfile 才需要重建镜像
bash deploy/deploy.sh --rebuild
# 排查
docker ps | grep astroblog # 看健康状态
docker logs --tail 30 astroblog # 看日志
curl -I https://你的域名 # 线上验证
关于本文
写这篇文章的时候,博客已经上线第二周。这篇是站内第 9 篇文章,也是第一篇完整的项目复盘。
期间还发生了一次架构重构------把部署从"服务器构建"改成"本地构建 + rsync 增量同步"。这个改动本身也是复盘的一部分,所以一并写在这里了。
有些路,走过一遍就够了。