网站壳做出来只是第一步,真正麻烦的是让它变成用户自己的东西。

如果说 make-personal-sitecopy-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.jsonprojects.jsonassets/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.png 图片嵌入;
  • Note 内部链接;
  • 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 的价值在于,它们把“内容”当成一等公民,而内容落地本身就是一个完整环节。

这也是个人博客和普通展示页最大的区别。

个人博客最终服务于长期阅读和内容积累。

它是给人长期存放和阅读内容的。