这篇笔记记录搭建这个博客系统的全过程:安装 Hexo、引入并定制 Stellar 主题, Vercel 部署, Waline 评论系统。
同时

Hexo 安装与初始化

  1. 安装 Hexo CLI:npm install -g hexo-cli
  2. 初始化项目:hexo init jblog && cd jblog,随后执行 npm install 安装依赖。
  3. 使用 hexo s 启动本地预览,确认生成的默认站点能够正常访问。
  4. 站点通用信息(标题、作者、语言等)都集中在根目录的 _config.yml;之后撰写文章可以直接运行 hexo new "<标题>",😂当然我主要还是直接创建md文件。

Stellar 主题

  • 其实我最开始使用nextjs自己写过一版本博客,但是,确实怎么调都感觉有点丑,偶然看到stellar这个主题,觉得很好看,而且开源,所以我可以在这个轮子上好好折腾了。
  • 起初我通过命令 git submodule add https://github.com/jingcjie/hexo-theme-stellar.git themes/stellar 把 Stellar 拉进来,并在 _config.yml 中设置 theme: stellar 验证了外观,确实满意。
  • 为了后续自由修改主题,我将子模块完整复制到 themes/stellar/,然后删除 .gitmodules 及对应的 .git/modules/themes/stellar。这样主题就变成普通文件,直接跟主项目一起提交,不再需要管理额外仓库。
  • 现在只要 npm install 后运行 hexo clean && hexo s 就能预览,并随时针对主题做改动。

我做的自定义调整

  1. 暗色调整体重塑
    原主题实在太亮了,闪瞎狗眼。需要调暗一点:
    themes/stellar/source/css/_custom.styl 覆盖核心 CSS 变量,选择了中性炭灰 (#232830) 作为背景,让界面既不亮也不黑💡。
    同时同步调整 --bg-a20 ~ --bg-a100 等透明度变量,以及正文、标题、卡片等颜色,保持层次感。

  2. 搜索框视觉修复
    原主题在暗色模式下点击搜索会出现大块白色遮挡。我用 .search-wrapper > .search-form 定制了背景和边框,使其沿用新的暗色卡片样式,并移除会泛白的伪元素,输入内容始终可见。

  3. 色彩与阴影统一
    调整了阴影、强调色、文本权重等细节,让 Stellar 的原有组件与新配色融为一体,整体视觉更协调。

调试时的常用命令:

1
hexo clean && hexo s   # 清理生成文件并在本地预览改动

使用 Vercel 部署

后续准备把仓库托管到 GitHub,并接入 Vercel 自动部署。计划流程:

  1. 在 Vercel 新建项目并关联仓库。
  2. 选择hexo编译。
  3. 每次推送到主分支后自动构建发布。

Vercel analytics

可能这个很多人不用vercel或者自己实现了访客统计,所以居然都没人讨论这个!!
因为hexo编译完成是静态网页,没办法像vercel直接推荐方式来加入analytics。

这里我发现这个主题已经实现了html注入,所以只需要在config文件的plugins下面加入自定义的插件就可以了:

1
2
3
4
5
6
7
8
9
vercel_analytics:
enable: true
inject: |
<script>
window.va = window.va || function () {
(window.vaq = window.vaq || []).push(arguments)
}
</script>
<script defer src="/_vercel/insights/script.js"></script>

集成 Waline 评论系统

虽然这个站并没有什么访客,但是万一有了呢

Waline 是偏前后端分离的评论系统,:

  1. 准备 Waline 服务端,可以同样用vercel部署,vercel已经是我赛博菩萨的事实替身了。
  2. 在部署平台配置相关环境变量(在LeanCloud 获取)。
  3. 回到 themes/stellar/_config.yml 启用 Waline,按需调整样式,使其符合当前的暗色主题。

这里stellar主题又有一个小bug,如果开启最近回复的选项,waline的返回不是array,这个主题的js直接处理会报错的,调整成如下使用。
我这里直接修改了他的js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
utils.request(el, api, async resp => {
// const data = await resp.json();
const payload = await resp.json();
const data = Array.isArray(payload) ? payload : payload?.data || [];

data.forEach((item, i) => {
var cell = '<div class="timenode" index="' + i + '">';
cell += '<div class="header">';
cell += '<div class="user-info">';
cell += '<img src="' + (item.avatar || default_avatar) + '" onerror="javascript:this.src=\'' + default_avatar + '\';">';
cell += '<span>' + item.nick + '</span>';
cell += '</div>';
cell += '<span>' + new Date(item.time).toLocaleString() + '</span>';
cell += '</div>';
cell += '<a class="body" href="' + item.url + '#' + item.objectId + '" target="_blank" rel="external nofollow noopener noreferrer">';
cell += item.comment.replace(/<a\b[^>]*>(.*?)<\/a>/g, '$1');
cell += '</a>';
cell += '</div>';
$(el).append(cell);
});
});

由于还没摸清楚整个框架,我会保持更新这个文档,尤其是stellar的地方,肯定还有不少会自定义的内容。🧠