零成本博客实战①:三个仓库、两条命令,全站不花一分钱

一句话结论:三个仓库(源码 / 发布 / 图床)+ 两条命令(git push、博客园同步),全站 0 成本,图片和评论都不用买服务。

写了十年 Java,笔记散在 CSDN、博客园、掘金、语雀里。最后发现最难的不是写,而是找回来——同一个知识点,四个平台各有一半。

于是决定自己搭一套:内容自己管、发布自动化、一分钱不花。这套方案已经稳定跑了几个月,每天写、每天发,没出过岔子。

一、为什么不留在大平台

方案 成本 数据归属 定制能力 迁移成本
博客园 / 掘金 / 知乎 免费 平台 几乎没有 高
CSDN 免费 平台 低 高
WordPress + 主机 ¥300+/年 自己 强 中
Notion / 语雀 公开页 免费 平台 弱 中
GitHub Pages + 静态站 ¥0 自己 强 低

这些平台我基本都用过一轮。最早在 CSDN 写,那会儿确实省事,发出去就有人看;后来广告越堆越多,正文被挤在中间,排版、外链、甚至导出都开始受限,写起来越来越像在给别人打工,最后整站搬走。

平台给的流量是真的,但内容被广告包围、被规则牵着走也是真的。这也是我最后决定自己搭的根本原因:内容的所有权和呈现方式,得握在自己手里。

静态博客的本质只有一句话:Markdown 文件 + 构建脚本 + 免费托管。GitHub 三样都能给。

代价也要说清楚:没有后台、没有数据库、评论要外挂、全文搜索得自己做。对技术博客来说,这些都不是硬伤。

二、整体架构:三个仓库各司其职

流程图 1

仓库 职责 谁在写
blog-build VuePress 源码、文章、站点配置 你
your-name.github.io 构建产物,Pages 托管 CI 自动推
blog-images 文章配图与封面 你

为什么不合成一个仓库:发布仓库会被 CI 反复覆盖写,和源码混在一起后 git log 会变成一锅粥;图床单独放,图片可以无限往历史里堆,不影响站点构建。

三、第一步:本地跑起来

# Node 20+,pnpm 9+
pnpm create vuepress-theme-hope my-blog
cd my-blog
pnpm install
pnpm docs:dev      # 本地预览 http://localhost:8080
pnpm docs:build    # 产出 src/.vuepress/dist

目录约定是关键:源码放 src/,构建产物进 src/.vuepress/dist,草稿放 draft/。

{
  "scripts": {
    "docs:dev": "vuepress-vite dev src",
    "docs:build": "vuepress-vite build src"
  }
}

draft/ 不参与构建,是这套方案最舒服的地方:草稿写多久都不会上线。想发的时候,把文件 git mv 到 src/ 对应分类,permalink 早就定好,链接永远不会变。

Windows 下如果 pnpm docs:build 报 ERR_PNPM_IGNORED_BUILDS,直接用 node_modules\.bin\vuepress-vite.cmd build src 绕过 pnpm 的构建脚本限制。

四、第二步:发布仓库与 CI

4.1 建发布仓库

新建一个名为 <用户名>.github.io 的公开仓库(比如 your-name/your-name.github.io),什么都不用放。Pages 默认从 main 分支根目录提供服务。

4.2 配一个最小权限的 PAT

CI 要往另一个仓库推代码,默认的 GITHUB_TOKEN 权限不够,必须用 Personal Access Token:

  1. Settings → Developer settings → Personal access tokens → Fine-grained tokens
  2. Repository access 只勾 your-name.github.io
  3. Permissions 只给 Contents: Read and write(别给 admin,也别给全仓库)
  4. 生成后,在 blog-build 仓库的 Secrets 里存成 PERSONAL_TOKEN

这里是最容易翻车的一步:secret 名字写错、token 过期、权限给少了,CI 都会在 deploy 阶段失败,而报错信息很含蓄。第一次跑 CI 一定要盯着日志。

4.3 workflow

name: deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with: { version: 9 }
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: pnpm }
      - run: pnpm install --frozen-lockfile
      - run: pnpm docs:build
      - uses: peaceiris/actions-gh-pages@v4
        with:
          personal_token: ${{ secrets.PERSONAL_TOKEN }}
          external_repository: your-name/your-name.github.io
          publish_branch: main
          publish_dir: src/.vuepress/dist

推一次 main,2~3 分钟后线上就更新了,整个过程不用碰服务器。

4.4 两个必做的细节

.nojekyll:GitHub Pages 默认跑 Jekyll,会忽略下划线开头的目录。VuePress 产物里有 _assets 这类路径,不加这个空文件会白屏。

touch src/.vuepress/public/.nojekyll

base 路径:项目页(/repo-name/)必须配 base: "/repo-name/";用户页保持 base: "/"。配错的典型症状是页面能开、样式全丢。

五、第三步:图床用 Git 仓库

用仓库当图床,好处是不花钱、不担心图床跑路、图片和文章一起版本化。

https://cdn.jsdelivr.net/gh/your-name/blog-images-1@main/blog/05.ai/03.rag/01/xxx.png

三条实践约定:

  1. 路径跟文章对齐:blog/{分类}/{序号}/xxx.png,日后一眼知道属于哪篇
  2. 先推图,再推文:图没上传就发文,线上就是一片 404
  3. 文件名语义化:git-reset.png 比 image-20261030-2213.png 有用得多

还有一条反例:别把图放进站点 public/。那会一起打进构建产物——我踩过,一张 6.8MB 的 GIF 直接把 dist 从 3.6MB 顶到 11MB。

5.1 仓库放不下了怎么办

一个仓库装几百张图很轻松,但长期写、图又多,迟早会到 1GB 量级。解法是把图床做成可横向扩展的编号序列:

blog-images-1    ← 前 N 年的图
blog-images-2    ← 满了开下一个
blog-images-3    ← …
blog-images-n

规则很简单:按年份或分类分库,路径结构保持一致,只换 raw URL 前缀。老图不用迁移,老链接永久有效,成本还是 0。

六、第四步:评论用 giscus

静态站没有后端,评论走 giscus,把 GitHub Discussions 当评论库——免费、不用备案、还能用 GitHub 登录。

comment: {
  provider: "Giscus",
  repo: "your-name/your-name.github.io",
  repoId: "R_xxxxxxxxxxxx",
  category: "Announcements",
  categoryId: "DIC_xxxxxxxxxxxx",
}

坑在于 categoryId:它是 Discussions 分类的 node id,不是分类名。在 giscus.app 填仓库后会自动带出来;如果自己瞎填,评论区会静默不渲染,控制台也不报错。

还要记得:发布仓库必须开启 Discussions,giscus 应用要装上并有权限。

七、第五步:导航与分类

两级分类够用:一级按知识域(Java / Spring / AI / Tools…),二级按主题。每个目录一个 README.md 作为落地页:

---
title: 工程工具
date: 2026-10-30
categories: [tools]
tags: [tools]
permalink: /pages/xxxxxx/
hidden: true
index: false
article: false
---

article: false 很关键——不加的话目录页会混进文章列表。

导航两处要一起改:

// navbar.ts —— 顶部
{ text: "Tools", icon: "mdi:toolbox", prefix: "/06.tools/", children: [...] }

// sidebar.ts —— 侧边栏,children: "structure" 自动跟随目录
{ text: "工程工具", prefix: "06.tools/", collapsible: true, children: "structure" }

主题会给每篇文章生成 permalink: /pages/xxxxxx/,真实页面都在这个路径下;原始目录路径(/06.tools/01.git/03-xxx.html)在静态产物里并不存在,直接访问会 404。

两种解法:

  1. 全站只用 permalink 做链接——简单,但分享出去的语义化链接就废了
  2. 生成跳转页——写个小插件,在 onGenerated 阶段按 filePathRelative 推导原始路径,生成带 meta refresh + canonical + noindex 的 HTML

我用第二种,50 来个跳转页,体积可以忽略。两种链接都能开,SEO 也只认 permalink。

九、成本清单:确实是 0

项目 免费额度 够不够用
GitHub Pages 公开仓库,1GB 站点、100GB/月流量 博客绰绰有余
GitHub Actions 公开仓库免费不限量;私有仓库 2000 分钟/月 一次构建 ~20 秒
图片存储 每个仓库约 1GB,可开 images-2 ~ n 横向扩 按年分库,几年都够
域名 <user>.github.io 免费 想自定义再买,¥60/年
评论 giscus(Discussions) 免费
搜索 主题内置 slimsearch 免费

唯一的隐性成本是构建时间:文章多了以后 vuepress build 会从 15 秒涨到 1 分钟,但跑在 CI 上,感知不到。

十、踩过的坑

  1. CI deploy 失败:八成是 PERSONAL_TOKEN 没配或权限不足,fine-grained token 一定要给目标仓库 Contents: write
  2. 文件编码:Windows 上搬来的 Markdown 可能是 GBK 或带 BOM,构建时 frontmatter 解析失败、中文乱码,发之前统一转 UTF-8 无 BOM
  3. 图片拖垮构建:大图直接进 public/ 会让 dist 膨胀数倍,所有文章配图走图床
  4. 评论区不显示:categoryId 填错、仓库没开 Discussions,两个原因占九成
  5. 改目录名导致老链接 404:permalink 写在 frontmatter 里,跟着文件走;先定 permalink,再动目录

十一、这套方案适合谁

适合:想长期写、在意内容所有权、愿意折腾一次配置的技术人。

不适合:需要后台编辑器、多人协作审核、复杂 SEO 运营的场景——那种情况老老实实上 WordPress 或付费 CMS。

配置一次之后,每天只需要:写 Markdown → 推图 → git push,剩下的交给 CI。


作者原创声明

posted @ 2026-10-31 00:52  amyaxy  阅读(8)  评论(0)    收藏  举报