0%

Hexo个人博客不完全指北

小破站从Wordpress迁移到Hexo啦!并且遵循

  • Serverless
  • CI/CD
    这两大原则,将源代码托管到了Github上,网站托管到了Cloudflare Pages上,提交代码自动构建。这篇文章记录下迁移经过以及使用指南。

安装Hexo

参考官方安装教程,仅简单记录:

  1. 安装Node.js
  2. 安装Git
  3. 命令行安装Hexo:
1
npm install -g hexo-cli

Hexo常用命令

new

1
2
hexo new post "文章标题" -s "file-name" #生成文章
hexo new page "隐私政策" -s "privacy-policy" #生成页面

由于文章/页面标题常用中文,而文件名和url通常是英文,所以加上-s来指定文件名和url

generate

1
hexo g

用来生成静态文件,在部署时使用

clean

1
hexo clean

删掉之前生成的静态文件,相当于删除缓存

server

1
hexo s

在本地运行服务器,通过访问http://localhost:4000/进行预览

创建网站

新建文件夹

在命令行进入工作目录,执行下列命令:

1
2
3
hexo init 网站文件夹名称
cd 网站文件夹名称
npm install

请自行修改网站文件夹名称,用英文。
完成后目录结构如下:

1
2
3
4
5
6
7
8
网站文件夹名称
├── _config.yml
├── package.json
├── scaffolds
├── source
| ├── _drafts
| └── _posts
└── themes

配置网站

修改根目录下的_config.yml,主要修改下列内容:

1
2
3
4
5
6
7
8
9
10
11
title: Logiconsole
subtitle: '游戏人'
author: Logic
language: zh-CN
timezone: 'Asia/Shanghai'

url: https://www.logiconsole.com
permalink: :title/
pretty_urls:
trailing_index: false # Set to false to remove trailing 'index.html' from permalinks
trailing_html: false # Set to false to remove trailing '.html' from permalinks

初步配置完成

使用主题

本站使用的是NexT主题,以该主题为例。不同主题会有不同的配置项,需要参考主题文档

安装NexT

在网站文件夹下,执行命令:

1
npm install hexo-theme-next

激活主题

修改网站文件夹下的_config.yml中的主题:

1
theme: next

配置NexT主题

修改themes/next文件夹下的_config.yml文件,下面是主要修改项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
footer:
powered: false #尊重主题作者,可以不改

# scheme: Muse
scheme: Gemini

menu:
categories: /categories/ || fa fa-th # 在左侧菜单显示文章分类

social: #自行设置

back2top:
enable: true
scrollpercent: true # 可以在“回到顶点”按钮上显示当前滚动条进度

修改字体

本站目前使用的是中文:思源宋体,英文:Roboto Serif,代码:Fira Code,都是Google Fonts上可以找到的,更换其他字体的思路类似,但是此方法只对可以在Google Fonts上搜索到的字体有效。

修改英文和代码字体

继续修改themes/next文件夹下的_config.yml文件:

1
2
3
4
5
6
7
font:
enable: true
host: https://fonts.loli.net #使用loli.net反向代理的google fonts
global:
family: Roboto Serif
codes:
family: Fira Code
修改中文字体

修改themes/next/layout/_partials/head文件夹下的head.swig文件,在

1
2
3
{{ next_font() }}
{%- set font_awesome_uri = theme.vendors.fontawesome or next_vendors('font-awesome/css/all.min.css') %}
<link rel="stylesheet" href="{{ font_awesome_uri }}">

下方添加

1
<link href="https://fonts.loli.net/css?family=Noto+Serif+SC&display=swap" rel="stylesheet">

添加公式显示

我们使用katex来显示公式,其语法可以参考官方文档

安装hexo-renderer-markdown-it-plus

在终端中,回到网站文件夹根目录,执行命令:

1
2
$ npm un hexo-renderer-marked
$ npm i hexo-renderer-markdown-it-plus
启用katex

继续修改themes/next文件夹下的_config.yml文件:

1
2
3
4
math:
katex:
enable: true
copy_tex: true
添加katex.min.css

继续修改themes/next/layout/_partials/head文件夹下的head.swig文件,在最后添加一行:

1
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/katex.min.css">
使用katex
  • 行内显示,用$符号包裹
    这是一行文字前半段$\frac{a}{b}$这是一行文字后半段

显示效果
这是一行文字前半段ab\frac{a}{b}这是一行文字后半段

  • 区块显示,用用$$符号包裹
1
2
3
4
5
6
这是一行文字
$$\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}$$
这是一行文字

显示效果
这是一行文字

[abcd]\begin{bmatrix} a & b \\ c & d\end{bmatrix}

这是一行文字

修改favicon

  1. 自行准备图标,放到themes/next/source/images文件夹下
  2. 修改themes/next文件夹下的_config.yml文件的favicon:部分

添加评论功能

由于Hexo是纯静态的网页,只能使用第三方的评论模块。NexT自带了一些第三方模块,详见官方文档。本站使用的是DisqusJS。配置方法不再赘述。

添加搜索功能

安装hexo-generator-searchdb

在网站文件夹根目录下执行:

1
npm install hexo-generator-searchdb
修改Hexo配置

修改根目录下的_config.yml,在最后添加如下内容:

1
2
3
4
5
search:
path: search.xml
field: post
content: true
format: html
修改NexT配置

修改themes/next文件夹下的_config.yml文件:

1
2
local_search:
enable: true

添加分类菜单

创建分类页面

在网站文件夹根目录下执行:

1
hexo new page categories
修改页面类型

修改新生成的categories/index.md文件如下:

1
2
3
4
5
---
title: Categories
date: 2023-05-07 23:05:37
type: categories
---
激活分类菜单

修改themes/next文件夹下的_config.yml文件:

1
2
menu:
categories: /categories/ || fa fa-th

从Wordpress导入

其实从Wordpress迁移到Hexo最麻烦的还是如何保留以前的文章。这一点上Hexo做的还是不错的,除了Wordpress,官方还提供了从RSS/Jekyll/Octopress/Joomla迁移的方法,详见官方文档

安装迁移插件

在网站文件夹根目录下执行:

1
npm install hexo-migrator-wordpress --save

导出Wordpress备份

在Wordpress后台-Tools-Export,选择所有内容,导出,得到一个xml后缀的文件

导入

在网站文件夹根目录下执行:

1
hexo migrate wordpress xml文件存放路径

完成后,在source文件夹下会生成_posts文件夹,里面是每个文章的markdown(.md)文件。每个页面也会单独生成一个文件夹,其中的index.md是页面的markdown文件。
需要注意的是,虽然导入时生成的md文件已经相当不错,但肯定达不到完美的程度,最好还是把每篇文章和页面过一遍,看看是否有问题。

如果是根据本文一步步操作的,有这么几个点需要注意:

  • 用markdown语法包裹的斜体粗体加粗斜体,如果文字中含有括号,会被打乱,需要将括号内外的文字分别包裹
  • 如果Wordpress中的图片托管在Wordpress所在的服务器上(图片URL是<your-domain.com>/wp-content/uploads/开头的),需要根据官方文档重新整理图片资源,或者参考下方中的PicGo图床部分,将图片托管在自己的图床上,并批量修改链接

Cloudflare托管

大善人Cloudflare推出了网站托管服务Pages,和Workers加起来每天共用10万次的免费访问额度,对于个人博客来说完全够用了。如果不够用的话,建议您这么大个站点挂广告赚钱吧……
基本思路是,将Hexo的代码提交到Github的私人仓库,在Cloudflare Pages中拉取仓库代码并自动运行hexo g构建出静态页面,生成的页面由Cloudflare托管。

Github托管代码

如果你能熟练使用Git,可以直接跳到自动部署。新手的话建议按照下面的步骤操作。(当然首先你得注册一个Github账号)

安装Github Desktop

进入官网下载安装即可。安装之后的登录部分不再赘述。

创建仓库

点击菜单中的File-Add local repository

在弹出的窗口中,选择网站文件夹所在的目录:

这时会提示当前目录还不是一个Git仓库。点击create a repository

什么都不用改,直接创建:

创建完之前的弹出窗口还在,点击取消即可。

随后点击Publish repository,将仓库提交到Github。记得勾选Keep this code private,不要公开仓库:

更新

以后每次更新了代码,都只需要进入Github Desktop,先提交更改,再Publish repository即可:

Cloudflare自动部署

接下来,来到Cloudflare的部分。首先还是得保证你已经注册了一个Cloudflare免费账号。

创建Pages项目

进入Cloudflare控制台,找到左侧的Pages标签,点击Create a project,选择Connect to Git

按照指引绑定你的Github账号,随后会列出你Github账户中的仓库。选择网站的仓库,点击Begin setup(截图中已经创建了,所以显示In use):

在设置界面,注意更改下面几个设置:

  • Build command: 填写hexo generate
  • Build output directory: 填写public
  • Environment variables: 添加一条,Variable name填写NODE_VERISONValue填写18.15.0 (node的版本号,尽量和你自己电脑上的node版本一致,可以在终端运行node -v查看)

最后点击Save and Deploy就完成了。完成后,就可以通过控制台显示的网址(通常是xxx.pages.dev)访问了!

原理就是Cloudflare拉取了代码,在一个容器环境中安装了Hexo并执行了hexo g命令,最后托管了生成的public文件夹。

绑定自定义域名

如果你想使用自己的域名访问网站,操作也很简单:

  1. 确保域名已经在Cloudflare下,可以通过Cloudflare控制台的Website查看。如果不知道如果转移,可以搜索相关教程:
  2. 进入Pages的设置页面,选择Custom domains,点击Set up a custom domain
  3. 在下一个界面输入自己的域名,比如我可以输入blog.logiconsole.com,确定,在下一个页面确认无误后点击Activate domain

通过这些操作,Cloudflare帮你做了两件事:

  • 创建一个CNAME的DNS记录,将你自己的域名指向了xxx.pages.dev
  • 创建了一个完全托管的SSL证书

所以理论上,如果你不将域名转移到Cloudflare的话,也可以想办法设置CNAME的DNS记录、解决SSL证书来绑定自定义域名

大功告成

以后每次有更新将代码提交到Github时,都会触发Cloudflare进行一次新的构建和部署。

PicGo图床

个人感觉在Hexo的文件结构中一张张添加图片并引用进文章还是挺麻烦的,Markdown要插入图片,PicGo依然是我的第一选择。
以前我都是使用Github公开仓库来当图床,但是难免有一些图片不方便公开,正好看到一篇知乎好文,可以通过Cloudflare Worker反向代理Github私人仓库,达到私人图床的使用效果,感谢此文作者。可以参考原文,也可以安装下面步骤操作。

新建Github仓库

  1. 在Github新建一个私人仓库,比如imagehost
  2. 进入生成Token页面,点击Generate new token,选择classic
  3. 随便输入Note,Expiration选择No expiration,勾选repo,点击Generate token
  4. 记住生成的token,页面刷新或关闭之后就不再显示了

Cloudflare Worker代理

回到Cloudflare控制台,在Workers中点击Create a Service,填入服务名称(注意服务名称决定了图床的域名,我这里用的是image),其他不变,点击Create service

在新建的服务中,点击右上角的Quick edit,无脑将下面的代码替换全部内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Website you intended to retrieve for users.
const upstream = "raw.githubusercontent.com";

// Custom pathname for the upstream website.
// (1) 填写代理的路径,格式为 /<用户>/<仓库名>/<分支>
const upstream_path = "";

// github personal access token.
// (2) 填写github令牌
const github_token = "";

// Website you intended to retrieve for users using mobile devices.
const upstream_mobile = upstream;

// Countries and regions where you wish to suspend your service.
const blocked_region = [];

// IP addresses which you wish to block from using your service.
const blocked_ip_address = ["0.0.0.0", "127.0.0.1"];

// Whether to use HTTPS protocol for upstream address.
const https = true;

// Whether to disable cache.
const disable_cache = false;

// Replace texts.
const replace_dict = {
$upstream: "$custom_domain",
};

addEventListener("fetch", (event) => {
event.respondWith(fetchAndApply(event.request));
});

async function fetchAndApply(request) {
const region = request.headers.get("cf-ipcountry")?.toUpperCase();
const ip_address = request.headers.get("cf-connecting-ip");
const user_agent = request.headers.get("user-agent");

let response = null;
let url = new URL(request.url);
let url_hostname = url.hostname;

if (https == true) {
url.protocol = "https:";
} else {
url.protocol = "http:";
}

if (await device_status(user_agent)) {
var upstream_domain = upstream;
} else {
var upstream_domain = upstream_mobile;
}

url.host = upstream_domain;
if (url.pathname == "/") {
url.pathname = upstream_path;
} else {
url.pathname = upstream_path + url.pathname;
}

if (blocked_region.includes(region)) {
response = new Response(
"Access denied: WorkersProxy is not available in your region yet.",
{
status: 403,
}
);
} else if (blocked_ip_address.includes(ip_address)) {
response = new Response(
"Access denied: Your IP address is blocked by WorkersProxy.",
{
status: 403,
}
);
} else {
let method = request.method;
let request_headers = request.headers;
let new_request_headers = new Headers(request_headers);

new_request_headers.set("Host", upstream_domain);
new_request_headers.set("Referer", url.protocol + "//" + url_hostname);
new_request_headers.set("Authorization", "token " + github_token);

let original_response = await fetch(url.href, {
method: method,
headers: new_request_headers,
body: request.body,
});

connection_upgrade = new_request_headers.get("Upgrade");
if (connection_upgrade && connection_upgrade.toLowerCase() == "websocket") {
return original_response;
}

let original_response_clone = original_response.clone();
let original_text = null;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;

if (disable_cache) {
new_response_headers.set("Cache-Control", "no-store");
} else {
new_response_headers.set("Cache-Control", "max-age=43200000");
}

new_response_headers.set("access-control-allow-origin", "*");
new_response_headers.set("access-control-allow-credentials", true);
new_response_headers.delete("content-security-policy");
new_response_headers.delete("content-security-policy-report-only");
new_response_headers.delete("clear-site-data");

if (new_response_headers.get("x-pjax-url")) {
new_response_headers.set(
"x-pjax-url",
response_headers
.get("x-pjax-url")
.replace("//" + upstream_domain, "//" + url_hostname)
);
}

const content_type = new_response_headers.get("content-type");
if (
content_type != null &&
content_type.includes("text/html") &&
content_type.includes("UTF-8")
) {
original_text = await replace_response_text(
original_response_clone,
upstream_domain,
url_hostname
);
} else {
original_text = original_response_clone.body;
}

response = new Response(original_text, {
status,
headers: new_response_headers,
});
}
return response;
}

async function replace_response_text(response, upstream_domain, host_name) {
let text = await response.text();

var i, j;
for (i in replace_dict) {
j = replace_dict[i];
if (i == "$upstream") {
i = upstream_domain;
} else if (i == "$custom_domain") {
i = host_name;
}

if (j == "$upstream") {
j = upstream_domain;
} else if (j == "$custom_domain") {
j = host_name;
}

let re = new RegExp(i, "g");
text = text.replace(re, j);
}
return text;
}

async function device_status(user_agent_info) {
var agents = [
"Android",
"iPhone",
"SymbianOS",
"Windows Phone",
"iPad",
"iPod",
];
var flag = true;
for (var v = 0; v < agents.length; v++) {
if (user_agent_info.indexOf(agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}

其中最上方有两个地方需要修改:

  • upstream_path: 格式为/<用户>/<仓库名>/<分支>,比如github用户名为username,上文创建的仓库名为imagehost,分支是main(在Github仓库页面可以看到),则填写/username/imagehost/main/
  • github_token: 上文新建Github仓库中创建的token

修改完后点击Save and deploy

绑定自定义图床域名

如果需要绑定自定义的图床域名,而不是使用默认的xxx.xxx.workers.dev,可以在workers的Triggers界面点击Add Custom Domain

和上文中给网站绑定域名类似,Cloudflare会自动创建一个类型为Worker的dns记录:

配置PicGo

打开PicGo,配置GitHub图床。仓库名分支名Token都是上面步骤提过的。如果绑定了自己的域名,最后一栏填入自定义域名即可。