为什么要自己写主题?
用 Hexo 搭个人站时,现成主题很多:Landscape、NexT、Butterfly……它们功能齐全,却也往往太重——开关、插件、侧栏、评论、动画一层叠一层。
我更想要的是:
- 像作品集一样干净的首页
- 文章列表一眼扫完
- 深浅色切换不闪屏
- 部署到 GitHub Pages 足够轻量
所以我做了 Folio——当前 oksite 正在使用的自定义主题。这篇文章会从「Hexo 主题是什么」讲起,再拆开 Folio 的实现,最后补上 GitHub Pages 的构建与发布流程。
Hexo 主题到底是什么?
Hexo 把内容和皮肤分开:
| 部分 | 职责 |
|---|---|
source/_posts/*.md |
文章正文与 front-matter |
站点根目录 _config.yml |
站点信息、permalink、部署、启用哪个主题 |
themes/<name>/ |
模板、样式、脚本、主题级配置 |
生成站点时,Hexo 大致做三件事:
- 读 Markdown,渲染成 HTML 片段
- 把片段塞进主题的 EJS 布局
- 拷贝
themes/*/source/与站点source/里的静态资源到public/
因此,自定义主题 = 约定好的目录结构 + 几套布局模板 + 一套 CSS/JS。不需要改 Hexo 内核。
主题最小骨架
1 | themes/folio/ |
在站点 _config.yml 里写一行即可启用:
1 | theme: folio |
Folio 设计目标
Folio 名字来自 portfolio / folio:更像独立开发者的作品夹,而不是功能堆叠的 CMS 皮肤。
设计原则很简单:
- 少页面、强信息架构:首页 Hero + 最近文章;归档按年分组;文章页窄栏阅读
- 配置驱动:名字、简介、菜单、页脚文案都写在主题
_config.yml - CSS 变量管主题色:亮/暗模式只换 token,不写两套布局
- 零构建依赖主题侧:单文件 CSS + 十几行原生 JS,不绑 React/Vite
- 可访问与移动端:语义标签、ARIA、窄屏汉堡菜单、
prefers-reduced-motion
强调色是 #d94f32,品牌标记是「大圆圈 + 中心小圆点」——克制,但有辨识度。
Folio 实现了什么
1. 配置驱动的个人主页
主题配置(themes/folio/_config.yml)大致如下:
1 | profile: |
首页 index.ejs 直接读取 theme.profile.*:
- 左侧:HELLO 文案、姓名、角色、简介、CTA
- 右侧:
identity卡片——姓名前两字大写 + 网格背景 + 旋转方框装饰
这样换站点时,几乎不用改模板,只改 YAML。
2. 页面类型齐全,模板保持薄
| 模板 | 作用 |
|---|---|
layout.ejs |
文档外壳,挂 head / header / footer / site.js |
index.ejs |
Hero + 最近文章列表 |
post.ejs |
标题、日期、估算阅读时长、正文、标签、上下篇 |
page.ejs |
关于等独立页 |
archive.ejs |
全部文章,按年份插入小标题 |
tag.ejs / category.ejs |
复用 archive 布局 |
列表项抽成 _partials/post-card.ejs:日期、标题、摘要、标签、箭头入口,首页和归档共用,避免复制粘贴。
文章页有一个小巧但实用的细节——阅读时长估算:
1 | <%= Math.max(1, Math.ceil(strip_html(page.content).length / 400)) %> 分钟阅读 |
按纯文本字数粗算,不引入额外插件。
3. 导航、激活态与品牌标
header.ejs 根据 theme.menu 循环生成链接,并用 page.path 判断当前页:
1 | <% Object.entries(theme.menu).forEach(([label, path]) => { %> |
路径一律走 url_for(),这样以后若把站点挂在子路径(root: /blog/)也不会硬编码炸链。
品牌区是内联 SVG,而不是外链图片——少一次请求,也方便用 CSS 变量上色:
1 | <svg viewBox="0 0 24 24" width="34" height="34"> |
外圈描边、内点填充,颜色都指向 --accent。
4. 亮 / 暗主题:系统偏好 + 手动切换 + 防闪屏
CSS 用 token 定义两套语义色:
1 | :root { |
优先级大致是:
- 用户点切换 →
document.documentElement.dataset.theme = 'dark' | 'light' - 写入
localStorage - 未手动选择时,跟随
prefers-color-scheme - 在
head里同步读 localStorage,避免首屏先亮后暗:
1 | <script> |
交互逻辑集中在 source/js/site.js:主题按钮 + 移动端菜单展开,总共不到 20 行。
5. 排版与响应式
- 全局
shell限宽(约 1120px),文章页用narrow更适合阅读 - 首页双栏 Hero,小屏改单列
- sticky 顶栏 +
backdrop-filter毛玻璃 - 文章正文衬线字体,引用条用强调色左边线
@media (max-width: 760px)收起导航为汉堡菜单prefers-reduced-motion时关掉过渡,照顾晕动敏感用户
样式刻意做成单文件、无预处理器依赖(主题侧不强制 Stylus 流水线),方便改、方便部署。
6. SEO 与基础元信息
_partials/head.ejs 处理:
title拼接:文章标题 / 站点名description回退链:页面描述 → 站点描述 → profile biocanonical使用full_url_forviewport/theme-color
没有上复杂的 SEO 插件,但对个人站已经够用。
目录与数据流(串起来看)
整站生成可以看成五步:
- Markdown 文章:写在
source/_posts/ - hexo generate:启动构建
- generators:由 index / archive / tag / category 等生成器产出页面数据
- EJS 模板:套用
themes/folio/layout/*,模板内读取config、theme、page、site - 发布产物:输出到
public/(HTML + css / js / images),再经hexo-deployer-git推到 GitHub Pages
模板里常用的数据角色:
| 对象 | 来源 | 典型用途 |
|---|---|---|
config |
站点 _config.yml |
url、title、theme… |
theme |
主题 themes/folio/_config.yml |
profile、menu、footer… |
page |
当前页数据 | 标题、正文、日期、标签… |
site.posts |
全站文章集合 | 列表、归档、统计 |
写主题时优先用这些对象,以及 url_for / partial / paginator,不要自己硬拼相对路径。
从仓库到 GitHub Pages
本站使用的是经典路径:本地 Hexo 生成 → git 推送到 Pages 仓库。
1. 站点配置要点
1 | # _config.yml(节选) |
依赖里需要:
hexo+ 各 generatorhexo-renderer-ejs/hexo-renderer-markedhexo-deployer-git(一键部署)
package.json 脚本:
1 | { |
2. 本地开发
1 | npm install |
改 themes/folio 下的 EJS/CSS/JS,保存后刷新即可。写文章放在 source/_posts/,front-matter 指定 layout: post、标题、分类与标签。
3. 构建与发布
1 | npx hexo clean |
hexo-deployer-git 会把 public/ 内容提交到目标仓库的指定分支。若仓库开启 GitHub Pages 并指向该分支,几分钟后站点更新。
用户站
oksite.github.io与自定义域名oksite.net是常见组合:仓库提供托管,域名在 DNS 做 CNAME/A 记录即可。
4. 可选:GitHub Actions 自动构建
当前仓库也可以改成「源码进 main,CI 负责 generate 并推送到 Pages」。思路是:
- 源码仓保留 Hexo 项目(含
themes/folio) - Workflow 里
npm ci→npx hexo generate - 用
actions/upload-pages-artifact+actions/deploy-pages发布
好处是换电脑也能发文章;Folio 本身无特殊构建步骤,非常适合 CI。
自己做一个 Folio 式主题时,可以怎么起步
若你也想从零做一个「够用、好改」的主题,建议顺序:
- 先定信息架构:首页 / 文章 / 归档 / 关于,别一上来加搜索评论
- 搭 layout 壳:header + main + footer,确认
url_for静态资源能加载 - 做 post 与 index:列表卡片抽 partial
- 用 CSS 变量定品牌色,再补 dark mode
- 补 archive / page,tag、category 先复用 archive
- 最后才加动画、评论、阅读统计
Folio 刻意停在第 4~5 步:功能闭环,代码量可控,以后要加东西也不费劲。
和「套一个现成主题」比,多出来的价值
| 现成主题 | Folio 这类自研主题 | |
|---|---|---|
| 上手速度 | 快 | 需要写模板 |
| 定制成本 | 覆盖样式容易「打补丁」 | 改结构即改源码 |
| 体积与性能 | 常带大量开关与依赖 | 可压到极简 |
| 品牌一致 | 容易和别人撞脸 | 布局即个人风格 |
| 可维护性 | 升级主题可能冲突 | 完全自己掌握 |
对我来说,博客本身就是产品的一部分。NOTEYOU 用本地优先讲工具观,Folio 则用页面结构讲审美与取舍——少而准,胜过大而全。
小结
- Hexo 自定义主题本质是:布局模板 + 主题配置 + 静态资源
- Folio 围绕独立开发者站点场景,实现了:配置驱动主页、薄模板多页面、阅读时长、亮暗色与防闪屏、响应式导航、Git 一键部署友好
- GitHub Pages 侧:
hexo generate产出静态文件,再经hexo-deployer-git或 Actions 发布即可
如果你也在搭个人站,不妨先问自己一句:首页打开的三秒内,访客应记住什么?
Folio 的答案是:人是谁、在做什么、最近写了什么——其余的,能省则省。
相关链接
- 站点:oksite
- GitHub:github.com/oksite
- 上一篇产品向文章:NOTEYOU:一款本地优先的 Markdown 编辑器
- Hexo 官方文档:hexo.io/docs
- 部署说明:One-command deployment