以下内容基本上是 AI 生成的,我还没校对,可能质量不高
语雀 Blog¶
A blog site using SvelteKit and UnoCSS.
用语雀当 CMS 的 SvelteKit 博客模板。写文章在语雀,自动同步到网站。
CNSeniorious000/yuque-blog muspimerol.site
No database needed
直接用语雀 API,SSR 渲染。
What It Does¶
把语雀的知识库变成博客。文章在语雀写,网站自动拉取渲染。支持 RSS feed、SEO、评论(Giscus)。
Tech Stack¶
- SvelteKit:SSR/SSG 框架
- UnoCSS:原子化 CSS
- Yuque API:拉取文档内容
- Cheerio + htmlparser2:HTML 解析和处理
- xsfetch:带重试的 HTTP 请求
Implementation¶
API Integration¶
src/lib/serverConstants.ts 定义了 API 基础 URL 和认证 token:
export const apiBaseurl = `${BASE_URL}/api/v2`;
export const headers = { "X-Auth-Token": ak };
SSR Handlers¶
src/routes/blog/+layout.server.ts 处理博客页面加载:
- 列表页:
listPosts()拉取文章列表 - 详情页:
getPost(slug)拉取单篇文章
用 Cheerio 解析 HTML,提取纯文本摘要,替换图片链接(NLark 代理)。
RSS Feed¶
src/routes/feed/+server.ts 生成 Atom feed:
- 遍历所有文章
- 解析 HTML 内容,清理语雀特有属性
- 输出标准 Atom XML
My Clever Bits¶
- NLark 图片代理:语雀图片用 CDN,但网站可能跨域。用
/nlark/路由代理请求,避免 CORS 问题 - AI Bot 识别:路由里检测 User-Agent,如果是 AI bot 就 302 重定向到
/llms.txt,为训练场景提供纯文本 - 渐进式加载:用 ISR(Incremental Static Regeneration),feed 每 5 分钟更新一次
- 多语言日期:
formatDate函数根据浏览器语言格式化日期,fallback 到中文
Source Dive¶
- src/lib/serverConstants.ts:API 配置
- src/routes/blog/+layout.server.ts:页面数据加载
- src/routes/feed/+server.ts:Feed 生成逻辑
- src/lib/utils.ts:工具函数
SEO 深入¶
ISR 缓存策略¶
export const config = { isr: { expiration: 300 } };
RSS feed 每 5 分钟更新,图片缓存 24 小时。
HTML 清理管道¶
语雀导出的 HTML 包含大量内联属性和特殊标签,用 Cheerio 清理:
const $ = load(parseDocument(body_html));
// 移除草稿特有的属性
for (const attribute of ["id", "class", "data-lake-index-type", "data-href"]) {
$(`[${attribute}]`).each((_, el) => $(el).removeAttr(attribute));
}
// 展开 span 标签(语雀用 span 包裹大量内容)
$("span").each(function () {
const content = $(this).html();
if (content) $(this).replaceWith(content);
});
AI Bot 重定向¶
检测 AI 爬虫,重定向到纯文本版本 /llms.txt:
const UA = UAParser(request.headers.get("user-agent") || "");
if (UA.bot?.name) {
throw redirect(302, "/llms.txt");
}
这为 LLM 训练场景提供干净的纯文本,减少 token 浪费。
多语言日期¶
同年文章不显示年份:
const options: Intl.DateTimeFormatOptions = {
month: "long",
day: "numeric",
year: date.getFullYear() === new Date().getFullYear() ? undefined : "numeric",
};
语言检测在 SSR 和 CSR 阶段不同。
// SSR: 从 HTTP 请求头获取
const language = headers.get("accept-language")?.split(",")[0];
// CSR: 从浏览器 API 获取
this.defaultIsChinese = Boolean(
browser ? navigator.languages.join().includes("zh") : acceptLanguage?.includes("zh"),
);
browser 来自 $app/environment,是 SvelteKit 提供的环境判断。