网站壳做出来只是第一步,真正麻烦的是让它变成用户自己的东西。
如果说 make-personal-site 和 copy-website-style 负责把网站壳做出来,那么内容落地层解决的是另一个问题,而且这个问题我觉得非常关键:
这个网站怎么变成“我的”?
这一层主要有两个 skill:
personalize-site
add-blog-posts
它们的分工很清楚,可以简单理解成:
personalize-site:替换身份、简介、项目、链接、metadata、demo 内容
add-blog-posts:导入文章、笔记、Markdown、Obsidian、Word、旧 HTML、图片和富内容
一个解决“这个人是谁”。
一个解决“这个人写了什么”。
这两件事情都要认真做。个人站如果身份还是 placeholder,或者文章导入之后格式全乱,那前面的设计再好也没什么意义。
1. 为什么内容层要单独拆出来?
很多 Agent 生成个人站的时候,会把内容和设计混在一起,这就很容易出问题。
比如它生成一个漂亮首页,同时随手写:
Hi, I'm Alex Chen, a product-minded full-stack engineer based in San Francisco.
如果用户确实叫 Alex Chen,那当然没问题。
但如果用户什么都没提供,这就是编造。
更糟的是,编造出来的内容往往看起来非常完整:
- 公司经历;
- 项目指标;
- 技术栈;
- 所在城市;
- 社交链接;
- 邮箱;
- 文章标题。
这些内容在截图里会让网站显得丰满,但真正交付时全部是风险。因为它看起来像真的,用户后面反而更难发现哪里是模型编出来的。
所以 Idea2Site 选择把内容落地拆成独立 skill。先做壳,再把用户真实材料放进去,这样更稳。
建站 skill 可以先放清晰 placeholder。
等用户提供资料之后,再由 personalize-site 去替换。
如果用户提供文章,再由 add-blog-posts 导入。
这样可以避免在生成阶段胡编内容,也能让内容修改更可控。
2. personalize-site:把模板变成用户自己的站
personalize-site 的目标是:
把一个已经生成的静态站,变成用户自己的个人网站,摆脱模板、demo 或复制来的参考站痕迹。
这一层的注意力放在身份和 metadata 上:名字、简介、链接、项目、头像、站点标题、社交信息,以及 demo / reference residue。
它要回答的问题很直接:这个站现在到底像不像用户自己的站。
2.1 输入材料
它可以利用很多种材料:
- 用户 bio;
- resume / CV;
- GitHub README;
- old homepage;
- social links;
- project list;
- avatar/logo;
- contact details;
- 用户明确给出的替换文本;
- 生成站里的
EDITING.md; profile.json、projects.json、assets/script.js里的数据块。
如果信息缺失,就用 placeholder、保留空位,或者直接省略。这个地方我觉得要非常克制。
比如用户没给邮箱,邮箱位置就保持 placeholder,或者明确标成待替换信息。
用户没给公司和学校,这些字段就留空。个人站最怕这种看起来专业、实际虚构的内容。
2.2 内容盘点
这个 skill 的第一步是盘点,改动放在后面。因为个人信息经常藏得很散,直接改首页很容易漏。
它要找出个人信息藏在哪里:
- HTML 标题;
- meta description;
- Open Graph / Twitter metadata;
- header/nav;
- hero;
- about section;
- contact block;
- footer;
- sidebar;
- project card;
- link page;
- article demo;
assets/script.js数据;- JSON 文件;
EDITING.md。
同时要搜索常见 placeholder:
your name
hello@example.com
example.com
lorem ipsum
sample post
sample project
TODO
replace me
这一点很重要。
因为现代静态站里内容不一定都在 HTML 里,有时候在 JS data object 里,有时候在 JSON 里,有时候同一个名字在 title、footer、OG metadata 出现了三遍。
如果只改首页可见文字,很容易漏掉 metadata 和分享卡片。
2.3 替换身份字段
常见需要替换的字段包括:
- site title;
- author name;
- display name;
- role;
- tagline;
- short bio;
- location;
- status;
- interests;
- email;
- GitHub / X / LinkedIn / Bluesky;
- RSS;
- resume/CV;
- avatar/logo/favicon;
- homepage hero copy;
- about page;
- footer;
- metadata。
这里的难点在于保持一致性。
比如站点 title 叫 Idea Lab,首页显示 Gyschuaner,metadata 还是 Your Name,footer 还是原参考站作者,那就不对。
personalize-site 要把浏览器可见内容和源码层 metadata 都一起处理。
2.4 清理源站身份
如果前一步是 copy-website-style,那这个 skill 还要负责清理参考站残留。
要去掉:
- 原作者名字;
- 原 handle;
- 原域名;
- 原邮箱;
- 原社交链接;
- analytics ID;
- 友链;
- 原文章目录;
- 原项目名;
- 原头像和封面;
- 原站统计或版权声明。
这一步的目的在于避免最后交付一个带着别人身份的网站。这在复刻参考站时尤其容易发生。
视觉结构可以借鉴,身份内容必须换掉。
2.5 内容适配,避免硬塞
我觉得 personalize-site 很重要的一点是,它会先适配用户材料,再放进页面。
不同风格的站,内容应该被不同方式表达。
比如:
- 极简站:一句话简介要短;
- 终端/档案风:可以写成 status/log/config;
- bento 风:内容要拆成短卡片;
- 研究主页:要正式、准确、重论文项目;
- blog/digital garden:要突出写作主题和标签;
- portfolio:要突出项目结果、技术栈和链接。
如果用户给了一大段 bio,更适合拆成首页短介绍、关于页长介绍、metadata description 几个层次。
更好的处理是:
首页放一句短 bio。
关于页放完整版本。
项目页放具体项目。
metadata 用更简短的描述。
这就是内容适配。
3. add-blog-posts:把文章自然接进现有站点
add-blog-posts 的目标是:
沿用现有博客系统,把用户文章加到现有静态站里。
它承担的工作超过通用 Markdown 转 HTML。真实文章里会有图片、公式、Obsidian 链接、旧 HTML、代码块、表格,一堆东西都要处理。
这是一个很重要的区别。
如果一个网站已经有自己的文章样式、索引结构、路径规则、图片目录和 tags 方式,那么导入文章时应该沿用现有系统,避免另起一套。
3.1 先理解现有 post system
导入文章之前,它要先检查当前站点怎么表示文章。这个步骤很重要,先看结构,再转 HTML:
posts/.html还是articles/.html?- 是否有
blog.html? - 是否有
archive/index.html? - 是否有 tag/category 页面?
- 是否有
data/posts.json? - 是否在
assets/script.js里写了 posts array? - 首页是否有 recent posts?
- 是否有 RSS、sitemap、search index?
- 图片放在
assets/images/posts/<slug>/还是和文章同目录? - 文章页 class 命名和排版是什么?
只有理解现有结构,才能把新文章放进去。不然很容易出现“文章页面有了,但是全站找不到入口”的情况。
否则就会出现一个常见问题:
新文章页面生成了,但 blog index 没更新。
或者 index 更新了,但路径指错。
或者路径对首页有效,对 posts/ 子页面无效。
3.2 元数据提取
每篇文章都需要元数据。
可能包括:
- title;
- date;
- slug;
- tags;
- category;
- summary;
- cover;
- source path;
- reading time。
这些信息优先从 frontmatter 里读。
frontmatter 缺失时,就从:
- 第一个 heading;
- 文件名;
- 文件夹名;
- 可见日期;
- 第一段摘要;
中推断。
但是推断要有依据。
比如原文没有日期,就省略日期,或者放 placeholder,取决于当前站点是否强制需要日期。
3.3 Markdown 和 Obsidian 有更多结构
很多用户的文章来自 Obsidian,这类文章看起来是 Markdown,但其实背后有一套自己的链接和附件习惯。
这类内容有普通 Markdown 之外的结构,里面可能有:
图片嵌入;!image.pngNote内部链接;Alias;- callout;
- task list;
- footnote;
- math;
- Excalidraw;
- Mermaid;
- 本地附件目录。
add-blog-posts 需要尽量保留这些结构。
比如 Obsidian wikilink 如果能解析到目标文章,就转换成正常 HTML 链接。
如果目标没导入,就保留成可见 placeholder 或文本,并报告 unresolved。
图片路径也要从本地绝对路径迁移出来:
D:\Obsidian\vault\attachments\image.png
更稳的做法是复制到站点资产目录,比如:
assets/images/posts/my-post/image.png
然后从文章页引用:
<img src="../assets/images/posts/my-post/image.png">
3.4 富内容处理
这个 skill 对富内容写得很细,因为真实博客文章最容易在这里翻车。
因为真实文章经常包含:
- 数学公式;
- 代码块;
- 表格;
- footnotes;
- callouts;
- citation;
- URL 列表;
- PDF、视频、音频、附件;
- Mermaid;
- SVG/Excalidraw。
处理原则是:
先看现有站点有没有支持。
有就沿用。
没有就用简单语义 HTML。
远程依赖要保持现有站点策略,有新增需求就显式说明。
比如数学公式。
如果站点已有 KaTeX 或 MathJax,就沿用。
如果文章公式很多,但站点没有 renderer,可以考虑加本地 KaTeX vendor,避免偷偷引用 CDN。
并且要保留原始 TeX fallback,防止渲染失败后内容完全不可读。
代码块要保留语言名,HTML 要 escape,长行用横向滚动处理,避免撑坏页面。
表格要变成 semantic table,宽表要有 responsive wrapper。
callout 要转换成站点已有的 note/tip/warning 样式。
这些细节决定了文章导入后像不像原站的一部分。
3.5 更新入口
文章生成出来只是第一步。它还要能从首页、文章列表、tag、archive 这些入口点进去。
还要更新入口。
可能包括:
- blog index;
- archive;
- tag/category;
- homepage recent posts;
- search data;
- RSS/Atom;
- sitemap。
生成范围也要克制。
如果站点没有 tag 系统,这篇文章就沿用现有索引结构。
如果首页展示最近三篇,导入后更新这三篇入口就够了。
它应该沿用现有站点策略。
3.6 batch strategy
对于一篇文章,直接完整导入。
对于小批量文章,如果结构清楚,可以一起导入。
对于大批量文章,先看结构,再决定怎么导入,这会省掉很多后面的修复成本。
应该先看:
- 文件数量;
- frontmatter 是否一致;
- 图片是否齐;
- 内链是否复杂;
- 公式和表格数量;
- 日期和 tag 是否可用;
- 是否有重复 slug;
- 是否有旧站身份和 analytics。
必要时先导入代表性子集。
这很符合 Agent 工作流的现实:大批量迁移时,先确认结构比直接开干更稳。
4. personalize-site 和 add-blog-posts 的关系
这两个 skill 很容易混淆,但其实边界很清楚。
personalize-site 负责站点身份:
我是谁?
我做什么?
我有哪些项目?
我有哪些链接?
metadata 怎么写?
模板残留清掉了吗?
add-blog-posts 负责文章系统:
这篇文章怎么变成页面?
图片放哪里?
公式怎么渲染?
链接怎么转换?
文章怎么出现在 blog index?
一个改 profile,一个改 content archive。
如果用户说:
把首页的名字、简介、GitHub 链接换成我的。
走 personalize-site。
如果用户说:
把这几篇 Obsidian 笔记加到博客。
走 add-blog-posts。
如果用户给了完整个人材料和文章,那就是:
personalize-site -> add-blog-posts -> review-static-site
顺序也有意义。
先让站点属于用户,再导入用户文章。
5. 内容层的常见 badcase
内容层最容易出的问题有这些。
5.1 残留 placeholder
页面看起来已经换了名字,但 footer 里还有:
Your Name
hello@example.com
sample project
5.2 metadata 没改
浏览器 title 还是模板名。
分享卡片还是参考站描述。
5.3 文章路径错
文章页在 posts/foo.html,但图片引用成:
<img src="assets/images/foo.png">
这会从 posts/assets/... 找,必然失败。
正确应该是:
<img src="../assets/images/posts/foo/image.png">
5.4 Obsidian 链接没处理
文章里残留:
[[另一篇笔记]]
![[image.png]]
直接发布的话,读者看不懂,图片也会显示异常。
5.5 数学公式被当作代码
公式文章如果只把 $...$ 放进 <pre>,看起来像源码,不像文章。
renderer 缺失时,也至少要说明限制,把数学渲染状态讲清楚。
5.6 旧站内容没清理
从旧 HTML 导入时,很容易把原站导航、评论区、analytics、广告、头像、友链一起带进来。
这些都应该清理,只保留文章主体和必要 metadata。
6. 小结
内容落地层解决的是个人网站最核心的问题:
这个站要成为用户自己的内容容器。
personalize-site 让它从模板变成用户身份的表达。
add-blog-posts 让它从静态页面变成可以持续写作的博客系统。
我觉得这两个 skill 的价值在于,它们把“内容”当成一等公民,而内容落地本身就是一个完整环节。
这也是个人博客和普通展示页最大的区别。
个人博客最终服务于长期阅读和内容积累。
它是给人长期存放和阅读内容的。