Wasp 博客的 AI 内容流水线:notion-to-blog Skill 如何把 Notion 页面转成可构建的 MDX 博客
2026/9/13 17:21:29 网站建设 项目流程

Wasp 博客的 AI 内容流水线:notion-to-blog Skill 如何把 Notion 页面转成可构建的 MDX 博客

【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp

这篇文章解析 Wasp 官方仓库中的notion-to-blogClaude Code Skill——一条"输入一个 Notion 页面 URL,输出一篇符合 Docusaurus 博客构建规范的 MDX 文章"的 Agent 自动化流水线。读完后,你将掌握该 Skill 的完整五步工作流(抓取、解析、图片优化、MDX 生成、人工确认)、Wasp 博客的 frontmatter 与截断标记约定,以及这些约定在 Docusaurus 配置 和 ImgWithCaption 组件 中的源码级落点。

Skill 定位:一条可被 Agent 直接执行的五步流水线

Skill 的完整定义位于 web/.claude/skills/notion-to-blog/SKILL.md,其 frontmatter 声明了三个元信息:

--- name: notion-to-blog description: Transfer a blog post from Notion to the Wasp blog. Fetches content, downloads and optimizes images, and creates a properly formatted MDX file. argument-hint: "[notion-page-url]" ---

argument-hint表明该 Skill 接受一个参数——Notion 页面 URL,输入输出边界非常清晰:输入是 Notion URL,输出是web/blog/目录下的一篇格式化 MDX 文章。这不是一个泛泛的提示词模板,而是一份对 Agent 的精确操作规程:每一步规定了要调用哪个 MCP 工具、写什么路径的文件、用什么命令行工具、以及产物必须满足什么格式约定。

从仓库结构看,这个 Skill 是 Wasp 博客内容生产链的一环。web/blog/CLAUDE.md 中定义的博客工作流为:草稿规划 → 撰写 → 审阅(含 GEO 检查) → 通过crosspostingSkill 分发到 DEV.to/Medium → 通过social-contentSkill 生成社媒内容。notion-to-blog覆盖的是"外部草稿(Notion)→ 本站 MDX"这一入口环节,其产物必须满足博客构建系统的全部硬约束,因此文档中大量条款实际上是构建系统要求的"翻译"。后文会逐一给出这些要求对应的源码证据。

Step 1:抓取 Notion 页面(注意分页)

第一步只有一行核心逻辑,但包含一个容易踩坑的细节:

  1. 从 URL 中提取页面 ID——末尾的带连字符 UUID(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx);
  2. 调用mcp__notion__API-retrieve-a-page获取页面元数据(标题、属性);
  3. 调用mcp__notion__API-get-block-children抓取全部内容块。Notion API 每页返回 100 个块,必须检查响应中的has_more字段,并用next_cursor继续翻页,直到取完。

这里的工程要点是第三点。博客全文的内容块数量很容易超过 100(正文段落、代码块、图片、列表项都算块),如果 Agent 只取第一页,产出的 MDX 会在文章中段"断尾"且没有任何报错。Skill 文档把分页检查写成显式步骤,就是为了让 Agent 把"是否取完"当作一个可验证的终止条件,而不是默认一次调用即完整。

Step 2:解析内容——边界识别与块类型到 Markdown 的完整映射

这一步分两个子问题:先确定正文边界,再做块级转换。

确定正文起止位置

Notion 页面里常常混杂 WIP/工作笔记,真正的博文并不占满全页。Skill 给出的启发式规则是:

  • 不确定时向用户确认正文的起止位置;
  • 正文通常从第一个heading_1块开始;
  • WIP 段落通常带有## WIP标题,或出现在文末附近的divider分隔符之后。

即"能用信号判断就判断,不能判断就问人"——这是该 Skill 设计中"人机协作"原则的体现,而不是让 Agent 擅自裁剪内容。

为什么必须用脚本解析

文档明确要求:用 Bash 运行 Python 脚本解析 JSON 块数据,因为数据可能非常大(50K+ token)。原因很直接:如果把整页块 JSON 读进模型上下文再让模型逐块"翻译"成 Markdown,不仅 token 成本极高,还会在长文本中丢失格式细节。把确定性的转换逻辑(块类型 → Markdown 语法)交给脚本,模型只负责需要判断力的部分(正文边界、图片命名、frontmatter 撰写),这是典型的"Agent 编排确定性工具"的模式。

块类型 → Markdown 映射表(完整继承自 Skill 文档)

脚本需按 Notion 块的type字段逐一转换:

Notion 块类型Markdown 输出
heading_1/heading_2/heading_3#/##/###
paragraph纯文本(保留行内格式)
bulleted_list_item-
numbered_list_item1.
code带语言标注的围栏代码块
image单独收集 URL 和 caption,不在正文原位输出
quote>
callout> {emoji}
divider---
table_of_contents跳过(Docusaurus 自动处理 TOC)

两个细节值得展开:

  • 富文本格式必须逐字段保留:粗体(**)、斜体(*)、行内代码(反引号)、删除线(~~)、链接(text)。Notion 的rich_text是数组结构,每个 run 带bold/italic/code/strikethrough布尔标志和可选的link对象,脚本需要对每个 run 做包裹式转换。
  • table_of_contents块被显式跳过,注释给出的理由是 Docusaurus 会自己渲染目录。这类"目标框架已具备的能力,源平台块就不搬运"的判断,避免了成文后出现双目录。

此外脚本要把所有图片 URL 与 caption 单独收集成一份清单,供 Step 3 的下载与 Step 4 的正文插图使用——图片不在转换阶段内联,这正是"下载-重命名-压缩-回填"三步流水线的前提。

Step 3:图片下载与 WebP 优化

图片处理被拆成五个动作,顺序固定:

  1. 创建目录:web/static/img/<post-slug>/;
  2. curl下载全部图片;
  3. 根据上下文/caption 给图片起描述性文件名;
  4. cwebp -q 85把全部图片转为 WebP;
  5. 删除转换前的原文件。

第 2 步有一句关键注释:Notion 的图片 URL 是临时签名的 S3 URL,必须立即下载。这类 URL 带有过期时间,若先完成全文转换再回头下载,链接可能已失效——所以"解析完立即下载"不是风格偏好,而是由 URL 的生命周期决定的硬约束。

第 3 步的"描述性命名"决定了 frontmatter 中 banner 图和正文<ImgWithCaption>source路径是否语义自明。以仓库中真实存在的一篇文章为例,web/static/img/buzz-wasp-one-message-app/目录下的文件即按prompt.webpcrm.webppodcast.webp这类与内容强相关的名字组织,与 Skill 描述的命名要求一致。

第 4 步的cwebp -q 85是质量 85 的有损压缩参数,配合 WebP 容器能显著降低博客静态站的图片体积;第 5 步删除原图则保证web/static/img/下不残留冗余文件。一个值得注意的下游联动:crossposting Skill 在把文章发到 Medium 时,专门有一段把.webp转回.jpg的逻辑,因为 Medium 不支持 WebP——本步骤的 WebP 化优化是针对本站静态站的,分发链路会按需回转。

Step 4:生成 MDX 文件——frontmatter 与内容规则

产物路径为web/blog/YYYY-MM-DD-<slug>.mdx,即"发布日期 + 短横线 slug"的文件名约定(仓库中如web/blog/2026-07-30-buzz-wasp-one-message-app.mdx均为此格式)。这一步的规范分两部分:frontmatter 字段约定,和正文内容规则。

frontmatter 完整约定(继承自 Skill 文档)

--- title: "Post Title" authors: [authorHandle] # from web/blog/authors.yml image: /img/<post-slug>/banner.webp tags: [wasp, tag2, tag3] # 3-6 lowercase tags keywords: ["keyword1", "keyword2", ...] # 10+ for SEO description: "SEO summary under 160 chars." ---

逐字段说明(可结合仓库佐证):

  • authors:取值必须是 web/blog/authors.yml 中已有的 handle,例如martinsosvinnysodic等。该文件为每位作者维护name/title/url/socials等信息,Docusaurus 据此渲染作者卡片;
  • image:指向 Step 3 生成的目录下的banner.webp,是文章的分享横幅图;
  • tags:3~6 个小写标签,首标签惯例为wasp;
  • keywords:10 个以上的长尾词,服务于 SEO/GEO 检索;
  • description:160 字符以内的 SEO 摘要。

web/blog/CLAUDE.md 的"Frontmatter"一节给出了完全一致的字段约定(另提及可选的last_update.date字段用于重大更新),说明 SKILL 文档并非孤立规范,而是全站博客规范的执行副本。

内容规则与源码级证据

SKILL 文档规定正文的四条规则,每一条都能在仓库中找到对应的构建/渲染系统证据:

规则一:frontmatter 之后必须import { ImgWithCaption } from './components/ImgWithCaption';,所有图片必须用该组件渲染:

<ImgWithCaption source="..." caption="..." alt="..." />

caption没有就省略该 prop;caption 内含双引号时,prop 值改用单引号:caption='text with "quotes" in it'

对应组件 web/blog/components/ImgWithCaption.tsx 的实现印证了这些细节:sourceuseBaseUrl解析(所以写/img/...站点路径即可),caption渲染为斜体、半透明的<figcaption>;组件还提供framed模式(带 Wasp 黄底黑框的展示型图片)及justifyContentmargin等布局 prop。外层figure-container类名把图片间距接入文档/博客的垂直节奏系统——这也是 Skill 要求"所有图片统一走该组件"而非裸<img>的原因:图片间距、居中对齐、字幕样式全部由这一处收口。

规则二:开头 hook 段落之后必须加{/* truncate */}注释(它控制博客列表页的文章摘要截断位置)。

这条规则的强制性有直接的构建配置证据:web/docusaurus.config.ts 中 blog 插件配置了:

blog: { id: "blog", path: "./blog", routeBasePath: "blog", // ... onUntruncatedBlogPosts: "throw", // ... }

onUntruncatedBlogPosts: "throw"意味着构建时若某篇文章缺少截断标记,Docusaurus 会直接抛错中断构建。所以{/* truncate */}不是排版建议,而是 CI 级别的硬校验——这正是它被写进 Skill 内容规则的第一位的原因。

规则三:所有指向 wasp.sh / wasp-lang.dev 的 URL 必须相对化。具体转换:https://wasp.sh/blog/.../blog/...,https://wasp.sh/docs/.../docs/...,https://wasp-lang.dev/docs/.../docs/...,且"适用于全文中任何位置的 markdown 链接或其他引用"。相对化后链接同时兼容wasp.shwasp-lang.dev两个域名的部署,并让 Docusaurus 的静态资源/路由解析正常生效。

规则四:外部 URL(GitHub、Twitter 等)保持原样——即相对化只针对本站域名,不可全局无差别改写。

Step 5:与用户确认(人工验收清单)

流水线最后一步是向用户汇报,内容包含四项:

  • 生成的文件路径;
  • 下载图片的数量以及 WebP 转换的总节省体积;
  • 过程中做的决策(标题取舍、被跳过的段落等);
  • 提醒用户检查 TL;DR 区块中的锚点链接

第四步提醒尤其有实践价值:Notion 中手写的 "TL;DR" 段落若包含指向文内小节的锚点链接,经过脚本转换后锚点文本可能已被改写(比如标题大小写或标点变化),锚点会失效——这是自动化转换链路中残留的"最后一类人为检查项",Skill 选择显式声明它而不是假装全自动。

小结:这条流水线的设计模式

回到 web/.claude/skills/notion-to-blog/SKILL.md 全文,它作为一份 Agent Skill 有几个可复用的设计特征:

  1. 每步都有可验证的完成条件(分页的has_more、图片保存成功、文件落盘、人工确认),Agent 可以自检推进;
  2. 确定性逻辑外置给脚本和 CLI(cwebp、Python 解析脚本),模型只做边界判断和文案撰写;
  3. 格式规范与构建系统对齐——frontmatter 字段、ImgWithCaption组件、{/* truncate */}截断标记分别对应 authors.yml、ImgWithCaption.tsx 和onUntruncatedBlogPosts: "throw"这三处仓库事实,规范不是凭空约定,而是构建产物的要求;
  4. 人机边界明确:正文起止不确定时问人,TL;DR 锚点留给人复查,其余全自动。

如果你的站点同样采用 Docusaurus 博客,这套"抓取 → 脚本化解析 → 资源落地 → 构建规范对齐的 MDX 生成 → 人工验收"结构,以及"用构建配置强制约定(如throw模式)而非口头规范"的思路,可以直接借鉴到自己的内容管线中。

【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询