Hexo + Github Pages 搭建个人博客2 优化篇 SEO及图床

SEO(搜索引擎优化)可以提升博客在 Google、Bing、百度 等搜索引擎的排名,提高曝光度,吸引更多访客。
在搜索引擎输入以下内容:

1
2
# 查看是否被收录
site:你的网站

如果提示抱歉,未找到相关结果。即还未收录。

1 网站收录

理论上,只要我们的网站网站可被外部访问,内容有价值,搜索引擎都会通过爬虫自动发现并收录,但是对于我们的新网站可能会比较慢。手动提交 Sitemap 能加快收录。

1.1 在搜索引擎添加站点

  • 百度
  1. 注册并登录百度站长平台
  2. 添加站点。用户中心->站点管理->添加站点,输入你的域名
  3. 验证域名
    以 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: # 在这里填上面的字符串
  4. 网站重新部署完后,在百度站长平台完成 HTML 标签验证即完成添加。
  • 谷歌收录
  1. 注册并登录Google Search Console
  2. 添加站点,大体同上。
  3. 输入网址并验证,以“网址前缀” 为例子,输入网址 https://youwebsite.top ,验证方式可选择 HTML 标签验证,方法同百度验证。

1.2 生成站点地图(sitemap)

站点地图可以告诉搜索引擎网站上有哪些可供抓取的网页,以便搜索引擎可以更加智能地抓取网站。

  1. 安装百度和 Google 的站点地图生成插件
    1
    2
    npm install hexo-generator-baidu-sitemap --save
    npm install hexo-generator-sitemap --save
  2. 在配置文件config.yml添加以下字段:
    1
    2
    3
    4
    5
    # 站点地图
    sitemap:
    path: sitemap.xml
    baidusitemap:
    path: baidusitemap.xml
    3 hexo g -d 重新部署并访问以下URL,看看网页中有没有出现代码。有的话就成功
    1
    2
    https://你的域名/sitemap.xml
    https://你的域名/baidusitemap.xml
    虽然搜索引擎会自动爬取网站内容,但手动提交 sitemap.xml 更快发现你的博客,并提高收录效率。

1.3 提交链接

  • 谷歌

Search Console-> Sitemaps 页面,提交 上一步获得sitemap,即https://你的域名.com/sitemap.xml

  • 百度

百度站长平台的链接提交方式分为自动提交和手动提交两种,此处只讲自动提交,手动提交按照要求操作即可
主动推送最为快速的提交方式,是被百度收录最快的推送方式。

  1. 主动推送可以通过安装插件实现
    1
    2
    npm install hexo-baidu-url-submit --save

  2. 在 hexo 根目录配置文件_config.yml 中,添加
    1
    2
    3
    4
    5
    6
    7
    # 主动推送百度,被百度收录
    baidu_url_submit:
    count: 10 # 提交最新的10个链接
    host: # 百度站长平台中注册的域名
    token: # 秘钥,百度站长平台 > 普通收录 > 推送接口 > 接口调用地址中token字段
    path: baidu_urls.txt # 文本文档的地址, 新链接会保存在此文本文档里,不用改

    其次,记得查看 hexo 根目录中_config.yml 文件中 url 的值, 必须包含是百度站长平台注册的域名
    1
    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
    deploy 加入新的 type
    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_submitter
    若要实现手动提交,则把下面的代码粘贴到百度站长平台的 “手动收录” 地址窗口即可
    1
    2
    https://你的域名/sitemap.xml
    https://你的域名/baidusitemap.xml

1.4 robots

robots.txt 是一个网站爬虫协议文件,用于告诉搜索引擎哪些页面可以爬取,哪些页面不能爬取,从而控制搜索引擎的行为。例如通常我们希望搜索引擎爬取我们的文章页面,而目录、关于页这些可以忽略,则可以在这个文件指定。

  1. 博客根目录下的 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
  2. robots.txt加到heox配置的skip_render字段,避免渲染。

通常Hexo会将解析 Markdown 文件并渲染成html,skip_render则可以指定需要忽略的内容,避免格式错误。
在博客根目录下的配置文件_config.yml中的skip_render字段添加以下内容:

1
2
3
# 跳过渲染
skip_render:
- 'robots.txt'

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 相关的操作。

  1. 点击Github 用户设置setting->Developer Settings->Personal access tokens 进行仓库,字段可按如下填写,然后点击生成。注意这个token只展示一次,所以请复制下来并保存
    1
    2
    3
    Note: # token 标识,随意
    Expiration: # 过期时间,自行选择,可选No expiration(永久)
    Select scopes: # 选择权限范,勾选 repo

2.2 Cloudflare 部分

2.2.1 创建用于代理的Worker

  1. Cloudflare 的管理界面后,点击侧边栏的 “Workers” 选项,然后点击 “创建服务” 创建一个 Worker,修改名称并部署.
  2. 点击编辑,粘贴以下代码,并将代理路径和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
2
3
4
5
仓库名:<Github用户名/仓库名>
分支名:现在GitHub默认主分支是 "main",以前是"master"
token:Github token,github->settings->tokens->Generate new token
存储路径:设置文件夹目录,可选,例如"img/",
自定义域名:自定义域名,可选,我的配置是https://img.kdsunset.top

成功上传图片后,图片链接为:

1
![](https://img.kdsunset.top/img3031c4ae8f0be9933017b40af93dda1.png)

3 其他

3.1 给博客图片添加水印

使用hexo-images-watermark插件,这个插件不对原图产生任何影响,在网站静态页构建过程中将原图读取,输出添加了水印的图片。但是目前这个插件只支持本地的source/_post下的图片生成水印,不支持网络图片。
1. 安装sharphexo-images-watermark

1
2
3
4
# 这个插件依赖`sharp`
npm install sharp

npm install hexo-images-watermark

这个插件依赖sharpnpm 安装 sharp 时,必须先安装 windows-build-tools ,如果还没安装,可以选择从Visual Studio官网,选择“用于 Visual Studio 的工具”->Visual Studio 2022 生成工具,下载安装包,并选择安装C++ 桌面工具。

2 在hexo主配置文件,添加:

1
2
3
4
5
watermark:
enable: true
textEnable: true
rotate: -45
gravity: centre

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:压缩图片
  1. 安装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

  2. 在Hexo博客根目录下新建 gulpfile.js ,并填入以下内容
    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
    var 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')));

    3.生成博文时执行gulp命令就会根据 gulpfile.js 中的配置,对 public 目录中的静态资源文件进行压缩。重新部署即可看到css文件等被压缩了(不生效清一下浏览器缓存)
    1
    2
    3
    echo "Starting Hexo cmd"
    hexo clean && hexo g && gulp && hexo d
    echo "Hexo deploy success !"

3.3 文章目录禁用自动编号

默认情况下,butterfly会对文章目录自动编号,这可能跟我们自定义的目录标题冲突,可通过设置butterfly配置文件的top字段进行配置:

1
2
3
toc:
enable: true
number: false # 自动编号

3.4 开启内置的404页面

修改enable开启

1
2
3
4
5
# A simple 404 page
error_404:
enable: true
subtitle: '页面沒有找到'
background: https://i.loli.net/2020/05/19/aKOcLiyPl2JQdFD.png

修改页面的样式,打开butterfly主题文件/layout/include/404.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- var top_img_404 = theme.error_404.background || theme.default_top_img

body(style='background-image: url(' + url_for(theme.default_top_img) + '); background-size: cover; background-position: center center;')

#body-wrap.error404
include ./header/index.pug

#error-wrap
.error-content
.error-img
img(src=url_for(top_img_404) alt='Page not found')
.error-info
h1.error_title= '404'
.error_subtitle= theme.error_404.subtitle || _p('error404')

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
2
3
4
5
6
7
inject:
head:
# - <link rel="stylesheet" href="/xxx.css">
- <link rel="stylesheet" href="/css/my_bg_color.css">
bottom:
- <script src="//github.elemecdn.com/leancloud-storage@3/dist/av-min.js"></script>
# - <script src="xxxx"></script>

3.6 添加RSS订阅

RSS用来订阅网站的最新文章、新闻和博客更新,相比算法推荐,它相当的古老,但是既然博客还存在,RSS订阅也一定有存在的理由。
1 安装RSS生成插件

1
2
# 安装hexo-generator-feed(https://github.com/hexojs/hexo-generator-feed)
npm install hexo-generator-feed --save

2 配置hexo,添加feed字段
在 hexo-blog/_config.yml 文件中,找到 feed 配置部分(如果没有,请添加)

1
2
3
4
5
6
7
8
9
10
11
12
feed:
enable: true # 启用 RSS 订阅功能
type: atom # 订阅源格式,可选值:atom、rss2
path: atom.xml # 订阅源的生成路径(默认 `atom.xml`,最终访问路径为 `https://your-blog.com/atom.xml`)
limit: 20 # 订阅源中最多包含的文章数量,默认 20(可根据需求调整)
hub: # WebSub(以前的 PubSubHubbub)通知中心的 URL(通常留空)
content: true # 是否在订阅源中包含文章正文内容
content_limit: 140 # 文章内容的最大字数限制(默认 140,可调整或设为 0 以显示全文)
content_limit_delim: ' ' # 文章内容截断时使用的分隔符(默认为空格)
order_by: -date # 文章排序方式,`-date` 表示按发布时间倒序排列
icon: /img/avatar.jpg # RSS 订阅图标(建议使用博客的 LOGO 或头像)
autodiscovery: true # 启用自动发现功能,使浏览器或 RSS 阅读器可以自动检测订阅源

3 在 Butterfly 主题中添加 RSS 链接
在Butterfly 主题的配置文件_config.butterfly.yml中menu字段增加:

1
2
menu:
RSS: /atom.xml

4 发布更新
重新部署后,可以看到在首页menu菜单中增加了RSS选项,点击得到类似https://your-blog.com/atom.xml的网页。使用方式是,在RSS 订阅平台例如Feedlyinoreader(有国内版本)、The Old Reader
例如inoreader,注册完成后在add feed->Website中输入刚才的atom.xml网站,即可添加订阅。

3.7 发布原生网页

有时我们写的不是md文章,也不需要butterfly主题的网页布局,而是想直接发布一个自定义样式的html网页,可以通过指定跳过渲染实现。
hexo/source中创建or粘贴你需要的html网页,如果是子目录则对应网站二级目录,例如raw/index.html
然后再hexo配置中,添加跳过渲染:

1
2
3
skip_render:
- 'robots.txt'
- 'raw/index.html'

注意如果网页使用到了外部js,需要从\themes\butterfly\source\js目录下引入。
重新部署后可以通过https://your-blog.com/raw/index.html访问。