How to Build, Deploy and Configure a Hexo Site
本文介绍如何构建、部署并配置 Hexo 站点(配合 NexT 主题)。内容面向计算机背景读者,强调可复用的操作流程与关键坑位。
Sources:
- Hexo 官网
- 较完整的 Hexo + NexT 搭建教程(部分内容偏旧)
Note: After configuring the Hexo project, you may be insterested in playing with a Hexo theme. Check out my post about Hexo NexT Theme.
How to Build, Deploy and Configure a Hexo Site
Introduction
Dependency Versions
- NexT Version: 8.20.0
hexo-cliVersion: 4.3.2
Positioning
Hexo 是流行的静态博客框架,同类还有 Hugo 和 Jekyll。
- 优点:插件生态更丰富,自定义更灵活;整体更“傻瓜化”,更专注写作。
- 缺点:基于 npm,构建与运行通常较慢。
本文使用 Hexo + NexT。
Directory Layout and Config Files
- Hexo 目录:
<hexo-dir>/ - Theme 目录:
<hexo-dir>/themes/next/ - Hexo 配置文件(Hexo
_config.yml):<hexo-dir>/_config.yml - NexT 配置文件(NexT
_config.yml):<hexo-dir>/themes/next/_config.yml
说明:下文如无特殊说明,编辑的均为 Hexo 配置文件:
<hexo-dir>/_config.yml。
Common Hexo Commands
在Hexo官方文档中可以查看全部命令,这里只列出常用的。
| 命令 | 描述 |
|---|---|
hexo init [folder] |
初始化网站 |
hexo new [layout] |
新建文章, 默认是“post”, 我通过default_layout: draft设置为新建到“draft” |
hexo publish [layout] |
发布草稿 |
hexo g[enerate] |
构建网站, 生成的文件放在<hexo>/public目录下. |
hexo s[erver] |
启动本地服务器, 服务器会监听文件变化并自动更新 |
hexo d[eploy] |
在安装了deploy git 插件后, 可以生成本地文件并远程部署到GitPage. 再也不用hexo d -g了 |
hexo clean |
清理数据库和静态文件 |
hexo list |
列出站点信息 |
hexo version |
版本信息 |
hexo d -g |
生成并部署 |
以及:
Check version:
1 | hexo version |
Install Hexo:
1 | npm install -g hexo-cli |
如果想要安装指定版本的Hexo,尽管这个需求很奇怪,但你可以copy一份对应版本的Hexo的package.json, 然后npm install.
Upgrade:
1 | npm install hexo |
Uninstall:
1 | npm uninstall hexo |
Create a Hexo Site
安装 Hexo:
1
npm install -g hexo-cli
在本地指定文件夹
<folder>中建立项目:1
2
3hexo init <folder>
cd <folder>
npm install初始化完成后,文件夹
<folder>的目录结构如下:1
2
3
4
5
6
7
8.
├── _config.yml
├── package.json
├── scaffolds
├── source
| ├── _drafts
| └── _posts
└── themes文件/文件夹 作用 _config.yml 网站的配置文件 package.json 应用程序的信息 scaffolds 模版文件夹。当您新建文章时,Hexo 会根据 scaffold 来建立文件 source 资源文件夹是存放用户资源的地方。除 posts 文件夹之外,开头命名为 (下划线)的文件 / 文件夹和隐藏的文件将会被忽略。Markdown 和 HTML 文件会被解析并放到 public 文件夹,而其他文件会被拷贝过去 themes 主题文件夹。Hexo 会根据主题来生成静态页面 安装Hexo Theme, 见下文
Build Site
Hexo项目的构建(也就是把你的源文件(markdown + 主题模板 + 配置 + 资源)“编译”成可直接部署的静态站点文件 , i.e., HTML, CSS, JS.)可以在本地进行,也可以在本地构建完后推送到某个远程github repo的某个分支,第二种方法常用鱼Gitpage等网站托管工具。
1. Build to ./public (Local Build)
构建Hexo网站并将产物放到 <hexo>/public:
1 | hexo g[enerate] |
要点:
hexo g的构建产物位于<hexo>/public。<hexo>/.gitignore通常包含public,因此public不会被 add 并 push 到源码分支(例如你用于写作的hexo分支)。- 这种构建仅发生在本地,仅用于本机预览/调试”的工作流,不会涉及“把XX文件push到远程”。
2. Build Locally and Push to Remote
hexo d构建网站并将产物放到.deploy_git, 然后按配置push到某个remote repo的某个branch。当你的网站部署工具是Gitpage时,就会用这种方法。我的网站部署工具是Netlify(后面会提到),它不需要这么做,因此如果你也想像我一样部署网站,则这一小节可以跳过。
命令为:
1 | hexo d |
它的工作方式为:
构建项目,把构建产物放到
.deploy_git- 若
.deploy_git不存在,会git init;否则复用现有.deploy_git仓库及其历史。 - 注:
hexo d使用 SSH 而非 HTTP;所以需要先在 GitHub 添加 SSH 公钥。
- 若
把构建产物按
<hexo>/_config.yml里的配置来push到某个远程仓库的分支。例如,如果配置为1
2
3
4
5
6deploy:
# 使用hexo-deployer-git
type: git
repo:
github: [email protected]:LYK-love/LYK-love.github.io.git
branch: master则构建产物会被push到github仓库
LYK-love/LYK-love.github.io的master分支.
使用hexo d需要先安装对应的插件,如果你的网站部署工具是Gitpage,那么插件就是hexo-deployer-git.
Summary: hexo g vs hexo d
hexo g:产物在本地public/,用于本地预览/调试。hexo d:产物在本地.deploy_git/,随后 force push 到远端指定分支(例如master),用于对外发布。
只要基于同一份源文件构建,理论上:
- 本地
public/的内容 - 本地
.deploy_git/的内容 - 远端
master分支内容(Pages 发布源)
应当一致。
Recommended Config
个人建议在本地和远程都创建两个git branches "hexo" 和 “master”,并且在Github仓库->Settings->Branches->Default branch中将默认分支设为hexo,平时进行的各种编辑也都在"hexo"分支下;master分支则专门用于在使用Gitpage进行部署的情况下存放项目的构建文件(HTML,CSS等等),不需要额外关心。
如果你像我一样使用Netlify进行部署,则不需要这个master分支,项目在本地和远程都只需要一个分支"hexo"(或者取别的名字,例如main)。
Deployment Options
这里给出 3 种部署方式:Local / GitHub Pages(Gitpage)/ Netlify。
第一种是部署到本地, 用于本地debugging.
后面两种分别是部署到Gitpage和Netlify. 前者更简单,但是Gitpage这个工具提供的高阶功能较少,因此我实际选择的是Netlify。
Gitpage的部署需要用hexo-deployer-git插件, 之前说过这个插件会在本地构建,然后把构建产物放在一个指定的分支(master分支)并push到Github远程仓库. 因此Github本身不需要运行任何构建指令, 只需要把构建好的push上来的文件放到Gitpage提供的服务器.
Netlify的部署不使用这个方式. Netlify会根据Github仓库进行构建, 由于仓库里没有构建产物, Netlify需要运行用户给定的构建指令来在本地(是Netlify的本地, 不是用户本机)进行构建, 然后把给定位置的目录下的内容放到Netlify服务器.
根据我的配置, Netlify会在它的本地运行hexo clean & hexo g, 并指定public目录作为发布目录, 将该目录下的文件放到服务器. 之前介绍过hexo g会把构建产物放在<public>目录, 因此发布目录的内容其实就是hexo g的内容.
Deploy Locally
用hexo g来进行本地构建,产物位于./public。 然后, 你就可以用 hexo-server这个工具在本地启动一个 HTTP 服务器(默认 localhost:4000),用来预览生成的网站,并在文件变更时自动重建/刷新。
1 | hexo g |
Deploy to GitPages
- Plugin: hexo-deployer-git
GitPage 允许你将你的博客创建为一个 GitHub Project,通过 your-account.github.io 这样的特殊项目名称与 GitPage 进行关联,然后,你只需要像平时一样 commit & push 你的博文到 GitHub 上就 OK 了,GitPage 会自动将你的更新部署.
它的工作逻辑是:
- 在 GitHub 仓库设置里指定 Pages 关联的分支(例如
master)。 - 本地用
hexo-deployer-git把构建产物 push 到这个分支。 - GitHub Pages 自动把该分支内容发布到 Pages 服务器。
注意:私有仓库的 GitHub Pages 也可能被公共访问;因此不要用含敏感信息的私有仓库做 Pages 源。
整个workflow:
安装 deploy git 插件:
1
npm install hexo-deployer-git --save
在 GitHub 创建一个名为
<username>.github.io.git的仓库.在主题配置文件
_config.yml中修改仓库地址, 注意, 为了后文说的多主机同步. 我的仓库有两个分支.master用于存放生成的网页文件,hexo存放源文件. 部署当然是push网页文件, 也就是master分支:1
2
3
4
5deploy:
type: git
repo:
github: [email protected]:LYK-love/LYK-love.github.io.git
branch: masterGitPage可以关联到项目的任意分支, 我们要到仓库的Settings -> Code and automation -> Pages里, 把Pages关联到master分支. 这样我们部署到master的网页文件就可以同步到Pages上.
执行
hexo d即可部署到 GitHub 仓库.之前介绍过,
hexo d会构建网站, 把产物存放在本地的.deploy_git文件夹中, 并将文件夹的内容根据配置来force push到Github. 我的配置是push到仓库的master分支.由于已经在Gitpage的设置中将Page关联为master分支, 因此我对master分支的提交内容就会被同步到gitpage服务器上, 实现对Gitpage的部署.
新增或修改主题配置后部署时请执行
hexo clean && hexo d
Domain Notes for GitHub Pages
关于域名配置,可参考后文 Netlify 的域名配置逻辑;差异点如下:
- GitHub Pages 在中国大陆未被屏蔽,因此通常不需要 Cloudflare 的 DNS/Proxy;使用阿里云默认 DNS 即可。
- GitHub Pages 通常给的是
github.io对应的 IP(例如你的网站是151.101.129.147),因此 DNS 记录一般是A record(例如lyk-love.cn->151.101.129.147),而不是CNAME指向某个.netlify.app域名。 - DNS 生效后,本地执行
hexo d发布。此时远端仓库里应能看到CNAME文件,之后即可通过自定义域名访问站点。
Deploy to Netlify
Netlify是一个国外的提供免费的静态网站部署的一个网站, 它的功能比gitpage更强大, 而且操作起来也很简单, 我的Hexo网站也是用Netlify部署的。
利用Netlify进行部署的基本逻辑:
- Netlify 关联 GitHub 仓库;
- 一旦仓库收到 push,GitHub 通知 Netlify(类似 webhook);
- Netlify 在云端按配置执行构建(例如指定 Node.js 版本、构建命令、构建产物所放到的目录等);
- 发布到一个
*.netlify.app域名(例如lyk-love.netlify.app)。
注意:上述流程都在 Netlify 云端完成,本地不需要额外运行构建/部署指令。
参考教程(很详细):https://blog.cuijiacai.com/blog-building/
这篇教程对如何使用讲得非常详细,照着它来就行了。 我只做如下补充:
Note 1: Git Branch used by Netlify
你的 Hexo 源码默认 push 到远程 hexo 分支,因此 Netlify 的构建分支也应设为远端 hexo 分支。
(你可能还记得 master 分支:它是 hexo-deployer-git 用于存放构建产物的分支;但 Netlify 走的是“云端构建”,不需要你把产物 push 上去。)
Netlify 中可在 Project configuration -> Branches and deploy contexts 查看/编辑分支。
之后每次部署你只需要:
确保在本地
hexo分支(通常默认就是)。push 到远端
hexo分支:1
2
3git add .
git commit -m "XXX"
git push origin hexo
Note 2: Building Commands in Netlify
对Netlify 进行的配置如下:监听对 github.com/LYK-love/LYK-love.github.io 的 hexo 分支的 push;它会触发构建命令 hexo clean & hexo g;并将 public/ 作为发布目录。
这与 Hexo 本地部署 (hexo s) 的行为一致:hexo g 会把产物写到 public/,因此“发布目录”就是构建产物所在的目录。
Note 3: Domain Name Config
Netlify 部署站点后会自动分配一个 *.netlify.app 域名(例如 lyk-love.netlify.app),在中国大陆之外可正常访问。下面说明如何让该站点在中国大陆也可以访问。 这部分内容同样在这个教程中。
TL;DR:
- 从阿里云买域名
lyk-love.netlify.app - 在Netlify中添加该域名,但是不给该域名使用Netlify DNS
- 在Cloudflare中,为该域名添加CNAME record,并给该record启用DNS record proxy。结束后,Cloudflare会提供给你DNS server。
- 回到阿里云的域名控制台,把该域名的DNS从阿里云默认给的切换成Cloudflare提供的
- 在Netlify中为该站点添加SSL证书,这样就能使用HTTPS(而不仅是HTTP)访问
下面是对上述操作的详细解释:
在中国大陆,lyk-love.netlify.app 打不开的根本原因是:该域名最终会把浏览器带到 Netlify 的 CDN 节点;而 Netlify CDN 在中国大陆的很多节点不可用,因此“大陆用户浏览器 → Netlify CDN”的直连链路经常失败。
为了解决这个问题,第一步是购买一个自有域名(例如 lyk-love.cn)。从实践经验出发,为了降低域名本身被干扰的风险,可以选择在国内注册商购买(我是在阿里云购买的)。
很多人会想到:在域名 DNS 里加一条 CNAME,把 lyk-love.cn 指向 lyk-love.netlify.app,然后访问 lyk-love.cn 即可。这并不能解决“中国大陆可访问”的问题,因为它不会改变“浏览器直连Netlify节点”的路径:
1 | 浏览器访问 https://lyk-love.cn |
因此,需要在“大陆用户 → Netlify”之间加入某种代理,把链路改成:
- 大陆用户先访问一个代理/CDN
- 由该代理去访问 Netlify,再把内容转发给用户
这里使用 Cloudflare 的 DNS record Proxy(国内叫做“橙云”) 来做这个代理。Cloudflare Proxy的机制是:当某条 DNS 记录开启 Proxy 后,客户端解析域名时不会拿到真实回源目标的解析结果;相反,Cloudflare 会返回自己的 Anycast IP,让浏览器先连接 Cloudflare (严谨地说,是Cloudflare的CDN),然后由 Cloudflare 代为回源到你的源站并转发流量。官方说明见:
- Cloudflare Proxy status 文档:https://developers.cloudflare.com/dns/proxy-status/
该文档说明了一个关键:使用“DNS record Proxy(橙云)”的前提是域名必须在 Cloudflare 的 Authoritative DNS(权威 DNS)之下。否则 Cloudflare 无法对外回答该域名的 DNS 查询、也就无法把解析结果“替换”为 Cloudflare Anycast IP,更无法接管后续的 HTTP/HTTPS 流量路径。
配置完成后,你的访问路径应当是:
1 | 浏览器访问 https://lyk-love.cn |
使用Proxy后,大陆用户不再需要直连 Netlify CDN(这段链路不稳定/被屏蔽),而是先连 Cloudflare;随后由 Cloudflare 在其网络侧回源访问 Netlify(通常发生在境外网络环境),从而绕开“大陆直连 Netlify CDN 不稳定”的问题。
最后,我们还需要在Netlify 的站点设置里添加 lyk-love.cn(Domain management / Production domains 中 Add domain alias)。 尽管我们不使用Netlify的DNS,但Netlify依然需要“知道”该新域名,因为
- 用户对站点的请求在经过DNS/Proxy/... 后依然带有 Host/SNI=lyk-love.cn, 而Netlify需要知道这个
lyk-love.cn代表的站点。 - 浏览器访问
https://lyk-love.cn时要求证书覆盖lyk-love.cn。Netlify 只有在你把该域名添加到站点后,才会为其签发/部署对应的 HTTPS 证书;否则可能出现证书不匹配导致的 HTTPS 报错。
路由:Netlify 的边缘节点是多租户的,同一套边缘 IP 服务很多站点。边缘节点需要根据请求里的 Host(以及 TLS 的 SNI)判断这次请求属于哪个站点;如果 Netlify 没登记 lyk-love.cn,即使 DNS 指向 Netlify,边缘也可能无法把请求路由到你的站点(常见表现是 404 / Not Found)。
常见疑问:Netlify 文档说第三方 DNS 要 ALIAS/CNAME flattening,我为什么没专门配也能用?
在 Netlify 的域名入门文档中:https://docs.netlify.com/manage/domains/get-started-with-domains/
提到了“如果使用第三方DNS提供商,则需要在该提供商那里设置ALIAS/CNAME flattening/<任何等效的记录>”, 为什么我的方案中并没有提及要在Cloudflare中额外添加一条DNS record?
回答:你看到的 ALIAS/flattening 主要是在解释DNS的设计约束:根域(zone apex,例如
lyk-love.cn这种不带www的域名) 不能包含CNAME record”,所以需要一种“看起来像 CNAME,但对外回答 A/AAAA(IP)”的机制(即 ALIAS/ANAME/CNAME flattening)。在 Cloudflare 这个方案里,你之所以感觉“没有单独做 ALIAS/flattening”,原因通常是:
- 开启 Proxy(橙云)后:客户端拿到的是 Cloudflare Anycast IP;从客户端视角看,解析结果已经是可直连的 IP,后续回源由 Cloudflare 处理(你不需要再额外做“让客户端追 CNAME”的那套事)。
- 不开 Proxy 时:Cloudflare 也有其 flattening 能力/行为来处理 apex 场景(效果等价于 ALIAS 一类机制)。
因此不是“省略了必要步骤”,而是 Cloudflare 在“权威 DNS + Proxy”的组合里把这些细节包含了。
Note 4: Badges and Buttons from Netlify
Netlify offers several badges to put in the readme.md document of your project hosted by it.
- You can add a deployment status badge according to: https://docs.netlify.com/deploy/create-deploys/#deploy-to-netlify-button
- You can add a deploy-to-Netlify button according to: https://docs.netlify.com/deploy/create-deploys/#deploy-to-netlify-button
Markdown rendering with hexo-renderer-markdown-it (Deprecated Section)
该方法目前已废弃, 因为现在的renderer换成了pandoc, 不再使用hexo-renderer-markdown-it.
hexo-renderer-markdown-it的默认配置是无法正确给标题添加anchor的, 需要做一些修改, 并将配置添加到Hexo中.
编辑 Hexo _config.yml, 插入以下内容:
1 | # Config of hexo-renderer-markdown-it |
当然你也可以直接更改依赖的代码, 但是这样做无法进行版本管理, 所以不推荐:
进入包目录:
1
cd [hexo-site]/node_modules/hexo-renderer-markdown-it
编辑
index.js:1
2
3
4
5
6
7
8
9
10hexo.config.markdown.anchors = Object.assign({
level: 2,
collisionSuffix: '',
permalink: true, //更改为true
permalinkClass: 'header-anchor',
permalinkSide: 'left',
permalinkSymbol: '', //更改为空字符串
case: 0,
separator: '-'
}, hexo.config.markdown.anchors);
Render Anchor
如前所述, 只需要使用hexo-renderer-markdown-it, 并修改其配置文件, 就可以使文章Header自带Anchor, 实现页面内跳转.
例子:
Markdown的一个标题:
1 | // in markdown: |
会被Hexo渲染成:
1 | //in html |
- 空格会被转换为连字符, 大写会被转换为小写.
- 由于我在
hexo-renderer-markdown-it中的配置, 空格会被转换为-, 而大小写是不转换的
- 由于我在
如果有重名的标题(即使处于不同的标题层次), 就会在html的标签的id属性中予以区分:
1 | // in markdown: |
1 | //in html |
因此, 只需要在markdown中写:
1 | [显示的内容](#标题) |
生成的Html是:
1 | <a href="标题">显示的内容</a> |
这就引用了对应的标题:
1 | # 标题 |
可以看到, 这是基于Html的标签id匹配的, 而Markdown标题生成的Html标签的id和标题级别没有关系, 只和标题名字有关系. 所以
1 | [显示的内容](#KKK) |
可以引用到:
1 | # Haha |
中的二级标题KKK
由于空格会被转换为-, 因此如果标题为:
1 | # Traditional Dead Lift |
则应该这样引用:
1 | [显示的内容](#Traditional-Dead-Lift) |
Customization
Hidden Posts
See hexo-hide-posts plugin. You can install it via
1 | npm install hexo-hide-posts |
and use it by adding hidden: true to the front-matter of posts which you want to hide.
e.g. Edit source/_posts/lorem-ipsum.md:
1 | --- |
This post will not be shown anywhere, but you can still access it by https://hexo.test/lorem-ipsum/. (If you want to completely prevent a post from rendering, just set it as a draft.)
Performance Optimization
使用hexo-filter-optimize来提升网页加载速度:
下载插件:
1
npm install hexo-filter-optimize
编辑配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25filter_optimize:
enable: true
# remove the surrounding comments in each of the bundled files
remove_comments: false
css:
# minify all css files
minify: true
# bundle loaded css files into one
bundle: true
# use a script block to load css elements dynamically
delivery: true
# make specific css content inline into the html page
# - only support the full path
# - default is ['css/main.css']
inlines:
excludes:
js:
# minify all js files
minify: true
# bundle loaded js files into one
bundle: true
excludes:
# set the priority of this plugin,
# lower means it will be executed first, default of Hexo is 10
priority: 12
Notes on Writing
默认新建的文章都是posts, 改成新建为drafts:
1
default_layout: draft
这样
hexo new新建的文章就都是drafts了.文章可以有多个category, 分类具有顺序性和层次性, 有
3种不同的编写方式1
2
3
4
5
6
7
8
9
10# 第一种
categories:
- Java
- Servlet
# 第二种
categories: [Java, Servlet]
# 第三种
categories:
-[Java]
-[Servlet]前一、二种书写方式的作用一致,表示该文章分类于
Java/Servlet下,起到了子分类的作用第三种书写方式起到了多分类的作用,表示该文章分类于
Java和Servlet下标签没有层次性:
1
2
3
4
5categories:
- Diary
tags:
- PS3
- Games
Site Metadata
Configuring Author
Edit Hexo config file and set the value of author to your nickname.
1 | Hexo config file |
Configuring Description
Edit Hexo config file and set the value of description to your description, which can be a sentence you like.
1 | Hexo config file |
Enabling Theme
I use Hexo Next Theme. Like all Hexo themes, after you download it, open Hexo config file, find theme option, and change its value to next (or another theme directory name).
Edit Hexo config file:
1 | theme: next |
Now you have installed NexT theme and enabled it. Check out this post for config of this theme.
Troubleshooting and Bugs
hexo g会生成静态文件, 但是,如果你的目录下有失效的软链接, 就不会生成文件。 因此请删除所有的失效软链接- ref:Fixing Hexo Not Generating Files
hexo的markdown源代码避免出现跨级标题结构, 这里的跨级指的是不能从一个一级标题直接跟三级标题;二级标题后紧跟的子标题级别必须是三级标题
文章的Front-matter是YAML格式, 因此冒号后面必须有一个英文空格:
1
2
3title: XX
categories: XX
tags: XX否则报错:
YAMLException: can not read a block mapping entry; a multiline key may not be an implicit key
极其罕见的Bug: Hexo和Next分别更新, 结果Latex不能显示, hexo g巨慢, 页面闪烁, back2top小箭头图表消失等等等等...
由于 JS 依赖链复杂且版本冲突难以定位,遇到这类问题不建议死磕:直接重装 Hexo 和 NexT。