一个下午,从零到公网上线一个 AI 技术博客
完整记录这个博客的搭建过程:12 个阶段、从 PRD 到公网可访问、再到用独立 agent 自动化运维。全程由 Claude Code 驱动,踩过的坑都在这里。
这个博客本身就是一次实验:能不能在一个下午内,从零到公网上线,再配一个 agent 代管内容?
答案是可以。这篇文章是完整流水账。
路线图:12 个阶段
阶段 0-1 仓库脚手架 + 设计令牌
阶段 2-3 MDX 内容管线 + 首页/列表/详情
阶段 4-5 标签/分类/归档 + AI 工具目录
阶段 6-7 搜索/RSS/SEO + Giscus 评论
阶段 8 飞书机器人对接
阶段 9 互联网访问 (Vercel 试水)
阶段 10 切到 Cloudflare Pages (静态)
阶段 11 独立 blog-agent 运维
阶段 12 写这篇文章
每个阶段对应一个 git commit,可以 git log --oneline 看到时间线。
技术选型
不吹某个框架,只解释为什么:
| 层 | 选型 | 原因 |
|---|---|---|
| 框架 | Next.js 15 App Router | SSG 性能好,MDX 社区成熟,有 output: 'export' 可静态化 |
| 样式 | Tailwind + OKLCH tokens | 8px 基线网格,深浅色自动切换,字号阶梯 ≤ 6 档 |
| 内容 | Markdown + git | 无数据库,版本即历史,写作用任何编辑器 |
| 搜索 | Pagefind | 构建时索引,零后端成本,浏览器直接查 |
| 评论 | Giscus | GitHub Discussions 托管,零服务器,天然反滥用 |
| 托管 | Cloudflare Pages | 免费,无 SSO 保护,国内访问快 |
| 运维 | 独立 blog-agent | 不污染博客仓库 |
关键设计约束
四条硬约束写在 PRD 里,所有代码必须服从:
- 简约:首页主要板块 ≤ 3 个,图标仅一套,主色 ≤ 2 种
- 简洁:字号 ≤ 6 档,圆角 ≤ 3 种,阴影 ≤ 2 级
- 工整:8px 基线网格,文章 720px / 目录 1200px 最大宽度
- 排版合理:正文 16-18px,行高 1.7,段宽 60-75 字符
这些不是口号,是可验收的规则。Tailwind config 里字号只写了 6 档,想加第 7 档会被评审打回。
静态 vs 动态:一个开关
next.config.mjs 里加了 BUILD_MODE 开关:
const STATIC = process.env.BUILD_MODE !== 'dynamic';
const nextConfig = {
...(STATIC && { output: 'export' }),
trailingSlash: STATIC,
images: {
...(STATIC && { unoptimized: true }),
},
};
现在默认静态,部 Cloudflare Pages。未来要加用户登录/收藏:
export BUILD_MODE=dynamic && npm run build
# 改部 Vercel 或自建
代码主体一行不用动。
几个真实踩过的坑
1. YAML 的 date 字段会被解析成 Date 对象
gray-matter 把 frontmatter 里的 date: 2026-04-29 自动解析成 JavaScript Date,但我在 TypeScript 里声明的是 string。构建时报:
TypeError: r.date.slice is not a function
修:在解析层强制转字符串:
function toISODate(value: unknown): string | undefined {
if (value == null) return undefined;
if (value instanceof Date) return value.toISOString().slice(0, 10);
if (typeof value === 'string') return value.slice(0, 10);
return String(value);
}
2. Vercel 免费版默认给所有 .vercel.app 域名加了 SSO 保护
第一次部署成功后,任何人访问都会被重定向到 Vercel 登录页。ssoProtection: { deploymentType: 'all_except_custom_domains' } 是团队级默认。改项目 null 后需要重新部署,因为保护策略在部署时快照。
为了少折腾,直接切 Cloudflare Pages。Pages 免费版没有这个保护。
3. Next.js 15.1.3 被 Vercel 拒绝部署
报 Vulnerable version of Next.js detected。升到 16.2.4,顺手把 eslint-config-next 也升了,测试仍全绿。
4. RSS/Sitemap route 在 output: 'export' 下需要显式 force-static
export const dynamic = 'force-static';
export default function sitemap() { ... }
不加会报 not configured on route with output: export。
blog-agent:独立的运维层
博客主站只管展示,所有自动化放在独立仓库 ~/blog-agent:
能力一览:
写:write / write-tool / revise (走 LLM)
发:publish / deploy (git + wrangler)
运维:status / drafts / stale-tools / health
两个仓库关系:
~/ai-blog 博客主站(不认识 agent)
~/blog-agent 工具(通过 BLOG_ROOT 读写博客仓库)
这样分离的好处:
- 博客仓库保持干净,未来换前端框架不影响 agent
- agent 演进不用动博客代码
- CI/CD 可以独立配置
举例,发一篇新文章的完整流程:
cd ~/blog-agent
node bin/agent.mjs write "一个清晰的主题" # AI 生成草稿
# 编辑器审阅 ~/ai-blog/content/posts/xxx.mdx
node bin/agent.mjs publish xxx # draft:false + commit + push
node bin/agent.mjs deploy # build + Cloudflare 部署
node bin/agent.mjs health # 线上 URL 探测
每一步都幂等,跑第二次无副作用。
性能指标
构建产物:
- 28 个页面全部 SSG
- 首页 First Load JS ~110KB
- 全部 HTML + 静态资源 3.2MB
- Lighthouse 目标 ≥ 98
线上实测(Cloudflare Pages,北京网络):
200 / 1060ms
200 /posts/ 592ms
200 /tools/ 197ms
200 /rss.xml 198ms
200 /sitemap.xml 277ms
首屏渲染的大头在于 Cloudflare 边缘节点首次建连,二次访问基本 < 300ms。
总成本
- 域名:未买,用
*.pages.dev免费子域 - 托管:Cloudflare Pages 免费
- LLM:MiLLM 内部网关,团队内部免费
- 评论:Giscus 免费
- 搜索:Pagefind 构建时生成,无后端
- 总计:¥0
下一步
- 补 10 篇种子教程
- 工具目录扩到 30 条以上
- 买自定义域名,绑到 Pages
- GitHub Actions 自动部署(目前还是本地 wrangler)
- 尝试把 agent 接到飞书机器人,让"@机器人 发一篇关于 X 的文章"直接走完全流程
延伸阅读
docs/PRD.md:v1.2 产品需求文档,四条硬约束和全部功能清单docs/ARCHITECTURE.md:分层架构 + 路由映射docs/DEPLOY.md:部署全流程docs/FEISHU.md:飞书机器人双向接入指南
源码在 GitHub:https://github.com/a554524/ai-blog