你现在看到的这个博客,是我用 Claude Code 从零搭起来的。
不是”借助 AI 辅助写了几行代码”那种程度,而是整个项目——从目录结构、设计系统、页面组件,到内容 Schema、分享功能、部署流程——都在和 Claude 的对话中一步步完成的。
这篇文章完整记录这个过程,包括用了什么工具、怎么开始的、遇到了哪些坑、怎么解决的。如果你也想搭一个自己的博客,或者想了解 AI 辅助开发到底是什么体验,希望这篇记录对你有用。
工具和技术栈
Claude Code + Frontend Design 插件
这次开发用了两层工具组合:
Claude Code 是 Anthropic 官方的 AI 编程工具。我用的是集成在 Claude.ai 网页界面里的版本(Claude Code on the web),它不只是聊天,而是真的可以操作项目文件。激活后,它可以:
- 读取、创建、修改本地文件
- 在终端执行命令(build、deploy、git 等)
- 在预览窗口实时查看页面效果
- 截图比对、指出页面里的具体视觉问题
Frontend Design 插件(插件 ID:frontend-design)是 Anthropic 官方出品的第一个 Claude Code 插件,目前已有 27 万+ 安装量。它专门解决 AI 生成前端代码”千篇一律”的问题——没有这个插件,Claude 倾向于生成功能正确但视觉平庸的界面(业内叫 “AI slop”)。
安装方式:在 Claude Code 对话框里运行 /plugin 命令,搜索 frontend-design 安装即可。也可以直接访问插件页面:claude.com/plugins/frontend-design,或查看 GitHub 上的源码:anthropics/claude-code/plugins/frontend-design。
这个插件的核心是一个约 400 token 的 SKILL.md 文件,里面写明了一套”不要做平庸设计”的原则:大胆的排版选择、独特的配色逻辑、有层次感的视觉节奏。结合后面说到的 DESIGN.md,这次博客的视觉效果远超我不装插件时 Claude 给出的初稿。
getdesign:一行命令生成设计规范
开发前的另一个关键准备是用 getdesign.md 生成设计规范文档。这是一个开源工具,GitHub 项目地址是 VoltAgent/awesome-design-md,收录了一批参照主流产品设计风格的 DESIGN.md 模板,包括 Claude/Anthropic、Vercel、Stripe、Apple、Figma、Spotify 等。
使用方法非常简单:
npx getdesign@latest add claude
一行命令,项目根目录下就会生成一个 DESIGN.md 文件,里面包含完整的设计系统规范:颜色变量、字体层级、间距规则、组件样式、响应式断点……全部是 AI 编程工具可以直接理解和使用的格式。
这份文档的核心内容:
- 基础色调:羊皮纸底色
#f5f4ed,暖米色为主,拒绝冷灰 - 品牌色:赤陶橙
#c96442,用于强调和插图 - 字体:中英文各一套,中文用系统字体栈,标题用细体拉开层次
- 间距:8px 为基准单位,强调留白和”编辑感”
- 组件规范:按钮、卡片、导航、表单的具体样式细则
有了这份文档,Claude Code 就有了明确的”参照系”。开发过程中只需要说”按设计规范来”,它就能保持风格一致,不需要每次重新描述颜色和字号。
完整技术栈
| 层级 | 工具 | 用途 |
|---|---|---|
| AI 编程 | Claude Code(前端插件) | 代码生成、文件操作、命令执行 |
| 设计规范 | getdesign.md | 生成 DESIGN.md 设计系统文档 |
| 前端框架 | Astro v6 | 静态网站生成,Content Collections |
| 类型系统 | TypeScript + Zod | Schema 定义和运行时校验 |
| 样式 | 原生 CSS + CSS 变量 | 设计 token 系统,支持暗色模式 |
| 内容格式 | Markdown | 文章、随笔、项目数据 |
| 图片生成 | Canvas API | 随笔分享卡片的动态绘制 |
| 分享功能 | Web Share API | iOS/Android 系统原生分享 |
| 包管理 | Node.js v25 + NVM | 运行时和版本管理 |
| 版本控制 | Git + GitHub | 代码托管,gh CLI 工具 |
| 部署 | Cloudflare Pages + Wrangler | 托管、CDN、自动部署 |
第一阶段:搭骨架
Claude Code 做的第一件事是初始化 Astro 项目、配置 TypeScript 和 Content Collections 的 Schema。
这个 Schema 很关键,它定义了每种内容类型的字段。比如文章有标题、日期、摘要、标签、来源(原创/转载);项目有标题、描述、标签、网址、App Store 链接、开发状态。
const projects = defineCollection({
schema: z.object({
title: z.string(),
date: z.coerce.date(),
desc: z.string(),
tags: z.array(z.string()).optional(),
url: z.string().optional(),
appstore: z.string().optional(),
status: z.enum(['在建', '迭代中', '完成', '归档']).default('完成'),
}),
});
Schema 用 Zod 定义,写起来很直观,Astro 会在构建时自动做类型检查。
然后是全局 CSS 变量。这一步把设计规范文档里的所有颜色、字号、间距都翻译成 CSS 变量,后续所有组件都引用这些变量,换主题改一处就全改了:
:root {
--color-bg: #f5f4ed;
--color-brand: #c96442;
--color-text: #2c2b26;
--color-text-secondary: #6b6860;
/* ... */
}
暗色模式也一起做了,用 [data-theme="dark"] 覆盖变量值,切换时只改根节点的属性,CSS 自动响应。
第二阶段:页面开发
骨架搭好后,依次开发了六个页面:首页、项目、文章、随笔、文章详情、关于我。
每个页面都遵循相同的结构——顶部 Hero 区(大标题 + 描述)、主内容区、底部 Footer——视觉上统一,但各有侧重。
导航栏的设计调整了几次。最初做的是首页 / 项目 / 关于我三项,后来觉得把内容类型放进导航更合适,改成了项目 / 文章 / 随笔三项,让访客第一眼就知道博客有什么。
中间遇到一个有意思的问题:导航在电脑浏览器 DevTools 的移动端模拟器里显示挤压、换行,但拿真手机打开完全正常。这是 DevTools 模拟和真实设备渲染差异导致的。最终根据真机效果微调了一下字号和间距,两端都合适。
文章页经历了一次比较大的改版。最初的设计把文章分成”技术”和”生活”两个栏目,展示时用双栏布局——这是很多博客常见的做法,但实际用起来感觉强迫自己给文章贴标签,不自然。
后来改成不分类,用标签 + 来源(原创/转载)来组织,单列展示。每篇文章是一个卡片行,标题、标签、来源、日期一眼都能看到。这种改法需要同时修改 Schema(去掉 category 字段)、列表页、详情页,Claude Code 处理多文件联动改动很顺手。
第三阶段:填充真实内容
占位内容换成真实内容,是这个阶段的主要工作。
项目部分:我有两个正在迭代的项目放到仓库里(私有仓库),用 gh 命令行工具直接拉取 README 和仓库信息:
gh api repos/chemark/jinly --json name,description
gh api repos/chemark/jinly/readme | base64 -d
项目卡片的访问按钮有个细节:锦历这个项目既有 Web 端又有 iOS App,所以卡片右上角需要两个独立的按钮——”↗ Web”和”↗ App Store”,而不是一个通用的”访问”。这个需求引发了 Schema 和页面模板的同步调整。
文章部分:从旧博客迁入了一篇原创文章《ChatGPT 眼中的星树浩》,还添加了一篇关于 GitHub Stacked PRs 功能的转载。旧博客的文章是 HTML 格式,需要手动转成 Markdown,同时去掉原来的样式代码,只保留内容结构。
迁入后发现文章各章节之间有很多 --- 分割线(从旧博客 <hr> 标签转来的),在新博客里显得多余——H2 标题本身就已经划分了章节,不需要额外的横线。清理这些细节,正文更干净了。
随笔部分:两条真实记录,写代码和产品的一些感想。
第四阶段:随笔分享功能
这是整个项目里最有意思的一个功能——给每条随笔加一个分享按钮,点击后直接调出 iOS 系统原生分享面板,内容是一张自动生成的图片卡片。
卡片生成
用 Canvas API 在浏览器里实时绘制图片。卡片设计和博客风格保持一致:
- 羊皮纸底色
#f5f4ed - 左侧品牌竖线(赤陶橙)
- 大号装饰引号(透明度很低,作为背景装饰)
- 正文文字
- 分割线
- 圆形头像 + 姓名 + 网址
一开始做成了固定尺寸(800×800),短随笔下面空出一大片白,很难看。后来写了一个 measureLines() 函数先测量文字行数,再动态计算画布高度:
function measureLines(text, maxW, font) {
const ctx = document.createElement('canvas').getContext('2d');
ctx.font = font;
let line = '', count = 1;
for (const ch of Array.from(text)) {
if (ch === '\n') { line = ''; count++; continue; }
const test = line + ch;
if (ctx.measureText(test).width > maxW && line.length > 0) {
line = ch; count++;
} else { line = test; }
}
return count;
}
这样三行的随笔和十行的随笔,生成的卡片高度不一样,都刚好合适。
分享流程
最初的交互是两步:先点”生成卡片”,预览后再点”下载图片”。用起来感觉多了一步,改成一步:
点击分享按钮 → 直接弹出系统分享面板(带图片)
用的是 Web Share API:
const blob = await generateCardBlob(text);
const file = new File([blob], '星树浩-随笔.png', { type: 'image/png' });
if (navigator.canShare?.({ files: [file] })) {
await navigator.share({ files: [file], title: '#星树浩随笔' });
} else {
downloadBlob(blob, `星树浩-随笔.png`);
}
桌面浏览器没有 navigator.share,走下载逻辑;iOS/Android 弹系统原生分享面板。
一个 HTTPS 的坑
在本地开发时,手机通过局域网 IP 访问开发服务器(HTTP),点分享按钮出现的是下载弹窗而不是分享面板。
原因是:navigator.canShare({ files }) 在 HTTP 环境下会返回 false(文件分享需要安全上下文,即 HTTPS)。代码里判断 canShare 返回 false,就走了下载分支。
上线到 hoshikihao.com(HTTPS)后,分享功能正常工作。这个问题在开发阶段测不出来,只能上线验证——也算是 Web API 安全机制带来的代价。
第五阶段:部署上线
旧博客迁移
原来的 hoshikihao.com 有一个用纯 HTML 写的旧博客,挂在 github.com/chemark/hoshiki-blog,通过 Cloudflare Pages 的 GitHub 集成自动部署。
迁移方案:
- 保留唯一有价值的文章,迁入新博客
- 把新博客代码强制推送到同一个 GitHub 仓库的 main 分支
- Cloudflare Pages 连接不变,域名不变
git remote add origin https://github.com/chemark/hoshiki-blog.git
git push origin branch-a:main --force
顺手把仓库里更老的 gh-pages 分支(一个用 Eleventy 写的更早期版本)也删掉了:
gh api -X DELETE repos/chemark/hoshiki-blog/git/refs/heads/gh-pages
Cloudflare Pages 构建配置
旧博客是纯静态 HTML,Cloudflare Pages 直接托管,不需要构建步骤。新博客是 Astro 项目,需要先跑 npm run build 生成 dist/。
CF Pages 的 GitHub 集成不知道这个变化,需要手动在控制台更新构建配置:
- 构建命令:
cd site && npm install && npm run build - 构建输出目录:
site/dist
更新前,hoshikihao.com 会部署 Astro 源文件(.astro、.md),访问者看到的是空白或乱码。更新后,每次推送 GitHub 自动触发完整构建。
在此之前,用 wrangler 命令直接部署预构建产物作为过渡:
wrangler pages deploy dist --project-name hoshiki-blog --branch=main
过程中遇到的其他问题
Edit 工具报错:Claude Code 的文件编辑工具要求先读取文件才能编辑。直接 Edit 会报 File has not been read yet,需要先 Read 再 Edit。这是工具设计上的安全限制,防止覆盖没看过的文件。
小红书图标:第一次用的 SVG 图标不是正式的小红书 Logo,视觉上不对。从 Simple Icons 的 GitHub 仓库拉取了官方 SVG:
curl https://raw.githubusercontent.com/simple-icons/simple-icons/develop/icons/xiaohongshu.svg
npm 不可用:开发机器上 Node.js 通过 NVM 管理,NVM 用的是”懒加载”模式(只有交互式 Shell 才初始化)。Claude Code 运行的是非交互式命令,所以直接调用 npm 会报 command not found。
解决方法是用完整路径调用:
~/.nvm/versions/node/v25.0.0/bin/npm run build
类似地,launch.json(开发服务器配置)里也要用绝对路径:
{
"runtimeExecutable": "/Users/xingshuhao/.nvm/versions/node/v25.0.0/bin/node",
"runtimeArgs": ["site/node_modules/.bin/astro", "dev", "--root", "site", "--host"]
}
Cloudflare Pages 部署报错:直接用 wrangler pages deploy 时,CF Pages API 要求 commit message 是合法的 UTF-8 字符串。本地 git commit 消息是中文,但 wrangler 没有正确传递,报 Invalid commit message。加上 --commit-message 参数手动指定一个英文消息就解决了。
git index.lock 冲突:某次 Claude Code 并发操作触发了两个 git 进程同时运行,留下了 .git/index.lock 锁文件,后续 git 命令全部报 fatal: unable to create index.lock。删除锁文件恢复正常:
rm .git/index.lock
一些体会
提示词就是思维的外化。 这句话是我在写随笔时写的,但在这次开发里也深有体会。跟 Claude Code 协作,你说清楚了,它就做得好;你自己都没想明白,再准确的工具也帮不了你。“把导航栏改一下”比不上”把导航栏的选项从首页/项目/关于我改成项目/文章/随笔,让访客第一眼就知道博客有什么内容”。
设计规范文档是协作的基础。 有了 DESIGN.md,每次说”按设计规范来”就能保持风格一致,不需要每次重新描述颜色、字号、间距。这相当于给 AI 协作者提供了一本”词典”——双方共用同一套语言,沟通效率高很多。
上线才是真正的测试。 本地开发服务器和真实生产环境有很多差异——HTTPS/HTTP、缓存策略、CDN 行为。Web Share API 这个功能,在本地测不出来,必须上线到 HTTPS 域名才能验证。这不是 AI 的问题,是 Web 开发本身的特性。
这个博客目前还在持续完善。导航里的”文章”和”随笔”会持续更新。如果你也在用 Claude Code 做项目,欢迎来聊。