Hexo + Github Pages 搭建个人博客2 优化篇 SEO及图床
Hexo + Github Pages 搭建个人博客2 优化篇 SEO及图床
SEO(搜索引擎优化)可以提升博客在 Google、Bing、百度 等搜索引擎的排名,提高曝光度,吸引更多访客。
在搜索引擎输入以下内容:
1 | # 查看是否被收录 |
如果提示
抱歉,未找到相关结果。
即还未收录。1 网站收录
理论上,只要我们的网站网站可被外部访问,内容有价值,搜索引擎都会通过爬虫自动发现并收录,但是对于我们的新网站可能会比较慢。手动提交 Sitemap 能加快收录。
1.1 在搜索引擎添加站点
- 百度
- 注册并登录百度站长平台
- 添加站点。用户中心->站点管理->添加站点,输入你的域名
- 验证域名
以 HTML 标签验证为例子,复制标签到首页的header区域,由butterfly主题集成了这个功能,只需要将content 的值复制到
主题配置文件_config.butterfly.yml
中的site_verification
字段1
2
3
4
5
6
7# Baidu Webmaster tools verification.
# See: https://ziyuan.baidu.com/site
site_verification:
# - name: google-site-verification
# content: xxxxxx
- name: baidu-site-verification
content: # 在这里填上面的字符串 - 网站重新部署完后,在百度站长平台完成 HTML 标签验证即完成添加。
- 谷歌收录
- 注册并登录Google Search Console
- 添加站点,大体同上。
- 输入网址并验证,以“网址前缀” 为例子,输入网址 https://youwebsite.top ,验证方式可选择 HTML 标签验证,方法同百度验证。
1.2 生成站点地图(sitemap)
站点地图可以告诉搜索引擎网站上有哪些可供抓取的网页,以便搜索引擎可以更加智能地抓取网站。
- 安装百度和 Google 的站点地图生成插件
1
2npm install hexo-generator-baidu-sitemap --save
npm install hexo-generator-sitemap --save - 在配置文件
config.yml
添加以下字段:3 hexo g -d 重新部署并访问以下URL,看看网页中有没有出现代码。有的话就成功1
2
3
4
5# 站点地图
sitemap:
path: sitemap.xml
baidusitemap:
path: baidusitemap.xml虽然搜索引擎会自动爬取网站内容,但手动提交 sitemap.xml 更快发现你的博客,并提高收录效率。1
2https://你的域名/sitemap.xml
https://你的域名/baidusitemap.xml
1.3 提交链接
- 谷歌
在 Search Console
-> Sitemaps
页面,提交 上一步获得sitemap,即https://你的域名.com/sitemap.xml
- 百度
百度站长平台的链接提交方式分为自动提交和手动提交两种,此处只讲自动提交,手动提交按照要求操作即可
主动推送最为快速的提交方式,是被百度收录最快的推送方式。
- 主动推送可以通过安装插件实现
1
2npm install hexo-baidu-url-submit --save
- 在 hexo 根目录配置文件_config.yml 中,添加其次,记得查看 hexo 根目录中_config.yml 文件中 url 的值, 必须包含是百度站长平台注册的域名
1
2
3
4
5
6
7# 主动推送百度,被百度收录
baidu_url_submit:
count: 10 # 提交最新的10个链接
host: # 百度站长平台中注册的域名
token: # 秘钥,百度站长平台 > 普通收录 > 推送接口 > 接口调用地址中token字段
path: baidu_urls.txt # 文本文档的地址, 新链接会保存在此文本文档里,不用改
deploy 加入新的 type1
2
3# URL
## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'
url: https://kdsunset.top若要实现手动提交,则把下面的代码粘贴到百度站长平台的 “手动收录” 地址窗口即可1
2
3
4
5
6
7# Deployment
## Docs: https://hexo.io/docs/one-command-deployment
deploy:
- type: git
repo: [email protected]:kdsunset/kdsunset.github.io.git
branch: main
- type: baidu_url_submitter1
2https://你的域名/sitemap.xml
https://你的域名/baidusitemap.xml
1.4 robots
robots.txt 是一个网站爬虫协议文件,用于告诉搜索引擎哪些页面可以爬取,哪些页面不能爬取,从而控制搜索引擎的行为。例如通常我们希望搜索引擎爬取我们的文章页面,而目录、关于页这些可以忽略,则可以在这个文件指定。
- 博客根目录下的 source 文件夹新建robots.txt文件,填写以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# hexo robots.txt
# 允许所有用户代理的浏览器爬虫进行访问(爬取数据)
User-agent: *
Allow: /
Allow: /posts/
Disallow: /tags/
Disallow: /categories/
Disallow: /about/
Disallow: /archives/
# 如果js和fonts这些加了disallow的话,会出现谷歌抓取问题的话,就取消
Disallow: /js/
Disallow: /css/
Disallow: /fonts/
Disallow: /lib/
# 最后面两行是site-map
Sitemap: https://kdsunset.top/sitemap.xml
Sitemap: https://kdsunset.top/baidusitemap.xml - 将
robots.txt
加到heox
配置的skip_render
字段,避免渲染。
通常Hexo会将解析 Markdown 文件并渲染成html,skip_render
则可以指定需要忽略的内容,避免格式错误。
在博客根目录下的配置文件_config.yml
中的skip_render
字段添加以下内容:
1 | # 跳过渲染 |
2 图床
我们写的博客文章可能使用到很多图片,如果图片直接放在Hexo目录下,会占用仓库空间,而且GitHub Pages
有容量限制。另外每次 Hexo
部署都会重新上传所有图片,影响效率。因此比较好的方案是使用图床。
图床方案可以分为第三方图床和自建图床两大类。国内诸如xx云的服务商大都提供了图片存储服务,但是质量好的基本都要付费。自建方面,由于gitee
现在不支持图床外链作为公共仓库;jsdelivr
国内被墙。因此我们选择Github
私有仓库+Cloudflare Workers
搭建个人图床,Github私有仓库来负责存储图片,通过Cloudflare Workers
代理访问GitHub Raw
文件,并且提供 CDN 缓存。
2.1 Github部分
2.1.1 Github建立一个仓库
新建仓库作为图床,由于使用了Cloudflare反向代理了github仓库,可以选择私有仓库,文件不会暴露,增加安全性。这里以建立imagehosting
仓库为例
2.1.2 生成Github Tokens
GitHub Personal Access Tokens
(个人访问令牌)是一种用于进行身份验证的安全凭证,允许您通过命令行或应用程序与 GitHub 进行交互。这是一种替代使用用户名和密码的方式,通常用于通过 API 访问 GitHub 资源或执行与 GitHub 相关的操作。
- 点击Github 用户设置setting->Developer Settings->Personal access tokens 进行仓库,字段可按如下填写,然后点击生成。注意这个token只展示一次,所以请复制下来并保存
1
2
3Note: # token 标识,随意
Expiration: # 过期时间,自行选择,可选No expiration(永久)
Select scopes: # 选择权限范,勾选 repo
2.2 Cloudflare 部分
2.2.1 创建用于代理的Worker
- Cloudflare 的管理界面后,点击侧边栏的 “Workers” 选项,然后点击 “创建服务” 创建一个 Worker,修改名称并部署.
- 点击编辑,粘贴以下代码,并将代理路径和Github令牌修改为自己的值。
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
190
191
192
193//这段代码作用:1.反向代理了github仓库。
//2.使用令牌获取文件。
//3开启了缓存,避免重复请求图片。
// 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,
});
let 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;
}
2.2.2 将域名 NS 转到 Cloudflare
Cloudflare Workers 的域名绑定仅支持托管在 Cloudflare 上的域名,所以得先将域名的 NS 转到 Cloudflare。如果使用了Cloudflare进行CDN加速,这一步已经设置过了,没有则参考Hexo教程第一篇。
2.2.3 给创建的 Worker 服务绑定自己的域名
域名 NS 转到 Cloudflare成功后,在 Worker 服务的详情页点击“触发器”,然后点击“添加自定义域”,输入想要绑定的域名后,点击“添加自定义域”。这里以kdsunset.top域名为例子,可以填写自定义域名为”img.kdsunset.top”
2.4 配置 picgo 图床软件
PicGo 是一款开源的图片上传工具,主要用于将本地图片上传到云存储服务,并生成可访问的链接。
按以下描述填写Github仓库信息:
1 | 仓库名:<Github用户名/仓库名> |
成功上传图片后,图片链接为:
1 |  |
3 其他
3.1 给博客图片添加水印
使用hexo-images-watermark插件,这个插件不对原图产生任何影响,在网站静态页构建过程中将原图读取,输出添加了水印的图片。但是目前这个插件只支持本地的source/_post下的图片生成水印,不支持网络图片。
1. 安装sharp
和hexo-images-watermark
1 | # 这个插件依赖`sharp` |
这个插件依赖sharp
,npm
安装 sharp
时,必须先安装 windows-build-tools
,如果还没安装,可以选择从Visual Studio官网,选择“用于 Visual Studio 的工具”->Visual Studio 2022 生成工具,下载安装包,并选择安装C++ 桌面工具。
2 在hexo主配置文件,添加:
1 | watermark: |
3.2 静态资源压缩
静态资源压缩是指对 Hexo生成如HTML、CSS、JavaScript和图片等静态文件进行压缩,以减小文件大小,提高网页加载速度,节省带宽资源的目的。这主要包括对 、图片等静态文件的压缩。
Gulp 是一种流行的 JavaScript 任务自动化工具,广泛用于 Web 开发中的构建和自动化任务,如压缩 CSS、JavaScript 文件、优化图片、自动刷新浏览器等。
- 常用gulp 插件:
- gulp-htmlclean:清理html
- gulp-htmlmin:压缩html
- gulp-minify-css:压缩css
- gulp-uglify:混淆js
- gulp-imagemin:压缩图片
- 安装gulp
1
2
3
4
5
6# 项目根目录安装gulp
npm install gulp --save-dev
# 安装gulp 模块(项目目录)
npm install gulp-htmlclean gulp-htmlmin gulp-minify-css gulp-uglify gulp-imagemin --save - 在Hexo博客根目录下新建 gulpfile.js ,并填入以下内容3.生成博文时执行gulp命令就会根据 gulpfile.js 中的配置,对 public 目录中的静态资源文件进行压缩。重新部署即可看到css文件等被压缩了(不生效清一下浏览器缓存)
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
53var gulp = require('gulp');
var minifycss = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var htmlmin = require('gulp-htmlmin');
var htmlclean = require('gulp-htmlclean');
// var imagemin = require('gulp-imagemin');
// 压缩html
gulp.task('minify-html', function() {
return gulp.src('./public/**/*.html')
.pipe(htmlclean())
.pipe(htmlmin({
collapseWhitespace: true, //从字面意思应该可以看出来,清除空格,压缩html,这一条比较重要,作用比较大,引起的改变压缩量也特别大
collapseBooleanAttributes: true, //省略布尔属性的值,比如:<input checked="checked"/>,那么设置这个属性后,就会变成 <input checked/>
removeComments: true, //清除html中注释的部分
removeEmptyAttributes: true, //清除所有的空属性
removeScriptTypeAttributes: true, //清除所有script标签中的type="text/javascript"属性。
removeStyleLinkTypeAttributes: true, //清楚所有Link标签上的type属性。
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}))
.pipe(gulp.dest('./public'));
});
// 压缩css
gulp.task('minify-css', function() {
return gulp.src('./public/**/*.css')
.pipe(minifycss({
compatibility: 'ie8'
}))
.pipe(gulp.dest('./public'));
});
// 压缩js !代表排除的js,例如['!./public/js/**/*min.js']
gulp.task('minify-js', function() {
return gulp.src(['./public/js/**/.js'])
.pipe(uglify()) //压缩混淆
.pipe(gulp.dest('./public'));
});
// 压缩图片
//已在在上传时压缩
// gulp.task('minify-images', function() {
// return gulp.src('./public/images/**/*.*')
// .pipe(imagemin(
// [imagemin.gifsicle({'optimizationLevel': 3}),
// imagemin.jpegtran({'progressive': true}),
// imagemin.optipng({'optimizationLevel': 7}),
// imagemin.svgo()],
// {'verbose': true}))
// .pipe(gulp.dest('./public/images'));
// });
// 默认任务
gulp.task('default',gulp.series(gulp.parallel('minify-html','minify-css','minify-js')));1
2
3echo "Starting Hexo cmd"
hexo clean && hexo g && gulp && hexo d
echo "Hexo deploy success !"
3.3 文章目录禁用自动编号
默认情况下,butterfly会对文章目录自动编号,这可能跟我们自定义的目录标题冲突,可通过设置butterfly配置文件的top字段进行配置:
1 | toc: |
3.4 开启内置的404页面
修改enable开启
1 | # A simple 404 page |
修改页面的样式,打开butterfly主题文件/layout/include/404.pug
1 | - var top_img_404 = theme.error_404.background || theme.default_top_img |
3.5 解决国内线路 valine 评论区无法加载的问题
经过F12控制台定位是Valine.min.js引用的av-min.js 无法下载导致,原来的url
1 | //unpkg.com/leancloud-storage@3/dist/av-min.js |
替换成其他cdn,对于butterfly主题,替换方式是修改主题配置文件中的inject字段,插入饿了么的cnd链接
1 | inject: |
3.6 添加RSS订阅
RSS用来订阅网站的最新文章、新闻和博客更新,相比算法推荐,它相当的古老,但是既然博客还存在,RSS订阅也一定有存在的理由。
1 安装RSS生成插件
1 | # 安装hexo-generator-feed(https://github.com/hexojs/hexo-generator-feed) |
2 配置hexo,添加feed字段
在 hexo-blog/_config.yml 文件中,找到 feed 配置部分(如果没有,请添加)
1 | feed: |
3 在 Butterfly 主题中添加 RSS 链接
在Butterfly 主题的配置文件_config.butterfly.yml
中menu字段增加:
1 | menu: |
4 发布更新
重新部署后,可以看到在首页menu菜单中增加了RSS选项,点击得到类似https://your-blog.com/atom.xml
的网页。使用方式是,在RSS 订阅平台例如Feedly、inoreader(有国内版本)、The Old Reader。
例如inoreader,注册完成后在add feed
->Website
中输入刚才的atom.xml网站,即可添加订阅。
3.7 发布原生网页
有时我们写的不是md文章,也不需要butterfly主题的网页布局,而是想直接发布一个自定义样式的html网页,可以通过指定跳过渲染实现。
在hexo/source
中创建or粘贴你需要的html网页,如果是子目录则对应网站二级目录,例如raw/index.html
然后再hexo配置中,添加跳过渲染:
1 | skip_render: |
注意如果网页使用到了外部js,需要从\themes\butterfly\source\js
目录下引入。
重新部署后可以通过https://your-blog.com/raw/index.html
访问。