全部文章

用 Obsidian 写博客:我的发布流水线

让写作回到 Obsidian,让发布交给一条命令。这套流水线解决了我三年来「笔记写得很爽、发博客很痛苦」的割裂感。

7 分钟 · 1007 字

我的知识库在 Obsidian 里,我的博客是静态站点。中间隔着一道鸿沟:手动复制粘贴。

复制的时候要处理图片路径、要改 frontmatter、要删掉内部链接。写一篇文章的兴奋感,往往就在这些琐事里消耗干净了。

所以我把这条流水线自动化了。核心思路只有一句话:

Obsidian 负责写作,博客仓库负责发布,脚本负责搬运。

目录结构

博客站点的内容目录就是唯一事实来源:

blog/
├─ src/
│  ├─ content/
│  │  └─ posts/          ← 文章都在这里(.md)
│  ├─ components/
│  └─ pages/
├─ public/               ← 图片、favicon
└─ scripts/
   └─ deploy.sh

Obsidian 直接把这个 posts/ 目录当成一个 vault(或者用软链接挂进主库),这样写的时候就有双链、图谱、模板——所有 Obsidian 的能力。

Frontmatter 模板

我给自己定了一个最小字段集:

---
title: 文章标题
description: 一句话摘要,会用在列表页和 SEO
pubDate: 2026-08-05
updatedDate: 2026-08-06      # 可选
tags: ['写作', '工具']
featured: false              # 是否上首页精选
draft: true                  # 草稿:不会出现在构建产物里
---

配合 Obsidian 的模板插件,新建文件时自动填充,draft: true 默认打开——防止半成品被误发布。

图片怎么处理

这是最容易出问题的部分。我的规则是:

  1. 图片放在 public/img/<文章slug>/ 下
  2. 正文里用绝对路径引用:![截图](/img/obsidian-pipeline/step1.png)
  3. 上传前用脚本压缩一遍
#!/bin/bash
# scripts/optimize-images.sh
find public/img -type f \( -name '*.png' -o -name '*.jpg' \) -newermt '-1 day' | while read -r f; do
  # 用 sharp-cli 或 ImageMagick 压缩,宽度限制 1600px
  npx sharp-cli -i "$f" -o "$f" resize 1600 --withoutEnlargement
done

不要在 Obsidian 里直接拖拽图片进文章,那样会生成 ![[Pasted image 20260805123456.png]] 这种 wikilink 格式,静态站点解析不了。

我的做法是在 Obsidian 设置里把「新建附件位置」指定为 public/img/当前笔记名,并把链接格式设为 Markdown 标准格式。

发布流水线

整个流程是一条命令:

#!/bin/bash
set -euo pipefail

echo "→ 检查草稿状态"
DRAFTS=$(grep -rl '^draft: true' src/content/posts/ || true)
[ -n "$DRAFTS" ] && echo "⚠️  以下文章仍是草稿:" && echo "$DRAFTS"

echo "→ 构建"
pnpm build

echo "→ 同步到服务器"
rsync -az --delete \
  --chmod=D755,F644 \
  dist/ aliyun:/var/www/blog/

echo "→ 重载 Caddy"
ssh aliyun 'caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy'

echo "✓ 发布完成"

几个要点:

  • --delete 一定要加,否则删掉的文章会残留在服务器上
  • --chmod=D755,F644 保证 Caddy(以非 root 用户运行)能读到文件
  • 先 validate 再 reload,配置写错时不会把线上服务搞挂
  • 本地构建,别在小内存服务器上跑 Node 构建

我为什么不直接在服务器上构建

有人会问:为什么不推送到 Git,让服务器拉下来构建?

因为我的服务器只有 1.6G 内存。跑一次 Astro/Vite 构建,内存会飙到 1G 以上,加上现有的服务,很容易触发 OOM。

本地构建 + 同步产物,对个人站点来说是最省心的方案。构建耗时 8 秒,产物同步 1 秒,总共不到 10 秒。

写作流程长什么样

现在的完整流程是:

  1. 在 Obsidian 里新建笔记(模板自动填 frontmatter,draft: true)
  2. 写,随便写,中途截图直接存到 public/img/
  3. 写完后把 draft 改成 false,补上 description
  4. 终端里敲 pnpm deploy
  5. 打开线上地址验收

整个过程不需要离开编辑器,不需要手动复制任何东西。

顺手加的一个 Python 小工具

发布前我还会跑一个脚本做体检,比肉眼扫一遍可靠。用 Python 写是因为处理字符串和文件遍历太顺手了:

#!/usr/bin/env python3
"""发布前体检:检查 frontmatter、图片引用、疑似泄漏的内部链接。"""
import re
import sys
from pathlib import Path

POSTS = Path("src/content/posts")
PUBLIC = Path("public")

FRONTMATTER = re.compile(r"\A---\n(.*?)\n---\n", re.S)
IMAGE = re.compile(r"!\[[^\]]*\]\((/[^)]+)\)")
WIKILINK = re.compile(r"\[\[[^\]]+\]\]")


def check(path: Path) -> list[str]:
    problems = []
    raw = path.read_text(encoding="utf-8")

    m = FRONTMATTER.match(raw)
    if not m:
        return [f"{path.name}: 缺少 frontmatter"]
    fm = m.group(1)

    for field in ("title", "description", "pubDate"):
        if not re.search(rf"^{field}:", fm, re.M):
            problems.append(f"{path.name}: frontmatter 缺少 {field}")
    if re.search(r"^draft:\s*true", fm, re.M):
        problems.append(f"{path.name}: 仍是草稿 (draft: true)")

    for rel in IMAGE.findall(raw):
        if not (PUBLIC / rel.lstrip("/")).exists():
            problems.append(f"{path.name}: 图片不存在 -> {rel}")

    for w in WIKILINK.findall(raw):
        problems.append(f"{path.name}: 残留 Obsidian 双链 {w}(静态站点解析不了)")

    return problems


def main() -> int:
    files = sorted(POSTS.glob("*.md"))
    if not files:
        print("没有找到文章")
        return 1

    all_problems = [p for f in files for p in check(f)]
    for p in all_problems:
        print(f"✗ {p}")

    if all_problems:
        print(f"\n共 {len(all_problems)} 个问题,已阻止发布")
        return 1

    print(f"✓ {len(files)} 篇文章检查通过")
    return 0


if __name__ == "__main__":
    sys.exit(main())

把它接到发布脚本最前面:

python3 scripts/preflight.py || exit 1     # 体检不过就不发布
pnpm build

那三条检查分别救过我一次:草稿忘了改、图片路径拼错、以及从 Obsidian 复制正文时带出来的 [[双链]]——最后这个如果漏了,线上就会出现原文照抄的方括号,很难看。

一点心得

降低发布摩擦,比提升写作技巧更能提高产出。

我统计过:在手动发布的那段时期,我平均每月写 1.2 篇;把流程自动化之后,稳定在每月 3–4 篇。内容质量没有下降,因为省下来的全是机械劳动。

如果你也想这么干,最小可行的版本只需要三样东西:

  1. 一个把 Markdown 变静态站点的生成器(Astro / Hugo / VitePress 都行)
  2. 一个 rsync 命令
  3. 一个把 draft: true 拦在外面的检查

复杂度可以慢慢加,但不要为了搭流水线而推迟写第一篇文章。

本文由 Kyne 撰写,采用 CC BY-NC-SA 4.0 许可,转载请注明出处。