dranixj 网站构建过程全记录
一、引言
Claude Code 最近大发慈悲,亚洲区等于白送 4 倍 token,小杯变大杯。想着不用就浪费,拿着之前入的域名加白嫖的 AWS 免费用户,就来搞个网站吧。别管新闻新不新,最起码是自己想看的——还没广告。
dranixj 是一个知识与新闻分享网站——AI 搜索热门话题、生成文章、构建为纯静态页面,然后由 Nginx 直接配信给访问者。没有登录注册,没有评论系统,没有用户数据库。整个网站就是一堆 HTML、CSS、JSON 文件,安安静静地躺在服务器上等人来看。
听起来简单?确实简单。但走到这一步,中间经历了一次不小的架构"推倒重来"。
做啥内容呢?打开手机备忘录的小本本,里面有每天看头条留下的各种关键字——OpenClaw、Playwright、WebMCP 等等。干脆就做个给关键字帮我收集话题、组织成文章的工具,并打包挂到互联网上吧。光有这些太干了,干脆再加点乱七八糟的自行车啊、跑步啊、游戏啊。别管有没有 sense,就主打一个任性。
二、最初的架构设计:传统三层架构
项目刚开始的时候,我的第一反应是搭一个"正经"的全栈应用——React 做前端、Express 做后端 API、Aurora PostgreSQL 做数据库。经典的三层架构,教科书式的选型。
当时的想法很直觉:文章得存数据库吧?分类和标签得有关联表吧?搜索得走 SQL 查询吧?于是数据库设计文档写了一版,ORM 也挑好了,Express 路由规划得整整齐齐。
成本构成大概是这样:
| 项目 | 月费用 |
|---|---|
| Aurora PostgreSQL(db.t4g.micro) | ~$50 |
| EC2 t3.small(跑 Express + Nginx) | ~$15 |
| 其他(S3、数据传输、CloudWatch 等) | ~$13 |
| 合计 | ~$78 |
月费 78 美元,对于一个个人项目来说……说不上贵,但也绝对说不上便宜。AWS 复归用户的 100 美刀,只够折腾一个月的话肯定是不划算。尤其是 Aurora PostgreSQL 那 50 刀,占了大头。
这个阶段的代码后来保留在了 feature/server-version 分支,作为"历史的见证"。
当然,失去数据库也意味着失去一些互动性。但对于这个项目的需求来说,这个取舍是值得的。
三、架构转折:从全栈到纯静态
转折点出现在仔细审视了一遍网站的实际需求之后。
我问自己:这个网站有哪些写操作?答案是——没有。所有内容由 AI 在本地生成,经我审核后发布。读者只是来看文章的,不需要登录、不需要评论、不需要提交任何数据。整个网站就是一个纯只读的内容展示平台。
既然是纯只读的,那为什么要跑一个 Express 后端?为什么要养一个数据库?
于是新方案浮出水面:
content/output/*.md → build-content.js → client/public/data/*.json → Nginx 静态配信
Markdown 文件作为"数据库",构建脚本作为"后端 API",生成好的 JSON 文件直接让 Nginx 配信。没有运行时服务器进程、没有数据库连接池、没有 ORM。
迁移过程说起来也干脆——废止 Express 后端、删除 Aurora PostgreSQL、把数据模型从数据库表改为 Markdown 文件的 YAML frontmatter。原来的数据库设计文档顶部加了一行大大的"已废弃",指向新的内容管道文档。
成本的变化相当可观:
| 项目 | 月费用 |
|---|---|
| EC2 t4g.micro(仅 Nginx) | ~$6 |
| CloudWatch 监控 | ~$3 |
| 域名 + 杂项 | ~$11 |
| 合计 | ~$20 |
从 78 刀降到 20 刀,降幅接近 75%。主要是甩掉了 Aurora PostgreSQL 这个最大的开销,同时 EC2 也从跑应用服务的 t3.small 降到了只跑 Nginx 的 t4g.micro(ARM 架构还更便宜)。
当然,纯静态方案也不是没有代价。最明显的就是搜索功能——客户端搜索需要先加载整个 search-index.json(包含所有文章的标题和摘要),然后在本地做关键词匹配。文章数量少的时候还好,数量多了以后这个 JSON 文件会越来越大。不过对于一个个人网站来说,这个量级的文章数量在可预见的未来都不会是问题。
取舍有时就是这样:你用一点点理论上的"可扩展性",换来了架构上的极致简洁和运营成本的大幅下降。对这个项目来说,这笔买卖很划算。
四、内容管道设计
静态架构的核心就是内容管道——把 Markdown 文件变成前端能直接用的 JSON。
内容格式
每篇文章都是一个 Markdown 文件,头部用 YAML frontmatter 定义元数据:
---
title: 文章标题
slug: article-slug
date: 2026-03-17
category: ai
tags: [AI, LLM, 工具]
summary: 文章摘要,50-100 字
cover: /images/articles/article-slug/cover.jpg
author: AI 生成
readingTime: 8 分钟
difficulty: 中级
---
# 文章标题
正文内容...
这个格式够简单也够灵活。slug 用于 URL 路径,category 关联分类,tags 用于搜索和展示,coverImage 是文章卡片上显示的封面图。
构建脚本
build-content.js 是整个管道的核心。它做的事情其实不复杂:
- 扫描
content/output/下所有.md文件 - 用
gray-matter解析 YAML frontmatter,提取元数据 - 用
marked+highlight.js把 Markdown 正文转成 HTML(代码块带语法高亮) - 从正文末尾的"参考来源"章节提取引用链接
- 读取
topics.yaml获取分类定义 - 生成一系列 JSON 文件到
client/public/data/
生成的 JSON 文件一览:
| 文件 | 说明 |
|---|---|
articles-page-{N}.json |
分页文章列表(每页 12 篇) |
articles/{slug}.json |
文章详情(完整 HTML 正文、来源、标签) |
categories.json |
分类列表 |
categories/{slug}/page-{N}.json |
分类下的文章列表(分页) |
search-index.json |
搜索用的轻量索引(仅标题 + 摘要) |
一次 node scripts/build-content.js 就把所有 JSON 生成好,React 前端直接 fetch 这些静态 JSON 文件就行——简单、快速、不需要任何运行时依赖。

分类配置
分类体系在 topics.yaml 里定义。目前有 5 个分类:
| 分类 | 说明 | 权重 |
|---|---|---|
| AI | AI 工具、开源项目、大模型 | 3(最高频) |
| Fitness | 器材评测、训练技巧、赛事 | 2 |
| TV Game | 主机游戏、攻略、资讯 | 2 |
| Hobby | 模型制作、遥控车、树莓派 | 1 |
| Blog | 个人博客原创内容 | 0(不自动生成) |
每个分类都配有检索关键词列表,用于 AI 自动搜索话题。关键词还有个 recommend 标记——true 表示验证过的优质检索词,false 表示不适合的词(话题过时、质量低等),没标记的就是待评价。这样 AI 在选择话题时就有了优先级参考。
权重系统决定生成文章时各分类被选中的概率:AI 分类权重 3,意味着它被选中的概率大约是 Hobby(权重 1)的 3 倍。Blog 分类权重 0,完全不参与自动生成——博客文章由另一套 Skills 从本地素材创作。
从搜索到发布
完整的工作流是这样的:
generate.yaml 控制运行时参数:每次生成 3 篇、字数 1500-3000 字、搜索偏好(Hacker News、GitHub Trending、TechCrunch、36氪等)、图片配置(优先从原文获取封面图,兜底用 Unsplash)。
整套管道设计的哲学就是:配置驱动、AI 执行、人工把关。
五、前端技术选型与设计哲学
技术栈
前端选了 React 19 + Vite 6 + React Router 7。选型理由很朴素——React 用得最熟、Vite 构建速度快、React Router 是 React 生态下最成熟的路由方案。
依赖列表很短。package.json 的 dependencies 就三个包:react、react-dom、react-router-dom。没有 Redux、没有 Axios、没有 CSS-in-JS。数据获取用原生 fetch,状态管理用 useState/useEffect,样式用普通 CSS 文件。
这种"能少则少"的心态贯穿了整个前端开发。
设计理念
UI 设计受 htmlBurger 启发,走极简主义路线:
- 内容优先:设计服务于内容,不喧宾夺主
- 功能至上:每个视觉元素都有明确目的,拒绝纯装饰
- 克制而有温度:色彩用量少但精准,留白充足
色彩体系也很克制:白底(#ffffff)+ 深灰文字(#353535,不用纯黑,降低视觉压力)+ 蓝色强调(#009fd1,仅用于链接和可交互元素)+ 黄色 CTA(#ffe26d,每屏最多出现一次)。页面主体只用中性色,保持干净。
字体选了 Inter——开源、可变字重、为屏幕阅读优化。6 级字号层级、8px 基数间距体系,这些基础设定让整个网站看起来有序而不死板。
响应式布局
文章网格用 CSS Grid 实现,三个断点覆盖三种布局:
| 屏幕宽度 | 列数 | 关键变化 |
|---|---|---|
| > 1200px | 3 列 | 完整布局 |
| 768px ~ 1200px | 2 列 | 标题字号缩小 |
| < 640px | 1 列 | 搜索框纵向排列 |
桌面端用 repeat(auto-fill, minmax(320px, 1fr)) 自动填充列数,平板和手机端用固定列数。Header 导航栏在分类按钮过多时不会撑破布局——用 ResizeObserver 检测溢出,自动把放不下的分类收进汉堡菜单。

暗黑模式
暗黑模式用 CSS 变量 + localStorage + 系统偏好检测三重机制实现。首次访问时跟随系统设置,用户手动切换后记住偏好。没有花哨的渐变切换动画——内容型网站追求的是即时响应,点一下立刻变,别让人等。

六、规则与最佳实践
项目根目录下有一个 rules/ 文件夹,里面放了 13 个规则文件。你可能会好奇:一个个人项目,给谁定规则?
答案是——给 AI 定规则。
这个项目的大部分代码都是和 Claude Code 协作完成的。AI 写代码很快,但如果不给它"行为准则",它可能今天用 class 组件明天用函数组件,今天用 axios 明天用 fetch,代码风格飘忽不定。rules/ 就是用来解决这个问题的。
13 个规则文件覆盖了这些领域:
| 文件 | 内容 |
|---|---|
react-19-patterns.md |
React 19 新特性和最佳实践 |
react-components.md |
组件设计规范 |
react-hooks.md |
Hooks 使用规范 |
react-performance.md |
性能优化策略 |
react-security.md |
前端安全(XSS 防护等) |
react-testing.md |
测试规范(Vitest + Testing Library) |
uiux-design.md |
UI/UX 设计规范(色彩、字体、间距、组件样式) |
nodejs-security.md |
Node.js 安全实践 |
nodejs-performance.md |
Node.js 性能优化 |
express-api.md |
Express API 规范(历史遗留) |
express5-migration.md |
Express 5 迁移指南(历史遗留) |
database.md |
数据库规范(历史遗留) |
README.md |
规则目录索引 |
有几个是全栈时代的遗留——Express 和数据库相关的规则现在用不到了,但留着也不碍事。
除了 rules/,还有 CLAUDE.md 作为项目级约定。里面定了一个简单但重要的双语原则:文档用中文、代码用英文。所有注释、提交信息、文档都写中文,但变量名、函数名、文件名用英文。这样既方便阅读,又不会出现拼音命名的尴尬。
写规则这件事,其实就像给新入职的同事写 onboarding 文档——你不能假设对方知道你的偏好,得把期望说清楚。区别是,人类同事读一遍就记住了,AI 每次对话都会重新读一遍。所以这些规则越清晰、越具体,协作效果就越好。
七、AI 在构建中的角色
说到 Claude Code,它在这个项目中的角色远不止"代码补全工具"。
更准确的定位是:一个全栈搭档。
从最初的架构设计讨论("我要不要用数据库?"),到具体的代码编写、部署脚本开发、CI/CD 配置、内容生成管道搭建——甚至你现在读的这篇博客,也是 AI 基于项目素材起的草稿,然后我做审核和补充。
Skills 系统
项目用了两个核心 Skills:
generate-articles——自动搜索热门话题并批量生成文章。运行时它会:
- 读取配置文件(分类权重、检索关键词、生成参数)
- 按权重选分类 → 选检索词(优先 recommend: true 的)→ 用 WebSearch 搜索
- 扫描已有文章去重
- 逐篇生成 1500-3000 字的详细文章
- 保存到
content/output/
generate-blog——从本地素材生成博客文章。和 generate-articles 不同,它不需要搜索互联网,而是基于项目中已有的文档、代码、配置文件来创作。这篇文章就是这样诞生的。
人机协作的边界
AI 负责的部分:繁重的信息搜索、初稿撰写、代码实现、重复性的配置工作。 人类负责的部分:架构决策、内容审核、品质把关、最终拍板。
这不是"AI 替代人"的故事。更像是"AI 处理 80% 的体力活,人类专注在 20% 的判断和创意上"。比如 AI 能快速生成一篇关于某个开源项目的介绍文章,但它不知道这篇文章的角度是否适合我的读者、这个项目是否值得推荐、文中的技术观点是否准确——这些还是需要人来把关。
偶尔,它也会犯傻。

如果道歉有用的话还要警察干什么?
八、CI/CD 与部署
GitLab CI 四阶段管道
CI/CD 用的是 GitLab CI,定义在 .gitlab-ci.yml 里,四个阶段:
lint → test → build → deploy

- lint:ESLint 检查前端代码
- test:Vitest 跑单元和组件测试
- build:构建内容 JSON + React 构建 + 打包成 tarball
- deploy:上传 S3 → SSM 触发 EC2 部署(仅 main 分支,手动触发)
运行环境是 node:22-slim 镜像(deploy 阶段换成 amazon/aws-cli),依赖用 npm ci 安装保证一致性。
部署架构
部署这块有个有趣的设计决策——EC2 实例完全不需要访问 GitLab。
GitLab CI Runner → 构建打包 → 上传 S3 → SSM 触发 → EC2 从 S3 下载 → 解压部署
为什么不用传统的 SSH 直连部署?几个原因:
- 安全:EC2 不需要开放 SSH 端口(22 端口可以完全关掉),攻击面大幅缩小
- 权限隔离:CI 用户只需要 S3 上传和 SSM 发送命令的权限,不需要 SSH 密钥
- 审计:SSM 的所有命令执行都有日志记录,比 SSH 操作更容易追溯
- 简单:不用管理 SSH 密钥分发和轮换
SSM(Systems Manager)本质上是 AWS 提供的远程命令执行服务。CI 管道通过 SSM 发一条命令让 EC2 执行部署脚本,然后轮询命令执行状态直到成功或失败。整个过程不需要 EC2 主动连接任何外部系统——它只是被动地接收指令、从 S3 下载文件、执行本地脚本。
部署流程细节
EC2 上的部署脚本会做这些事:
- 从 S3 下载部署包(tarball)
- 备份当前版本到
/var/www/dranixj-backups/ - 解压新版本到
/var/www/dranixj/ - 重载 Nginx 配置
- 执行健康检查
备份机制保留最近 5 个版本。如果新版本出了问题,一行命令就能回滚:
bash infra/scripts/rollback.sh /var/www/dranixj-backups/backup-20260317-120000.tar.gz
也可以从 S3 拉取历史版本回滚。总之,回滚不应该比部署更难——这是运维的基本原则。
SSL 与健康检查
SSL 证书用 Let's Encrypt + certbot,cron 自动续期。健康检查脚本会检查 Nginx 运行状态、静态文件可用性、磁盘空间、SSL 证书过期时间。这些都是基础设施层面的"保底"措施——不出问题的时候你忘了它们的存在,出问题的时候庆幸有它们在。
S3 上的旧部署包也会自动清理,保留最近 10 个。毕竟 S3 存储虽然不贵,但无限堆积也不是好习惯。
九、AWS 权限设定(IAM)
IAM(Identity and Access Management)这一块可能是整个项目里最"无聊"但最重要的部分。权限配错了,轻则部署失败,重则安全事故。
最小权限原则
核心思想很简单:每个角色只给它需要的权限,一点都不多给。
项目里有三种 IAM 身份,职责完全分离:
| 身份 | 用途 | 关键权限 |
|---|---|---|
| 管理用户 | 日常监控、告警管理 | EC2 只读、CloudWatch 读+告警管理、账单查看、S3 只读 |
| CI/CD 部署用户 | 自动化部署 | S3 读写、SSM 发送命令 |
| EC2 实例角色 | 运行时服务 | SSM Agent、S3 只读下载、CloudWatch Agent |
管理用户不能部署、CI 用户不能看账单、EC2 不能往 S3 写东西——每个角色各司其职。
CI/CD 用户的权限设计
这个最有代表性。CI 部署用户只需要两件事:
- 把构建产物上传到 S3 部署桶
- 通过 SSM 给 EC2 发送部署命令
所以它的权限就精确到:
s3:PutObject、s3:GetObject、s3:ListBucket、s3:DeleteObject——限定在部署桶ssm:SendCommand——限定到特定文档类型和特定实例ssm:GetCommandInvocation——查询命令执行结果
注意 SSM 权限的拆分:SendCommand 支持资源级限制(可以精确到具体实例),但 GetCommandInvocation 不支持——Resource 必须是 *。这是 AWS API 设计的限制,不是偷懒。如果把这两个权限写在同一个 Statement 里,会因为 Resource 冲突导致权限不足。
EC2 实例角色
EC2 的角色更简单:
AmazonSSMManagedInstanceCore——让 SSM Agent 能正常运行- S3 只读策略——只能从部署桶下载,不能上传
CloudWatchAgentServerPolicy——让 CloudWatch Agent 能上报指标和日志
密钥管理
生产环境的 AWS 凭证(Access Key ID、Secret Access Key)存在 GitLab CI 变量里,标记为"受保护"(仅 protected 分支可用)和"隐藏"(日志中不显示)。本地开发用 .env 文件,当然这个文件在 .gitignore 里——永远不提交。
密钥轮换有标准流程:创建新 Key → 更新 GitLab 变量 → 验证管道正常 → 禁用旧 Key → 删除旧 Key。建议每 90 天轮换一次。
十、监控与运维(CloudWatch)
网站上线不是终点,运维才是长期的事。
监控指标
用 CloudWatch Agent 收集系统级指标,5 分钟采集一次(免费额度内):
| 指标 | 来源 | 说明 |
|---|---|---|
| CPU 使用率 | AWS/EC2 内置 | 无需 Agent |
| 内存使用率 | CloudWatch Agent(自定义命名空间) | t4g.micro 只有 1GB,需要关注 |
| 磁盘使用率 | CloudWatch Agent | 根分区监控 |
| Swap 使用率 | CloudWatch Agent | 内存不够时的缓冲 |
| Nginx 5xx 错误 | 日志过滤指标 | 从 access log 中提取 |
| EC2 状态检查 | AWS/EC2 内置 | 实例或底层硬件异常 |
自定义指标用了独立的 Dranixj 命名空间,在 CloudWatch 控制台里一眼就能找到。
告警策略
告警通过 SNS(Simple Notification Service)发邮件通知:
| 告警 | 条件 | 说明 |
|---|---|---|
| CPU 过高 | > 80%,持续 15 分钟 | 3 个周期(5 分钟/次)连续超标 |
| 内存过高 | > 85%,持续 15 分钟 | 1GB 机器上 85% 就该紧张了 |
| 磁盘过高 | > 85%,持续 10 分钟 | 日志堆积或部署包未清理 |
| 5xx 错误突增 | 5 分钟内 > 10 次 | 静态站点理论上不该有 5xx |
| 状态检查失败 | 任何失败 | 实例不可达,最严重的告警 |
恢复时也会收到通知(OK Action),这样你知道问题什么时候解决了,不用一直盯着。
日志管理
三组日志上报到 CloudWatch Logs:
| 日志组 | 来源 | 保留天数 |
|---|---|---|
/dranixj/nginx/access |
Nginx 访问日志 | 14 天 |
/dranixj/nginx/error |
Nginx 错误日志 | 14 天 |
/dranixj/system/syslog |
系统日志 | 7 天 |
保留天数刻意设得比较短——长期存储日志费钱,而且对小站来说意义不大。14 天足够排查大多数问题了。
成本追踪
整套监控的费用:
| 方案 | 月费用 |
|---|---|
| 免费额度内(基本指标 + 10 个告警) | $0 |
| 轻量监控(推荐,详细指标 + 日志 + 告警) | ~$3 |
| 完整监控(含合成监控 + 日志过滤指标) | ~$5-8 |
对于日 PV 不到 1000 的小站,基本可以在免费额度内覆盖。加上轻量监控大概 3 刀/月,完整监控也就 5-8 刀。这笔钱花得值——总比半夜被用户告诉你"网站挂了"好。
回看整个成本结构:从最初全栈架构的 $78/月,到纯静态方案的 $20/月,这 $20 里包含了 EC2 运行、CloudWatch 监控、域名续费和零散的数据传输费。对于一个功能完整、有 CI/CD、有监控告警、有备份回滚的个人网站来说,这个数字相当合理。
十一、回顾与展望
回头看这个项目,最大的感触是:简单的东西往往更难做到,但一旦做到了,维护起来就特别省心。
架构从全栈三层到纯静态配信,不是偷懒,是在认清需求后做的减法。数据库没了、后端没了、但网站该有的功能一个不少——文章展示、分类浏览、关键词搜索、响应式布局、暗黑模式、CI/CD 自动部署、监控告警、备份回滚。区别只是实现方式更轻量了。
另一个收获是 AI 协作的体验。用 Claude Code 不是"让 AI 替你写代码",更像是有了一个不知疲倦的搭档——你说想法,它给方案;你定规则,它照着执行;你审核把关,它持续迭代。rules/ 目录和 CLAUDE.md 就是这种协作模式的产物——把期望写清楚,AI 就能稳定地输出符合预期的结果。
这个网站目前还在持续更新内容。架构已经稳定,成本可控,运维靠自动化。未来可能会做的事情……先不立 flag,走着看吧。
评论