跳转至

建站笔记-全站架构进阶篇

这是「进阶版」,假设你已经会 小白篇 → 里的最常见改法和本地预览。 这里讲引擎内部是怎么运作的:目录里每个文件干嘛用、内容怎么变成网页、CI 到底做了什么、怎么安全地自定义。


〇、三套引擎架构对比(一图速查)

维度 MkDocs(wiki / digest) Hugo(nav / works) Hexo(Civil_Blog)
内容模型 内容驱动:每篇 .md = 一页 数据驱动data/*.yaml + 模板 内容驱动source/_posts/*.md = 文章
入口配置 mkdocs.yml(含 nav config.toml + layouts/ _config.yml + themes/butterfly
内容放哪 docs/ data/ + layouts/ source/_posts/
构建命令 mkdocs build hugo hexo g
本地服务 mkdocs serve(:8000) hugo server(:1313) hexo s(:4000)
产物目录 site/ public/ public/
部署命令 mkdocs gh-deploy --force 或 CI CI(actions-gh-pages) hexo d
上线分支 gh-pages(项目页) gh-pages(项目页) main(用户页)
自定义点 overrides/ + extra.css + 插件 layouts/ + static/ 主题 _config + 主题覆盖

一、MkDocs 架构深潜(civil_wiki + civil_digest)

MkDocs 是最典型的"内容驱动":你写 Markdown,它在构建时按 mkdocs.yml 把文章织成站点。

1.1 目录解剖

civil_digest(结构最简)为例:

civil_digest/
├─ mkdocs.yml                 # 全局配置 + 导航(nav) + 插件 + 主题
├─ requirements.txt           # Python 依赖(mkdocs-material 等)
├─ docs/
│  ├─ index.md                # 首页(对应 nav 里的「我的书摘」)
│  ├─ CNAME                   # 自定义域名(构建时原样拷到 site 根)
│  └─ stylesheets/
│     └─ extra.css            # 自定义样式(在 mkdocs.yml 的 extra_css 引用)
├─ overrides/
│  └─ main.html               # 主题覆盖(Jinja2,extends base.html)
└─ .github/workflows/ci.yml   # 推送 main 时自动 mkdocs gh-deploy --force

civil_wiki 是同一个套路,但体量大得多docs/ 下按"工作与项目 / 硬件与半导体 / 阅读与学习 …"分了多层文件夹,mkdocs.ymlnav 是一棵很深的树,插件也更多(roamlinks、git-revision-date、tags、minify、sitemap hook)。

1.2 内容驱动模型

  • 每个 docs/**/*.md 会被渲染成一个页面,URL 由相对 docs/ 的路径决定(如 docs/硬件与半导体/xxx.md/硬件与半导体/xxx/)。
  • nav: 决定左侧菜单结构页面标题顺序。nav 里写的路径必须真实存在,否则构建会警告甚至失败。
  • front-matter(--- 之间的 title: 等)控制单页元信息。

1.3 构建与服务原理

  • mkdocs build:读 mkdocs.yml → 扫描 docs/ → 套用 Material 主题 → 输出到 site/
  • mkdocs serve = build + 起一个本地 HTTP 服务(:8000)+ 监听文件变化自动重建。所以它既能看效果,也是本地开发服务器。
  • 增删页面后,记得同步 nav:;否则页面能访问,但不会出现在菜单里。

1.4 wiki 与 digest 的差异(同一引擎两种规模)

civil_wiki civil_digest
nav 深树(多分区多层级) 仅首页
插件 search/roamlinks/git-revision-date/tags/minify + sitemap hook 仅 minify
自定义 overrides/main.html(OO图卡片)+ extra.css(本地中文字体) extra.css
适合 大知识库 轻量书摘

1.5 自定义方式

  • 改样式:在 docs/stylesheets/extra.css 写 CSS,并在 mkdocs.ymlextra_css 引用(wiki 用它换了中文字体、给图片加描边)。
  • <head> / 注入元信息:用 overrides/main.html(Jinja2,{% extends "base.html" %} + {% block extrahead %})。wiki 用它加了 Open Graph 社交卡片和字体 preload。
  • 加功能:安装插件写进 plugins:(如 tags 自动聚合标签页)。

1.6 部署内部

ci.yml 关键两步:pip install -r requirements.txtmkdocs gh-deploy --forcemkdocs gh-deploy 本质是把 site/ 推到仓库的 gh-pages 分支;--force 覆盖旧产物。GitHub Pages 的 Source 设为 gh-pages,于是访问 digest.hanvon.top 看到的就是它。 CNAME 必须放 docs/CNAME(不是仓库根),因为 gh-deploy 只拷贝 docs/ 下内容到 site/ 根。

1.7 常见坑

  • nav 指向了不存在的文件 → 构建警告/失败。
  • 自定义域名写在 docs/CNAME,写错地方(仓库根)不会生效。
  • 改了 docs/ 但没动 nav,页面存在却不在菜单里。
  • 本地 mkdocs serve 正常,但忘了 git push → 线上不变。

二、Hugo 架构深潜(civil_nav + civil_works)

Hugo 是数据驱动 + 模板驱动:内容不在 content/ 的一篇篇 md 里,而在 data/*.yaml,由 layouts/ 的 Go 模板"填"出来。

2.1 目录解剖

civil_works/
├─ config.toml                 # 站点标题、disableKinds、params
├─ data/
│  └─ projects.yaml            # ★ 作品数据(你主要改这里)
├─ layouts/
│  └─ index.html               # ★ 整页模板(Go template,无主题)
├─ static/
│  ├─ assets/                  # css/js(构建时拷到站点根)
│  ├─ images/                  # 作品封面图
│  └─ favicon.svg              # 站点图标
├─ content/
│  └─ _index.md                # 首页桩文件(Hugo 需要它来渲染首页)
├─ .github/workflows/ci.yml    # actions-hugo 构建 + actions-gh-pages 部署
└─ static/CNAME                # 自定义域名(Hugo 原样拷到 public 根)

civil_nav 结构完全一样,只是 data/links.yaml(导航数据)和 layouts/index.html(导航模板)不同——nav 的模板更复杂,复刻了 Webstack 风格的侧边栏 + 卡片网格,并引用 static/assets/ 下下载自参考站的 Bootstrap/xenon CSS 与 JS。

2.2 数据驱动模型

  • data/projects.yaml 里是一个结构化列表;layouts/index.html 用 Go 模板遍历它:
    {{ range .Site.Data.projects.items }}
      <div class="content">
        <h4>{{ .name }}</h4>
        <p>{{ .desc }}</p>
      </div>
    {{ end }}
    
  • 只改 data/*.yaml,模板和样式都不用动。这就是"数据驱动"的好处:内容与表现分离。
  • config.tomldisableKinds = ["taxonomy","term","RSS"] 关掉 Hugo 默认的分类/标签/RSS 页——本站只渲染首页筛选网格,不需要它们(不关的话会刷 WARN)。

2.3 Go 模板速记

写法 含义
{{ .Site.Data.projects.items }} 取 data/projects.yaml 的 items 列表
{{ range . }}{{ .name }}{{ end }} 遍历列表,取每项 name
{{ with .image }}…{{ end }} 进入后 . 变成 image 的值(见 2.6 坑)
{{ if .image }}…{{ .name }}…{{ end }} 条件判断,. 仍是当前 item
{{ delimit .categories " " }} 把 categories 数组拼成空格分隔字符串

2.4 nav 与 works 的布局差异

  • navlayouts/index.html 是 Webstack 风格——左侧固定深色 sidebar(站点名 + GitHub 链接)+ 右侧按 sections 分区的卡片网格(.xe-widget.xe-conversations.box2 白卡 + 左侧蓝条)。CSS/JS 来自 static/assets/(xenon/bootstrap/fontawesome/Arimo),最后加载的 nav.css 负责卡片悬浮效果。
  • workslayouts/index.html 是轻量过滤网格——顶部 #myBtnContainer 筛选按钮(Show all + 各分类)+ .row.column {{ delimit .categories " " }} + .content(封面图/标题/描述/Website+Repo 按钮组)。样式用参考站真实的 static/assets/index.css,交互用 static/assets/index.jsfilterSelection()

2.5 构建与服务原理

  • hugo:读 config.toml + data/ + layouts/ + static/ → 输出 public/hugo --minify 顺带压缩)。
  • hugo server:构建 + 本地服务(:1313)+ 热更新。
  • static/ 下的一切(css/js/img/字体/CNAME)会被原样拷贝到 public/,所以模板里引用 /assets/index.css 即可。

2.6 真实调试案例:image 字段导致的构建崩溃

我曾写:

{{ with .image }}<img src="{{ . }}" alt="{{ .name }}">{{ end }}
当某作品没有 image 时一切正常;一旦加了 image:with 把上下文 . 变成了图片字符串,再取 .name 就报错:can't evaluate field name in type string修复:把 with 改成 if——if 不切换上下文,. 仍是当前 item:
{{ if .image }}<img src="{{ .image }}" alt="{{ .name }}" style="width:100%">{{ end }}
教训:在模板里要取"其他字段"时,用 if 而不是 with(除非你明确只想用那个字段本身)。

2.7 常见坑

  • 忘了 content/_index.md → 首页不渲染。
  • baseURL 写错 → 资源 404(本地看不出,上线才暴)。
  • static/CNAME 漏了 → 自定义域名失效(Hugo 的 CNAME 在 static/,不是 docs/)。
  • 模板里 with 误用导致字段取不到(见 2.6)。
  • FontAwesome 用 CDN 时离线环境图标消失(可改本地化)。

三、Hexo 架构深潜(Civil_Blog)

Hexo 是专为博客设计的内容驱动引擎,按时间组织文章。

3.1 目录解剖

Civil_Blog/
├─ _config.yml                 # 站点信息 + theme + deploy + url
├─ package.json                # Node 依赖(hexo / hexo-deployer-git / 主题依赖)
├─ source/
│  ├─ _posts/                  # ★ 博客文章(每篇一个 .md,带 front-matter)
│  └─ CNAME                    # 自定义域名(blog.hanvon.top)
├─ themes/
│  └─ butterfly/               # 主题(不要直接改这里,用主题自带的 _config 覆盖)
├─ scaffolds/                  # 新建文章/页面的模板
└─ (无 .github/workflows)     # 手动 hexo d 部署,没有 CI

3.2 内容模型

  • source/_posts/年份-月份-日期-标题.md 每篇 = 一篇文章。
  • front-matter 至少要有 title / date / tags
    ---
    title: 我的文章
    date: 2026-07-20 12:00:00
    tags: [随笔]
    ---
    
  • Hexo 据此生成首页时间线、归档页、标签页、分类页

3.3 命令链(clean → g → s → d)

命令 作用
hexo clean 删除 db.jsonpublic/(清缓存,改了配置或主题后必跑)
hexo g (generate) 构建,输出 public/
hexo s (server) 本地服务 http://localhost:4000(热更新)
hexo d (deploy) 执行 _config.yml 里的 deploypublic/ 推到目标

日常开发:hexo clean && hexo g && hexo s 本地看;发布:hexo d

3.4 部署内部(为什么是 main

_config.yml 里:

deploy:
  type: git
  repo: git@github.com:wild-civil/wild-civil.github.io.git
  branch: main
hexo dhexo-deployer-git,把 public/ 推到 wild-civil.github.iomain 分支根目录。而该仓库名恰好是 <用户名>.github.io——属于用户页,GitHub 默认从 main 根 serve。所以博客"就是 main",和前面项目页走 gh-pages 不同(详见 分支区别 →)。

3.5 常见坑

  • 第一次要用前必须 npm install(装 Node 依赖)。
  • 改了主题配置要 hexo cleanhexo g,否则旧缓存作祟。
  • 不要直接改 themes/butterfly/ 源码,升级主题会丢;用主题提供的覆盖机制。
  • node 版本不兼容可能导致 hexo 命令报错,按主题要求选 LTS 版本。

四、统一的"静态服务"方式与 file:// 之坑

三个引擎的产物(site/ / public/都不能用 file:// 双击打开:页面引用资源用的是绝对路径(如 /assets/xxx.css/js/yyy.js),file:// 协议下根路径不对,会样式全崩、JS 全挂。

正确"纯静态看产物"的方式(不依赖引擎热更新):

引擎 构建 起静态服务
MkDocs mkdocs build(出 site/ cd site && python -m http.server 8000
Hugo hugo --minify(出 public/ cd public && python -m http.server 1313
Hexo hexo g(出 public/ cd public && python -m http.server 4000

但日常开发更推荐直接用引擎自带的 serve/servermkdocs serve / hugo server / hexo s),它们带热更新,省去手动起 HTTP 服务。


五、部署管线一句话总结

  • MkDocs(wiki/digest)git push → GitHub Actions 跑 mkdocs gh-deploy --force → 推 gh-pages → Pages 显示。
  • Hugo(nav/works)git push → GitHub Actions 跑 hugo --minify + actions-gh-pages → 推 gh-pages → Pages 显示。
  • Hexo(博客)hexo dhexo-deployer-gitmain → 用户页直接 serve。

六、交叉阅读