>
回到列表
#Vibe Coding#AI 生图#Cloudflare Tunnel#NAS

阿木的 Vibe Coding 建站实录:从 AI 生图提示词档案到 NAS 公网部署

记录我如何用 Vibe Coding 和单元式开发,完成一个 AI 生图提示词分享网站,并通过 Debian NAS、Docker 与 Cloudflare Tunnel 部署到公网。

#Vibe Coding#AI 生图#Cloudflare Tunnel#NAS#建站方法论

阿木的 Vibe Coding 建站实录:从 AI 生图提示词档案到 NAS 公网部署

这是我,阿木,做 MU / PICPROMPT 这个 AI 生图提示词分享网站的完整记录。

这个项目最后没有采用纯 Cloudflare Worker 生产部署,而是运行在家庭 Debian NAS 的 Docker 中,再通过 Cloudflare Tunnel 穿透到公网。Cloudflare 负责 DNS、HTTPS 和 Tunnel 入口,网站和 API 仍然由 NAS 自托管。

公网地址:

https://mupicprom.amuspaces.top

这篇文章想讲的不是“我用了什么框架”,而是一个更重要的问题:

如何把一个模糊的建站想法,拆成 AI 可以持续执行、每一步都能验收、最后真的能上线的开发过程。

一、最初的建站想法

我想做的是一个 AI 生图提示词及 AI 生图图片分享博客网站。

它不只是放图片,也不只是放一段 prompt,而是让用户完成一条完整路径:

看到图片 → 理解画面 → 查看结构化提示词 → 复制使用 → 按条件检索类似作品

所以它的核心理念是:

所见即所得,高收纳性,高检索性。

每张图片都应该对应一套清楚的创作语言;每条提示词都应该可以被拆开、理解、复制和再次组合。

二、先用建站方法论回答五个问题

我使用了 build-site-methodology 这套建站方法论。它没有一上来让我选择框架,而是先要求回答五个问题。

1. 核心用户是谁?

不是“所有互联网用户”,而是三类人:

  • 想直接复用提示词的 AI 生图用户
  • 想学习提示词结构的初学者
  • 需要整理灵感和素材的创作者

2. 用户来这里做什么?

核心动词只有四个:

  • 看图
  • 理解
  • 复制
  • 检索

3. 网站提供什么价值?

一句话概括:

用成图、结构化提示词和元数据,降低 AI 生图的学习与复用成本。

4. 哪些事情暂时放弃?

早期主动放弃:

  • 复杂社交系统
  • 私信和关注
  • 社区等级
  • 广告系统
  • 低价值 SEO 页面
  • 过度动效
  • 在线生图平台
  • 一开始就做复杂多语言

这一步很重要。很多项目不是做得太少,而是在核心功能还没有打磨好之前,把时间花在了不重要的地方。

5. 收益从哪里来?

第一阶段不急着变现,先验证内容价值和自然访问。未来再考虑投稿增值、会员或相关服务。

三、为什么采用单元式开发

这次最大的开发方法变化,是把工作分为两个角色:

总调度会话

总调度会话只负责:

  • 拆分任务
  • 安排单元顺序
  • 检查范围
  • 查看验收结果
  • 记录决策和踩坑
  • 给下一个 Agent 准备上下文

总调度会话不应该直接写业务代码。

开发单元会话

每个开发单元都新开一个对话,只完成一个目标:

  • 先读取项目规则
  • 明确本单元做什么
  • 明确本单元不做什么
  • 先写测试或验收标准
  • 开发功能
  • 验证构建和关键路径
  • 把结果写回 AGENTS.md
  • 结束会话

这样做的好处是,AI 不会在一个对话里不断扩大范围,也不会因为上下文变长而逐渐忘记最初的产品重点。

四、项目的完整开发单元

Unit 00:预备骨架

这是总调度会话中产生的预备代码,不计入正式开发单元。

完成了:

  • React + Vite + TypeScript 骨架
  • 首页视觉原型
  • 图片瀑布流原型
  • 独立作品详情页
  • 基础提示词结构
  • README 和 AGENTS 文档

后来我意识到,这些代码是在总调度会话中直接写出来的,违反了单元开发规则,所以在 AGENTS.md 中明确标记为“预备骨架”,不冒充正式 Unit 01。

Unit 01:内容模型与 Markdown 接入

目标是让网站脱离 App.tsx 里的硬编码数组。

完成了:

  • PromptEntry 类型
  • PromptSections 类型
  • GenerationParameters 类型
  • Markdown Frontmatter 解析
  • 标题分段解析
  • import.meta.glob 构建期加载
  • slug 作为稳定详情页地址
  • 一个完整示例提示词文件

每张图的提示词被拆成:

  • 主体描述
  • 动作与姿态
  • 场景与环境
  • 构图与镜头
  • 风格与画面表现
  • 质量与渲染
  • 负面提示词
  • 生成参数

同时保留 rawPrompt 和 rawNegativePrompt,避免结构化时丢失模型原始语法。

Unit 02:搜索与多条件筛选

搜索没有一开始就引入外部搜索引擎,而是采用构建期生成的客户端内存索引。

支持:

  • 标题搜索
  • 作者搜索
  • 标签搜索
  • 模型搜索
  • 生成器筛选
  • 提示词全文搜索
  • 多关键词 AND 语义
  • 多条件交集筛选
  • 清除筛选
  • 明确空状态
  • 搜索结果进入独立详情页

后续还修复了筛选条件变化后瀑布流可见数量没有重置的问题,以及 CSS 多栏布局在懒加载时产生的图片重排问题。

Unit 03:图片资源和 WebP 策略

这一单元确立了图片资产规则:

  • 统一使用 WebP 展示图
  • 列表和详情使用统一资源路径
  • 使用原生 lazy 加载
  • 使用异步解码
  • 使用稳定 aspect-ratio 占位
  • 使用模糊到清晰的渐进加载
  • 图片加载失败显示明确占位

我没有把原图源目录直接纳入网站。

项目中的 图/ 是 AI 生图原图源目录,必须满足:

  • 不扫描
  • 不索引
  • 不压缩
  • 不重命名
  • 不移动
  • 不提交 Git
  • 不参与构建

网站真正使用的是独立的 public/images/ WebP 资产。

Unit 04:Cloudflare Worker、D1、R2 API

这一单元完成了 Cloudflare 全栈形态的技术实现:

  • Worker 页面和 API
  • D1 数据库结构
  • 作品查询
  • 作品详情
  • R2 展示资源读取
  • 数据迁移脚本
  • API 单元测试

不过这个单元没有伪造生产绑定。D1、R2 和密钥仍然使用占位配置,真实生产授权被明确留到后面。

Unit 05:投稿和审核

这一单元让网站具备基本的内容运营能力:

  • 投稿页面
  • 管理审核台
  • 投稿草稿
  • 审核通过后发布
  • 管理员令牌校验
  • R2 图片上传
  • 浏览器端转 WebP
  • 图片压缩到 100 KB 以内
  • 审核前编辑标题、作者、模型、标签、提示词和参数
  • 删除无效图片资产

这里采取了一个很实际的策略:

投稿先进入草稿和审核记录,只有审核通过,作品才进入公开列表。

Unit 06:SEO、性能和基础安全

完成了:

  • Title
  • Description
  • Canonical
  • Open Graph
  • Twitter Card
  • 详情页动态元信息
  • /admin 和 /submit 的 noindex
  • 未知作品的 404 状态
  • R2 Key 命名空间限制
  • 上传大小限制
  • CORS 收紧
  • 构建期搜索索引
  • 图片 lazy、async 和 aspect-ratio 策略
  • 依赖版本锁定

Unit 07:账号、OAuth 和邀请码策略

这一单元曾经实现 GitHub OAuth、session、投稿额度和匿名邀请码。

但在后续安全策略调整中,账号投稿、OAuth 和 session 接口被关闭,当前正式策略改为:

  • 投稿必须使用管理员邀请码
  • 每个邀请码最多成功投稿 5 次
  • 普通用户不能绕过邀请码
  • 管理员令牌只放在本地环境变量中

这也是 Vibe Coding 中很重要的一点:功能做出来之后,还要敢于根据实际运营风险收缩功能,而不是为了“功能更多”强行保留。

Unit 08:生产安全基础

完成了:

  • 图片真实文件魔数校验
  • WebP、PNG、JPEG 类型识别
  • 上传内容类型校验
  • 客户端 IP 识别
  • 60 秒窗口限流
  • RATE_LIMIT KV binding
  • 管理员令牌轮换
  • 当前令牌和旧令牌过渡窗口
  • 邀请码并发递增回滚
  • 健康检查只暴露绑定状态,不泄露密钥

需要注意:KV 的 get + put 不是严格原子操作,因此它适合做边缘限流窗口,但邀请码的严格次数上限仍然由 D1 条件更新负责。

Unit 09:生产投稿链路和可观测性

本单元完成了本地可观测性:

  • x-request-id
  • 错误响应回显 requestId
  • 429 响应和 Retry-After
  • 结构化请求日志
  • 不记录 Authorization
  • 不记录邀请码
  • 不记录请求体
  • 不记录用户数据

但是由于当时没有正式生产 URL、D1/R2/KV 绑定确认和隔离邀请码,真实生产验收被明确标记为阻塞,没有用本地 mock 冒充生产证据。

Unit 10:严格生产审计契约

这一单元把真实并发验收标准固定下来:

  • 10 个请求必须严格得到 5 个 202
  • 另外 5 个必须得到 429
  • 每个 429 必须有正数 Retry-After
  • 失败请求不能残留 submission、prompt 或 R2 对象
  • 成功测试数据必须清理
  • Cloudflare 日志必须确认没有敏感字段

脚本在没有生产配置时会安全跳过,而不是伪造成功。

Unit 11:NAS Docker + Cloudflare Tunnel 上线

最终我选择了家庭 Debian NAS 自托管,而不是直接把生产服务放到 Cloudflare Worker。

实际架构如下:

Cloudflare DNS / HTTPS / Tunnel
                ↓
cloudflared
                ↓
web 容器 :4173
                ↓ /api/*
api 容器 :8787
                ↓
SQLite + 本地对象目录 + 本地 KV 文件

web 容器负责:

  • Vite 生产构建产物
  • 静态页面
  • /api/* 反向代理

api 容器负责:

  • Worker API 的本地 Node 运行实例
  • SQLite 数据库
  • 投稿和审核接口
  • 本地对象存储目录
  • 本地限流状态

Cloudflare Tunnel 将单域名:

mupicprom.amuspaces.top → http://web:4173

NAS 侧完成了:

  • Docker Compose 配置检查
  • web/api 镜像构建
  • 容器启动
  • 健康检查
  • API 重启恢复检查
  • SQLite 数据保留检查
  • 对象目录保留检查
  • 隔离投稿和清理测试
  • 公网访问确认

五、开发过程中遇到的主要问题

问题一:PowerShell 工具曾经启动失败

最开始终端工具报找不到:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

后来通过工具检查确认这个文件实际存在,原因是 Harness 终端进程的瞬时启动失败,而不是用户电脑没有 PowerShell。重试后终端恢复正常。

经验:不要看到 ENOENT 就立即断定项目环境缺少依赖,要区分“命令没有执行”和“命令执行后报错”。

问题二:React 类型缺失导致大量 JSX 报错

初始依赖没有安装:

  • @types/react
  • @types/react-dom
  • Vite CSS 类型声明

补充依赖并新增 vite-env.d.ts 后,npm run build 通过。

问题三:Vite 因原图和临时文件 EBUSY 崩溃

Vite 默认监听项目目录时,遇到以下文件会崩溃:

  • 图/ 下被其他程序占用的 PNG/JPG
  • Agent 编辑文件产生的 .tmpdir 临时目录
  • .AGENTS.md.

    <id>

    .tmpdir 这类动态临时目录

解决方式:

  • 明确把 图/ 视为外部原图源
  • 在 .gitignore 中忽略 图/
  • 在 Vite watcher 中排除 图/ 和 .tmpdir 路径
  • 每次重启后查看后台服务日志

问题四:Playwright 浏览器运行时不完整

Playwright 默认 headless shell 没有完整下载,浏览器验收时报 executable missing。后来改用已下载的 Chromium channel 完成验收。

经验:自动化测试不只是写测试代码,还要确认浏览器运行时本身存在,并把 CI 环境的浏览器安装写清楚。

问题五:不能把安全跳过当成生产通过

Unit 08、09、10 的生产脚本在缺少真实 URL 或凭据时会安全跳过并返回 0。

这个 0 只代表“脚本安全结束”,不代表:

  • 生产 D1 已验证
  • R2 已验证
  • KV 已验证
  • 并发限制已验证
  • 清理已验证
  • Cloudflare 日志已验证

这是非常容易被自动化流程误判的地方。

问题六:D1 mock 和真实 D1 行为不同

测试替身的 run().meta.changes 在旧版本中不存在。代码后来只在真实 D1 返回变化数时严格判定条件更新,生产前仍要求使用真实 SQLite/D1 做并发测试。

问题七:NAS 上 npm ci 失败

在 NAS 的 node:22-bookworm-slim 镜像中,npm ci 出现:

Exit handler never called

最后采取的方案是:

  • Windows 侧先完成 npm install 和生产构建
  • Docker 不在 NAS 内重新安装完整前端依赖
  • Docker 只打包 dist 和零依赖 Node 运行时
  • web/api 镜像成功构建并运行

这不是最理想的依赖构建方案,但它解决了 NAS 环境的实际兼容问题,并且没有把密钥或数据写进镜像。

六、Vibe Coding 的完整建站思路

第一步:先写产品,不先写代码

先明确:

  • 为谁服务
  • 用户来做什么
  • 核心价值是什么
  • 哪些功能暂时不做
  • 如何验证项目值得继续

第二步:建立业务地图

把所有功能按用户感知排序:

  1. 图片展示
  2. 图片和提示词绑定
  3. 结构化提示词
  4. 搜索筛选
  5. 详情分享
  6. 内容维护
  7. 收藏、投稿和社交

这张地图是防止 AI 乱加功能的依据。

第三步:先做骨架,再做核心

先让页面能看、能点、能走通,再接入真实内容和数据层。

但骨架必须被明确标注为预备代码,不能误把视觉原型当成产品完成。

第四步:每个单元只解决一个问题

一个好的开发单元应该能用一句话描述:

这一轮只负责把 Markdown 内容接入首页和详情页。

如果一句话里出现“顺便再加上登录、搜索、投稿、部署”,说明范围已经膨胀。

第五步:让 AGENTS.md 成为项目的长期记忆

每个单元必须把以下内容写回 AGENTS.md:

  • 做了什么
  • 为什么这样做
  • 验收结果
  • 没做什么
  • 哪些问题还没解决
  • 新踩了什么坑
  • 下一单元做什么

这样新 Agent 不需要重新猜项目背景,也不会重复踩相同的坑。

第六步:测试不是最后才做

每个单元都应先写验收标准,再写代码。

至少覆盖:

  • npm test
  • npm run build
  • 关键页面交互
  • 移动端布局
  • 图片加载
  • API 错误状态
  • 安全边界

第七步:生产环境不做假验收

没有真实绑定、真实 URL、真实隔离数据和清理权限时,就只能说“本地通过”或“脚本安全跳过”。

不能因为命令返回 0,就声称生产已经验收。

第八步:安全配置永远不写进仓库

以下内容只通过 NAS 环境变量、Cloudflare Secret 或部署环境注入:

  • 管理员令牌
  • 上传密钥
  • Tunnel Token
  • Cloudflare ID
  • 数据库密码
  • 邀请码

仓库只保存:

  • .env.example
  • .dev.vars.example
  • 占位绑定
  • 部署说明

七、为什么最后选择 NAS + Cloudflare Tunnel

纯 Cloudflare Worker 的方案是可行的,Unit 04 到 Unit 10 也完成了相应的 Worker、D1、R2、KV 和安全代码准备。

但实际部署时,我更看重:

  • 家庭 NAS 已经长期运行
  • 图片和数据库都在自己手里
  • 不需要先购买额外服务器
  • Docker 方便拆分 web 和 api
  • Cloudflare Tunnel 不需要暴露家庭公网 IP
  • 单域名 HTTPS 入口比较简单

最终形态是:

NAS 负责运行,Docker 负责隔离,Cloudflare Tunnel 负责穿透和公网入口。

这也说明架构不是信仰,而是根据实际资源和维护成本做选择。

八、最终上线后的维护注意事项

当前网站已经通过家庭 NAS 和 Cloudflare Tunnel 对外提供访问。

日常维护应遵循:

  • 更新前先备份 SQLite 和对象目录
  • 不使用 docker compose down -v
  • 不把 data、.env、.dev.vars 和 Tunnel 凭据提交 Git
  • 修改后重新执行 Docker Compose 配置检查
  • 检查 web 和 api 容器健康状态
  • 检查 /api/health
  • 检查公网 HTTPS 页面
  • 检查投稿后审核和清理链路

当前记录中明确选择了不配置自动备份和日志轮转,所以后续如果内容量增加,我会优先补充:

  • 自动备份
  • 备份保留策略
  • 日志轮转
  • 磁盘空间监控
  • R2 或异地对象备份

九、总结

这个项目真正让我学到的,不是某个框架 API,而是如何和 AI 一起做一个可维护的项目。

我最后形成的建站方式是:

产品五问
  ↓
业务地图
  ↓
架构取舍
  ↓
预备骨架
  ↓
独立开发单元
  ↓
测试和构建验收
  ↓
AGENTS.md 沉淀结果和踩坑
  ↓
生产环境隔离验证
  ↓
NAS Docker + Cloudflare Tunnel 上线

Vibe Coding 不是把一句“帮我做个网站”丢给 AI,然后等待奇迹。

真正有效的 Vibe Coding 是:

  • 我负责目标、判断和取舍
  • AI 负责实现、测试和重复劳动
  • 文档负责保存上下文
  • 单元负责控制范围
  • 测试负责证明结果
  • 生产验收负责区分“能运行”和“真的上线”

对我来说,这个网站不是一次性生成出来的,而是在不断拆分、验证、修正和记录中长出来的。

这就是我,阿木,用 Vibe Coding 建完一个 AI 生图提示词档案网站的全过程。

评论

AMU SYSTEM / BOOT