1 静态博客是怎么回事

  博客分两种:动态和静态。

  动态博客(WordPress、Typecho 这类)需要一台服务器,每次有人访问,服务器现场查数据库、拼页面、再发给你。功能强,但要维护服务器、要操心数据库、访问量一大就慢。

  静态博客(Hexo、Hugo、Jekyll 这类)是在你自己电脑上把文章提前编译成 HTML,然后把这一堆 HTML 文件扔到任何能托管文件的地方。访客拿到的就是现成的页面,没有数据库、没有后端。

  静态博客的好处很实在:

  ——快。没有数据库查询,纯静态文件,秒开。
  ——省。托管到 GitHub Pages、Cloudflare Pages 都免费;自己有服务器的话资源占用也极低。
  ——安全。没有后端,也就没有后端漏洞可打。
  ——文章是自己的。全部是本地 Markdown 文件,随时能搬走。

  代价是:评论、搜索这些“动态”功能得另外想办法(用第三方服务,或者干脆不要)。对我来说这笔交易很划算——我只是想安静地写东西。

  这个博客就是用 Hexo 搭的,主题是 Stellar,部署在自己的服务器上。下面是我实际走了一遍的流程。

2 装环境

  需要两样东西:Node.js 和 Git。

  Node.js:去官网 https://nodejs.org/ 下 LTS 版本(长期支持版),一路下一步就行。装完在终端里敲 node -v,能输出版本号就成了。

  Git:从 https://git-scm.com/ 下载安装。装完敲 git --version 验证。

  两个都装好后,装 Hexo 的命令行工具:

1
npm install -g hexo-cli

  如果这一步报网络错误,多半是 npm 源的问题,换成国内镜像:

1
npm config set registry https://registry.npmmirror.com

3 建站与目录结构

  找个放博客的目录,执行:

1
2
3
hexo init my-blog
cd my-blog
npm install

  hexo init 会生成一套完整的目录骨架。装完依赖后,本地预览:

1
hexo server

  浏览器打开 http://localhost:4000,能看到默认页面就成了。

3.1 目录结构

  Hexo 的目录结构不复杂,但几个关键目录要分清:

1
2
3
4
5
6
7
8
9
10
my-blog/
├── _config.yml 站点配置(标题、作者、部署方式)
├── package.json 依赖清单
├── scaffolds/ 新建文章的模板
├── source/ 你写的东西都在这里
│ ├── _posts/ 文章(Markdown)
│ ├── _data/ 数据文件(分类、标签等)
│ └── about/ 独立页面
├── themes/ 主题
└── public/ 编译产物(自动生成,不用管)

  核心就一句话:source/_posts/ 里放 Markdown,_config.yml 里改配置,themes/ 里换主题。 其它都是自动的。

  public/ 是编译产物,不要手动改——下次 hexo generate 会覆盖掉。要改样式改主题文件或配置,不要动编译结果。

3.2 常用命令

命令 作用
hexo new "标题" 新建文章
hexo new page "关于" 新建独立页面
hexo server 本地预览(默认 4000 端口)
hexo generate 编译成静态文件
hexo clean 删掉编译产物和缓存
hexo deploy 部署
hexo clean && hexo generate 改配置后必做

  提醒一下:改了配置文件、主题、或者 front-matter,都要 hexo clean 再 hexo generate,否则可能读的是缓存。

4 写第一篇文章

1
hexo new "我的第一篇文章"

  这会在 source/_posts/ 里生成一个 .md 文件,开头是文章元信息(front-matter):

1
2
3
4
5
---
title: 我的第一篇文章
date: 2026-09-20 10:00:00
tags: [随笔]
---

  下面的正文用 Markdown 写。不熟悉 Markdown 的话见Markdown 入门。

  几个常用的 front-matter 字段:

字段 作用
title 文章标题
date 发布日期(决定排序)
tags 标签,写成 YAML 数组 [a, b]
categories 分类
description 摘要,首页卡片和搜索结果会用到
cover 封面图路径
banner 文章页顶部大图
katex 是否加载数学公式支持

  建议每篇都写 description。 首页卡片如果没摘要会取正文前几十个字,效果通常很难看。

  还有个细节:文件名决定 URL。Hexo 默认按 日期/标题 生成路径,如果你手动改了文件名,URL 也会跟着变,已经发出去的链接会失效。想固定 URL 的话,最稳妥的做法是一开始就用“日期+序号”这种不含中文的名字,比如 202609200100.md。

5 主题:我用的是 Stellar

  Hexo 自带一个很朴素的主题。我试过几个之后选了 Stellar。

5.1 主题信息

项目 内容
名称 Stellar
作者 xaoxuu
仓库 https://github.com/xaoxuu/hexo-theme-stellar
文档 https://xaoxuu.com/wiki/stellar/
版本 我用的 1.44.0
许可 开源

  选它的原因有几个:

  ——自带的功能够全。标签页、归档、搜索、相关文章、目录、代码高亮、数学公式,开箱就有,不用自己一个个装插件。
  ——Wiki 系统。这是它比较特别的地方:可以把文章组织成项目式的“书架”,适合写系列文章。这个博客的数学、Abaqus、MATLAB 几个专栏就是用这套东西组织的。
  ——配置文件分开放。主题自己的配置在 _config.stellar.yml,和站点的 _config.yml 分开,升级主题时不会被覆盖。
  ——可定制程度高。想改样式可以直接注入自定义 CSS,不用去改主题源码。

5.2 安装

  两种方式,选一种:

1
2
3
4
5
# 方式一:Git 克隆(推荐,方便升级和改源码)
git clone https://github.com/xaoxuu/hexo-theme-stellar themes/stellar

# 方式二:npm 安装(简单,但不方便改源码)
npm install hexo-theme-stellar --save

  然后在 _config.yml 里指定:

1
theme: stellar

  建议用 Git 克隆的方式。 因为主题文档里推荐的很多效果(自定义样式、局部改版)需要能看到主题源码;npm 装的话文件在 node_modules 里,改起来不方便。

5.3 配置

  Stellar 的配置分两个文件:

文件 作用
_config.yml(站点根目录) 站点信息:标题、作者、部署方式、theme
_config.stellar.yml(站点根目录) 主题配置:菜单、侧栏、样式、插件

  主题自己的默认配置在 themes/stellar/_config.yml,不要直接改它。 正确做法是在站点根目录建一个 _config.stellar.yml,把要改的项写进去——同名文件会覆盖主题默认值。这样升级主题时你的配置不会丢。

  常改的几项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 站点标题与副标题
logo:
title: 你的博客名
subtitle: 副标题

# 顶部导航
menubar:
columns: 4
items:
- id: post
name: 博客
path: /
- id: wiki
name: 项目
path: /wiki/

# 左侧栏样式
style:
leftbar: glass

  主题还支持注入自定义内容(inject.head),这是自定义样式的入口。我的做法是把所有自定义 CSS 写成一个 <style> 块塞在里面——好处是升级主题不会丢,坏处是这个文件会越来越长。

5.4 我改动的部分

  主题再好也有不合适的地方,我改了几处。这里记一下,因为升级主题时这些改动会被覆盖:

文件 改了什么 不改的后果
layout/_partial/main/article/related_posts.ejs 兼容 wiki 页面的标签格式 + 去重 wiki 页面整页报错
layout/page.ejs 让 wiki 页面也显示相关文章 wiki 页没有相关文章
layout/tags.ejs 一级标签优先排序 标签页顺序混乱
layout/_partial/head.ejs 微信 X5 浏览器的 viewport 适配 微信里字号偏小

  教训是:改主题源码一定要记下来。 我第一次升级主题时忘了这回事,几个功能直接坏了,排查了半天。现在我在项目根目录放了一份改动清单,升级前先看一遍。

  还有一点值得说:主题的配置项和主题的源码是两回事。配置项(_config.stellar.yml)升级不会丢,源码改动会丢。所以能用配置解决的,别去改源码。

6 部署:我用的是自己的服务器

  编译一次看看:

1
2
hexo clean      # 清掉旧的编译产物
hexo generate # 重新编译,产物在 public/

  public/ 里就是完整的静态站点。把它放到任何能托管静态文件的地方就行。下面三种方案我都了解过,我最终选的是方案 B。

6.1 方案 A:GitHub Pages(免费,最省事)

  (1)在 GitHub 建一个仓库,名字必须是 <你的用户名>.github.io。

  (2)装部署插件:

1
npm install hexo-deployer-git --save

  (3)改 _config.yml:

1
2
3
4
deploy:
type: git
repo: https://github.com/<用户名>/<用户名>.github.io.git
branch: main

  (4)部署:

1
hexo deploy

  等一两分钟,访问 https://<用户名>.github.io 就能看到了。

  缺点:国内访问 GitHub Pages 时快时慢;想绑自己的域名要额外配置;不能跑任何服务端逻辑。

6.2 方案 B:自己的服务器(我用的这个)

  有服务器和域名的话,可以自己搭。思路是:服务器上放一个 Git 裸仓库,本地推送,服务器收到后自动把文件同步到网站目录。

  这套流程的好处是完全自主——域名、HTTPS、缓存策略都由你定,访问速度也比 GitHub Pages 稳定。代价是要自己维护服务器。

  我的环境是 Ubuntu + Nginx,下面是完整步骤。

服务器端准备

  (1)装 Nginx 和 Git:

1
apt update && apt install -y nginx git

  (2)建两个目录——一个放裸仓库,一个放网站文件:

1
2
3
mkdir -p /var/repo/blog.git
mkdir -p /var/www/blog
cd /var/repo/blog.git && git init --bare

  (3)关键一步:在裸仓库里加一个 hooks/post-receive 钩子,让 Git 收到推送后自动把内容同步到网站目录:

1
2
#!/bin/sh
GIT_WORK_TREE=/var/www/blog git --git-dir=/var/repo/blog.git reset --hard master

  给它执行权限:

1
chmod +x /var/repo/blog.git/hooks/post-receive

  这行命令的意思是:把 master 分支的内容强制检出到 /var/www/blog。每次推送,网站目录就自动更新了,不需要你手动上传文件。

  (4)配置 Nginx,把站点根目录指向 /var/www/blog:

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name your-domain.com;
root /var/www/blog;
index index.html;

location / {
try_files $uri $uri/ =404;
}
}

  (5)重载 Nginx:

1
nginx -t && systemctl reload nginx

本地配置

  改 _config.yml 的部署设置:

1
2
3
4
deploy:
type: git
repo: root@<服务器IP>:/var/repo/blog.git
branch: master

  然后 hexo deploy 就会推送到服务器。

免密推送

  每次部署都输密码很烦,配一下 SSH 免密:

1
2
3
4
5
# 本地生成密钥(已有的话跳过)
ssh-keygen -t ed25519

# 把公钥传到服务器
ssh-copy-id root@<服务器IP>

  之后 hexo deploy 就不用输密码了。

HTTPS

  用 Certbot 申请免费证书最省事:

1
2
apt install -y certbot python3-certbot-nginx
certbot --nginx -d your-domain.com

  它会自动改 Nginx 配置并设置证书自动续期。

6.3 方案 C:Cloudflare Pages / Vercel(免费,自动构建)

  把博客源码推到 GitHub,然后在 Cloudflare Pages 里连上这个仓库,每次推送它自动编译部署。适合不想在自己电脑上存编译产物的人。

  缺点:构建过程你看不到细节,出问题不好排查;国内访问速度一般。

6.4 三种方案怎么选

GitHub Pages 自己的服务器 Cloudflare Pages
费用 免费 服务器+域名费用 免费
国内速度 一般 取决于服务器线路 一般
自定义程度 低 完全自主 中
维护成本 几乎为零 要自己维护 极低
绑定域名 要额外配置 直接配 支持
适合谁 只想写文章 想完全掌控 不想碰服务器

  我的选择是方案 B,主要图两点:一是访问速度可控(服务器线路自己挑),二是这套流程本身有学习价值——配下来会顺带搞明白 Git hook、Nginx、SSH、HTTPS 是怎么回事。

7 部署脚本

  每次都要敲三四条命令挺烦的,写个脚本一次搞定。Windows 下用批处理:

1
2
3
4
5
6
@echo off
cd /d "%~dp0"
call hexo clean
call hexo generate
call hexo deploy
pause

  存成 deploy.bat,以后双击就行。

  不过光看部署命令不报错,不代表真的推上去了。SSH 偶发超时、远端仓库有问题,都可能让部署静默失败。所以更稳妥的做法是在部署后加一步验证:

1
2
3
4
5
6
7
8
@echo off
cd /d "%~dp0"
call hexo clean
call hexo generate
call hexo deploy
REM 验证线上是否真的更新了
curl -s -o nul -w "%%{http_code}\n" https://your-domain.com/
pause

  返回 200 才算成功。这个博客的部署脚本就是按这个思路写的,还会顺带检查站内失效链接。

8 进阶:几个值得做的事

  ① 站内搜索。 Hexo 有 hexo-generator-searchdb 之类的插件,生成一个 JSON 索引,前端搜索。Stellar 主题内置了支持。

  ② 图片优化。 原始截图动辄几 MB,直接放上去会让页面很慢。建议统一压成 WebP,长边控制在 1920 以内。我写了个脚本批量处理——这一步对加载速度的影响比任何前端优化都大。

  ③ 失效链接检查。 文章一多,改了文件名、删了图片,就会出现死链。写个脚本在部署前扫一遍 HTML 里的站内链接,能避免上线后才发现。

  ④ 备份。 博客源码要备份。最省事的做法是把整个目录(除了 node_modules 和 public)放进一个 Git 仓库,推到私有仓库里。

  ⑤ 统计。 想知道哪些文章有人看,可以接 Google Analytics、Umami、或者自建统计。不喜欢被追踪的话,用 Nginx 日志自己分析也行。

9 几个踩过的坑

  ① 改了配置不生效。 Hexo 会缓存编译结果,改完配置先 hexo clean 再 hexo generate。

  ② 图片不显示。 检查路径写法。放在 source/assets/ 下的图片,引用路径是 /assets/...(对应网站根目录),不要写成相对路径。文章可能在首页、归档页、标签页等多个位置渲染,相对路径很容易失效。

  ③ 部署成功但网站没变。 先按 Ctrl+Shift+R 强制刷新(可能是浏览器缓存);还没变就检查是不是推到了别的分支;再不行就登服务器看 /var/www/blog 里文件的时间戳。

  ④ 主题升级后功能坏了。 如果你改过主题源码,升级会全部覆盖。记住自己改了哪些文件——这是我踩过最深的坑。

  ⑤ 中文文件名或路径报错。 尽量全部用英文命名目录和脚本文件。有些工具链对中文路径的编码处理不可靠。尤其是 .bat 文件——cmd 按代码页解析批处理,里面的中文注释会被切分误读,导致莫名其妙的语法错误。

  ⑥ 部署时 SSH 偶发失败。 Could not read from remote repository 这类错误,多半是网络抖动。在脚本里加自动重试(等 30 秒重试,最多试三次)能省很多心。

  ⑦ 服务器磁盘被日志撑满。 Nginx 的 access log 长期不清理能涨到几个 GB。配一下 logrotate,或者定期清理。

10 值不值得

  如果你只想写文章、不想碰技术,用现成的平台(知乎、语雀、Notion)效率更高。

  但如果你想完全掌控自己的内容——排版、域名、数据、以后能不能导出——那静态博客是很好的选择。搭一次花一个周末,之后就是纯粹的写作。

  而且这个过程本身有收获:你会顺带搞明白 Git、命令行、Nginx、DNS、HTTPS 这些东西是怎么回事。这些东西学一次,以后到处都用得上。