什么是hexo?

Hexo 是一个快速、简洁且高效的博客框架。 Hexo 使用 Markdown(或其他标记语言)解析文章,在几秒内,即可利用靓丽的主题生成静态网页(html)。

其他博客框架:vuepress , gatsby;前者适合vue开发者,后者适合react开发者。

安装hexo

需要提前准备的工具:vscode, git, node.js;去对应的官网下载即可。

在任意位置新建一个文件夹,右键选择用vscode打开;或者直接打开vscode新建一个文件夹,取一个合适的名字,比如blog;右键文件夹,在当前文件下打开终端,执行以下命令:

1
2
3
4
5
npm install -g hexo-cli //全局安装hexo
hexo init blog //初始化一个博客blog
cd blog //进入到博客文件
npm install //安装所需依赖
hexo server //在本地部署博客

好了,再访问http://localhost:4000就能看到运行在本地的博客了

hexo常用指令

1
2
3
4
hexo clean //用于清理由 Hexo 生成的缓存文件和静态文件(public文件)
hexo generate //可以简写为hexo g,作用是生成静态文件
hexo server //可以简写为hexo s,部署自己的博客到本地,会打包文件但是不会输出实际文件
hexo deploy //部署自己的博客到服务器

项目结构分析

  • node_modules:存放安装到本项目的npm包
  • scaffold:存放md文档模板
    • page.md:页面的模板
    • post.md:博客的模板
  • source:存放博客和页面的文件夹
    • _post:存放博客的文件夹,里面都是md文件,最终会被编译成html文件
    • categories:存放分类页面的文件夹,通常只有一个文件
    • tags:存放标签页面的文件夹,通常只要一个文件
  • theme:存放下载的主题的文件夹
  • _config.yml:博客的配置文件
  • _config.主题名.yml:主题的配置文件,自定义主题配置的地方

更换主题:butterfly

hexo自带的主题也许不是那么好看,没关系,hexo提供了丰富的主题给我们选择,我们可以去官网上查找,或者去GitHub上查找(推荐),下面我们以GitHub为例。

在国内直接访问github很不稳定,需要借助加速器,这里推荐steam++

没有账号的用自己的邮箱注册一个即可,登录后在home主界面搜索hexo themes,查找自己喜欢的主题,这里我们选择butterfly主题。

  • 下载
1
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
  • 在你的博客文件夹的themes文件夹中,就能看到butterfly文件夹,删除.git文件夹。
  • 在博客根目录下新建_config.butterfly.yml文件,复制themes/butterfly文件夹下的_config.yml文件内容,粘贴到该文件,后续自定义博客几乎都在这个配置文件中进行。

新建博客文章

1
hexo new 文章名

执行这条命令之后你就能在source/_post文件夹下看到一个新的md文件,它就是你编写博客的地方,所以在这之前,我们需要学习一下md语法,其实也是非常简单的。

md文件结构分析

  • Front-matter:是 markdown 文件最上方以---分隔的区域,不是写博客的区域,而是配置博客的区域
    • Page-Front-Matter:用于 页面 配置
    • Post Front-matter:用于 文章页 配置

具体是配置什么的直接看文档Butterfly 文檔(二) 主題頁面 | Butterfly

  • 用md语法写博客的区域

修改博客下方的文章链接

在根目录下的_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://www.sanye.blog

修改博客分享方式

1
2
3
4
5
6
7
8
9
share:
# Choose: sharejs / addtoany
# Leave it empty if you don't need share
use: sharejs #选择提供分享功能的服务商,不需要分享功能则置空

# Share.js
# https://github.com/overtrue/share.js
sharejs:
sites: wechat,weibo,qq #选择分享到哪些平台

新建分类

为了方便我们博客文章的查找,我们很自然的会想到给博客分类,执行如下命令:

1
hexo new page categories //本质是在创建一个页面page

你会找到source/categories/index.md这个文件

修改这个文件的Front-Matter部分:

1
2
3
title: 分类  
date: 2018-01-05 00:00:00
type: 'categories'

然后我们在每篇博客的Front-matter部分就可以通过categories属性给文章分类。示例:categories: 'book'

注意:如果我们未创建分类页面,点击分类标签是会报错的,提示找不到该页面。

之后我们点击’分类’,就可以成功跳转到分类页面,但是,我们可以观察到,分类页没有顶部图片,是纯蓝色,不够美观,其实我们只要在source/categories/index.mdFront-matter部分添加一个top_img属性,指定顶部图片的路径即可。

1
2
3
4
5
6
---
title: 文章分类
date: 2024-11-24 21:38:53
type: 'categories'
top_img: /images/cover/江南.png
---

新建标签

我们还可以给每篇文章添加多个标签,执行如下命令:

1
hexo new page tags //本质也是在创建一个页面page

你会找到source/tags/index.md这个文件

修改这个文件的Front-Matter部分:

1
2
title: tags
type: 'tags'

然后我们在每篇博客的Front-matter部分就可以通过tags属性给文章添加标签,一篇文章可以添加多个标签,示例:

1
2
3
4
//要注意缩进,否则可能报错
tags:
- 'html'
- 'css'

注意:如果我们未创建tags页面,点击tags标签是会报错的,提示找不到该页面。

给tags页面添加top_img的方法同上文

新建图库

新建图库的原理和新建分类,标签的原理是相同的,都是新建一个页面

1
hexo new page gallery

你会找到source/gallery/index.md这个文件,这个文件的内容如下:

1
2
3
4
5
<div class="gallery-group-main">
{% galleryGroup name description link img-url %}
{% galleryGroup name description link img-url %}
{% galleryGroup name description link img-url %}
</div>

name:图库名字
description:图库描述
link:连接到对应相册的地址
img-url:图库封面的地址

举个例子:

1
2
3
4
5
6
7
8
9
---
title: gallery
date: 2025-04-24 13:19:19
top_img: false
---
<div class="gallery-group-main">
{% galleryGroup '坏女人' '玛琪玛' '/gallery/mqm' /images/gallery/mqm/5.png %}
{% galleryGroup '宫水三叶' '重度依赖' '/gallery/sanye' /images/gallery/sanye/4.jpeg %}
</div>

top_img: false,的作用是禁用gallery页面的顶部图片。

然后在source/gallery目录下新建其他文件夹,代表不同的图库,比如mqm,然后在这个目录下新建index.md文件

1
2
3
4
5
6
7
8
9
10
---
title: 玛琪玛
aside: false
top_img: false
---
{% gallery %}
![](/images/gallery/mqm/1.png)
![](/images/gallery/mqm/2.png)
![](/images/gallery/mqm/3.png)
{% endgallery %}

aside: false的作用是关闭gallery/mqm页面下的侧边栏

为了让页面更美观,可以引用一个background.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
const wrap = document.querySelector('#body-wrap')
const page = document.querySelector('#page')
const footer = document.querySelector('#footer')
const nav = document.querySelector('#nav')

// 如果是图库目录,则修改样式
if(/(gallery\/\w+)/.test(location.pathname)){
// 底部组件背景图片清空
footer.style.backgroundImage = ''
// 添加透明度
footer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'

page.style.backgroundColor = "rgba(0, 0, 0, 0.5)"
page.firstChild.style.color = "white"

// !important 是 CSS 的语法,无法通过 JavaScript 的 style 属性直接添加
// pageheader.style.backgroundColor = "rgba(0, 0, 0, 0.5) !important";

// 让nav的颜色变透明
nav.style.setProperty("background-color", "rgba(255, 255, 255, 0.5)", "important");
}else{
//如果不是图库目录,则自定义底部footer的背景图片
const index = Math.floor(Math.random() * 2)
const arr = ['/images/cover/云湖少女.png','/images/cover/forest.png']
footer.style.backgroundImage = `url(${arr[index]})`
}

//只要确保背景图片是可repeat的,就能自动实现无限滚动,背景图片自动repeat会解决空白的问题
//自动实现2张图片的横向拼接
if(location.pathname.startsWith('/gallery/mqm')){
wrap.style.background = "url(/images/cover/mqm.png) left top / 100vw auto"
wrap.style.animation = "scroll 20s linear infinite"
}else if(location.pathname.startsWith('/gallery/sanye')){
wrap.style.background = "url(/images/cover/sanye.png) left top / 100vw auto"
wrap.style.animation = "scroll 20s linear infinite"
}else if(location.pathname.startsWith('/gallery/reze')){
wrap.style.background = "url(/images/cover/reze.png) left top / 100vw auto"
wrap.style.animation = "scroll 20s linear infinite"
}
1
2
3
4
5
6
7
8
@keyframes scroll {
from {
background-position: left top;
}
to {
background-position: 100vw top;
}
}

新建视频播放器

其实有了前面几个新键页面的基础,新建一个视频播放页面也是比较简单的,不过这里有几个需要注意的地方

  • 在不使用top_img的情况下,会使用title来替代,为了方便,我们在所有title上都添加返回图标

  • 所有视频共用一个播放页面,通过查询参数判断播放什么视频

  • github上可存储的文件大小不能超过100mb,所以不能上传太大的视频,vercel也是。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    if (/\/animeClips\/play/.test(location.pathname)) {
    //使用URLSearchParams,能很快的从查询参数中取值,前提是你知道有哪些参数
    const urlParams = new URLSearchParams(location.search)
    const title = urlParams.get('title')
    const video = urlParams.get('video')

    document.addEventListener('DOMContentLoaded', () => {
    const v = document.querySelector('#article-container video')
    v.src = `/animeClips/video/${video}.mp4`
    //支持自定义标题
    document.querySelector('.page-title').innerHTML = `<i class="fa-solid fa-arrow-left" onclick="history.go(-1)"></i>${title ? title : video}`
    })
    }else{
    //给标题部分添加返回按钮,使用于所有不使用top_img的页面
    const title = document.querySelector('.page-title')
    if(title){
    let a = title.innerHTML
    title.innerHTML = `<i class="fa-solid fa-arrow-left" onclick="history.go(-1)"></i>`+a
    }
    }
  • 视频播放主要依赖的是video标签,添加controls属性,才能显示交互栏。

  • 在开发环境下,无法拖动进度条,而且拖动进度条不会发送请求,因为没有设置Accept-Ranges: bytes响应头。

  • 但是当我们把视频放到vercel上,并发请求获取,

    请求头如下:

    1
    2
    3
    4
    :method:GET
    :path:/animeClips/video/beautiful.mp4
    :scheme:https
    range:bytes=0- //请求从第 0 字节开始到结尾的视频数据,表示请求整个视频

    响应头如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    accept-ranges:bytes  //告诉客户端(比如浏览器),服务器支持 **字节范围请求**,允许拖动进度条
    access-control-allow-origin:* //允许任何网站通过 <video> 标签跨域访问该视频资源。
    cache-control:public, max-age=0, must-revalidate //max-age=0 + must-revalidate 表示每次使用前都要重新验证是否过期

    content-disposition:inline; filename="beautiful.mp4"//表示浏览器可以直接播放或显示这个文件(而不是下载它)
    content-length:25223717 //视频总大小为约 24MB。配合 Accept-Ranges: bytes,浏览器可以计算进度条长度、拖动位置等
    content-range:bytes 0-25223716/25223717//表示本次请求返回的是整个视频文件的一部分(在这个例子中是全部内容)
    content-type:video/mp4

    etag:"43c53e4feacf1f67320c9d282af73edf"
    last-modified:Fri, 02 May 2025 13:43:23 GMT
    server:Vercel//表明部署的服务提供商是 Vercel。
  • 为了优化视频加载,给video标签添加preload='auto'属性,提示浏览器尽可能多地预加载视频数据。如果服务器支持,并且用户网络条件允许,这将使得视频能够更快地开始播放,并允许用户随意拖动进度条到未缓冲部分。

自定义主页

博客的主页就是博客的首页,如下:

自定义主页的详细内容见官方文档Butterfly 文檔(三) 主題配置 | Butterfly,下面我只介绍我遇到困难的部分。

自定义菜单

_config.butterfly.yml文件中设置:

1
2
3
4
menu:
Home: / || fas fa-home
Tags: /tags/ || fas fa-tags
Categories: /categories/ || fas fa-folder-open

当然这些都是我认为最基础的菜单设置,||前面的表示的是页面的路径,后面表示的是图标

在自定义这部分的时候我遇到了一个问题,就是无论如何配置,菜单上都会有music和video选项,原因是在themes/butterfly_config.yml文件中,music和video选项是默认选中的。Hexo 会自动合并主题中的 _config.yml_config.butterfly.yml 里的配置,如果存在同名配置,会使用_config.butterfly.yml的配置,其优先度较高。参考文档Butterfly 文檔(一) 快速開始 | Butterfly

添加搜索功能

如果能根据本地博客内容实现文章搜索,那实在是太方便了;hexo提供了多种搜索方式,其中本地搜索最容易实现。

前往博客根目录,打开cmd命令窗口执行:

1
npm install hexo-generator-search --save

然后在_config.butterfly.yml文件中配置即可

1
2
3
4
search:
# Choose: algolia_search / local_search / docsearch
# leave it empty if you don't need search
use: local_search

更多搜索方式参考文章:Hexo + Butterfly 键入搜索功能 - 简书以及butterfly官方文档

自定义首页图片

1
2
3
4
5
6
# 禁用顶部图片
disable_top_img: false
# The banner image of index page
index_img: #指定顶部图片
# The height of top_img, eg: 300px/300em/300rem
index_top_img_height: 16rem #指定顶部图片的高度

这三个配置项默认并不在一起,但是我为了方便配置把它们放到了一起。

引入图片

  • 通过网络图片的形式引入

    优点:减小打包体积

    缺点:博客部署在github使用网络图片可能存在跨域问题

  • 通过本地图片引入

    优点: 不存在跨域问题,直接使用本地图片即可。

    引入方式:在根目录下的source文件夹中新建一个images文件夹,然后本地图片都放里面(假设放入了一张图片xxx.jpg),然后博客文章就能通过../images/xxx.jpg的相对路径来访问了。

    而且为了让我们的博客图片无论在本地直接打开博客md文件的时候能正常显示,还是在部署到本地或者远程服务器的时候能正常显示,我们需要用img标签来展示我的图片,而不是使用![]()这种md语法。

B站banner

用过B站的小伙伴应该都注意到了B站首页banner图片的特色,我们也可以用它来装饰自己的博客!

参考文章:Bilibili Dynamic Banner | Akilarの糖果屋

上述文章只给了2中banners,而且迁移到博客步骤比较多,所以我自己折腾了一下,当然也参考了别人的文章,并且部署到了github上:shiyuhuiya/BilibiliBannerToHexo: 把B站banner添加到自己的hexo博客!

自定义社交图标

如上图就是社交图标,可以在_config.butterfly.yml文件的social属性中配置,点击即可跳转到相关网页。

1
2
3
4
social:
fa-brands fa-bilibili: https://space.bilibili.com/660655866?spm_id_from=333.1007.0.0 || Bilibli || '#74C0FC'
fab fa-github: https://github.com/syhy || Github || '#24292e'
fas fa-envelope: mailto:3519450570@qq.com || Email || '#4a7dbe'

书写格式 图标名:url || 描述性文字 || color

图标名可在这个网站上查找:fontawesome,找到满意的图标直接使用即可,甚至不需要下载,非常方便

统计网站访客数目

我们的博客是纯静态网页,没有涉及到与后端的数据交互,但是我们如果想要实现统计网站访客数目的功能,就必须借助后端,那该怎么实现呢?幸运的是,有人愿意免费给我们提供服务器来存储我们网站的访客数据。

不蒜子 - 极简网页计数器 是一个轻量级的网站访问统计工具,它通过简单的 JavaScript 代码嵌入到网页中来实现访问次数的统计。

大致原理

当用户访问包含 Busuanzi 脚本的页面时,该脚本会在用户的浏览器上执行,然后发送一个jsonp请求,请求头中的Referer字段会携带当前网站的url,busuanzi服务器会从Referer字段中提取出网站的域名等信息,并将该域名对应的网站访问次数加1,再返回一串js代码,这串代码中包含了网站被访问次数等信息,然后浏览器会执行这串代码,把返回的信息渲染到页面上。具体原理参考源码

如官网所示,我们只需在网页中引入一个js文件就行,对于butterfly主题的hexo博客,用上这个工具更是简单,我们只需要在主题配置文件中启用这个插件就行:

1
2
3
4
busuanzi:
site_uv: true
site_pv: true
page_pv: true
1
2
3
# 确保开启webinfo,不然也不会显示访客数据
card_webinfo:
enable: true

博客网站访客量虚高的原因。

  • 我们的博客部署在本地localhost:4000,大家都用这个域名测试博客,访客量当然特别高了,部署到自己的网站后就没问题了。

  • 我们的博客网站所在域名之前可能已经使用过busuanzi提供的服务了,但是无论我们是按照官网的指示直接引入js文件,还是在butterfly的主题配置文件中开启相关配置,请求获取的都是官方的js文件,也就是源码,我们可以通过直接在浏览器导航栏输入官方js文件的网址(在官网已给出),从而下载,修改源码来解决这个问题。我们把下载好的源码放在themes/butterfly/source/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
// 我们先分析这三个字段什么意思
// "site_uv": 独立访客数
// "page_pv": 当前页面的访问量
// "site_pv": 站点总访问量
bszs: ["site_pv", "page_pv", "site_uv"],
texts: function (a) {
this.bszs.map(function (b) {
var c = document.getElementById("busuanzi_value_" + b);
if (c) {
switch (b) {
case "site_pv":
c.innerHTML = a[b]-虚高的值;
break
case "page_pv":
//文章链接通常是唯一的,出现二手的概率很小,所以直接使用返回过来的值
c.innerHTML = a[b]
break;
case "site_uv":
c.innerHTML = a[b] - 虚高的值;
break;
}
}
});
},

然后在主题配置文件中修改busuanzi的默认cdn链接,指定为本地文件。

1
2
3
CDN:
option:
busuanzi: /js/busuanzi.js

当然,如果我们的博客因为部署的域名改变了,需要加上先前的访问次数,也可以使用这种方式来实现。

live2D

参考:shiyuhuiya/shizuku: 优化live2d模型shizuku的交互效果

永久链接

Hexo 文章链接默认的生成规则是::year/:month/:day/:title,是按照年、月、日、标题来生成的。

这样的话,生成的链接非常长,而且如果我们的 Markdown 使用中文标题,那就更惨了,URL 一转码,将是一场灾难。

更难受的是如果我们修改了文章的日期或者标题,那么将导致链接改变,别人或者你分享出去的文章就会 404,这非常的蛋疼啊,但是只要使用了hexo-abbrlink插件,便可解决以上出现的问题,不论你如何修改文章的日期、名称,只要不改变 footer-matter 中的 id 值,那么文章链接永远不会变。

  • 安装:

    1
    npm install hexo-abbrlink --save
  • 修改_config.yml配置文件

    1
    2
    ## permalink: :year/:month/:day/:title/
    permalink: posts/:abbrlink.html

如果想要了解更多,去网上搜索资料即可,资料是非常多的。

自定义页脚

butterfly主题配置文件的footer.custom_text属性是支持传入html结构的,而其他footer属性不支持,我们可以选择关掉其他footer属性,把要定义的解构全写footer.custom_text里,可以放入一些彩色图标,添加更多结构,提供更大的自由度。

可以前往iconfont-阿里巴巴矢量图标库寻找自己喜欢的图标,根据说明操作即可。

1
2
3
4
5
6
7
footer:
owner:
enable: false
since: 2024
custom_text: <div id="sanye"><svg class="icon" aria-hidden="true"><use href="#icon-banquan"></use></svg><span>2024 - 2025 By 三叶sanye</span><svg class="icon" aria-hidden="true"><use href="#icon-sanyecao"></use></svg></div><div id="runtime"></div>
# Copyright of theme and framework
copyright: false

加载动画

如果我们的博客部署在GitHub,或者我们的博客打开需要加载较多的网络资源,就会导致我们的博客打开很慢,这个时候通过加入加载动画就能优化读者的阅读体验。

butterfly主题提供了默认的加载动画,但是可能不够美观,这时候我们就可以根据自己的喜好自定义了。

自定义loading的大概步骤:

  • 修改loading.pug
  • 修改loading.styl

参考文章:多种加载动画

我在做自己的博客的时候,并没有完全跟着博客敲代码,而是先大致了解一下代码的逻辑,然后只选择了其中的一种加载动画—钢铁之心,所以省去了很多步骤,如果有像我一样不喜欢加载完成后展开的过渡效果的,可以对source/css/_layout/loading.styl文件做如下修改:

1
2
3
4
5
6
7
8
&.loaded
display none
.loading-left-bg
transition all 1.0s
transform translate(-100%, 0)
.loading-right-bg
transition all 1.0s
transform translate(100%, 0)

意思就是,页面加载完毕之后,直接隐藏loading-box

如果要手动实现加载动画的话,除了一些css代码,核心就是windowload事件;

加载动画能够显示出来,也需要加载并解析它需要的css文件,为了让加载动画尽快显示出来,我们可以选择把这些css代码内联到html文件内,这样渲染加载动画无需等待其他首屏关键css文件,但是我们的博客有许多html页面(每篇文章都是一个独立的html页面),手动内联也太麻烦了…

如果加载动画需要的css代码,是通过link标签引入的,还要等待其他首屏关键css加载解析完毕,才能开始渲染加载动画,也就是说,开始加载css动画了,说明首屏关键css都加载完毕了,可以自己使用3G测试。

修改鼠标样式

修改起来也是很简单的,直接跟着文章敲代码就行。

Hexo鼠标样式修改 | 花猪のBlog

大家也可以自行去查找更多的鼠标样式
上述鼠标样式使用的css文件是网络文件,现在链接好像已经失效了,我们可以选择把鼠标样式图片(.cur文件)下载到本地的themes\butterfly\source\img目录下然后通过绝对路径引入。如果你觉得某个博主的鼠标样式好看,也可以右键检查页面,查看网络日志,获取博主鼠标样式图片的链接然后直接下载到本地。

参考:

hexo+butterfly主题利用css部署网站鼠标指针样式 | JiangnanPsalter

Hexo|Butterfly修改鼠标指针样式 | 珍珠巧克力

SEO优化

提高我们的博客在浏览器中的搜索排名,从而提高我们的博客的曝光量,让更多人看到。

  • 下载插件hexojs/hexo-filter-nofollow: 自动为所有外部链接添加 nofollow 属性。

    通过给博客中引入的外部链接(比如a标签)添加rel="noopener external nofollow noreferrer"属性来优化SEO

    noopener: 防止新打开的页面通过window.opener属性访问原始页面的window对象。

    external: 表示链接指向的是外部网站。

    nofollow: 告诉搜索引擎不要将这个链接视为对目标页面的信任投票;防止页面的链接权重传递给外部网站,有助于保留更多的链接权重;如果你的博客中包含指向低质量或垃圾网站的链接,使用nofollow可以减少这些链接对你的网站造成负面影响的风险。

  • 下载插件Baidu Sitemap generator plugin for Hexo

    下载并配置好后执行hexo g,会自动生成博客的站点地图。需要在百度搜索资源平台注册账号,添加并验证自己的网站,然后提交网站的sitemap.xml(站点地图), 从而提高自己的网站在百度中的SEO

    如果购买的是国外域名,验证可能因超时而失败

  • 下载插件hexojs/hexo-generator-sitemap: Sitemap generator for Hexo.

    下载并配置好后执行hexo g,会自动生成博客的站点地图。然后可以选择去google或者bing的资源平台验证自己的网站并提交站点地图,比较推荐提交到Home - Bing Webmaster Tools,不需要科学上网。

    参考文章:Hexo文章加密 & 搜索引擎优化SEO & 评论系统Twikoo | Yan Zhang’s blog

部署博客到github

部署方法

方法1:可以通过https://[username].github.io的格式来访问部署的资源

  • 仓库名必须为[username].github.io,这样貌似必须通过https://[username].github.io/[username].github.io的方式来访问,其实只需要通过https://[username].github.io的方式来访问就行。

  • 打包产物放到master分支

  • 优点是路径短,更适合用来做博客

方法2:可以通过https://[username].github.io/[repo]的格式来访问部署的资源

  • 可以自定义仓库名
  • 更适合用来展示demo
  • 打包产物放到gh-pages分支

我们其实使用哪种方式都可以,我反而喜欢使用第二种方式,无论如何,还要先下载hexo-deployer-git,这个插件的作用如下:

  • 打包后,初次执行hexo deploy时:
    • 在博客根目录下,创建.deploy_git目录,然后拷贝public目录下的所有文件,到这个目录下
    • 初始化.deploy_git目录为git仓库(git init )
    • 根据_config.yml文件,添加远程仓库origin
    • 自动执行git add .git commitgit push -u origin master(分支可自定义)
  • 后续博客更新,再次打包后,执行hexo deploy操作:
    • 删除.deploy_git目录下除了.git文件的所有文件
    • 拷贝public目录下的所有文件,到这个目录下
    • 然后再执行git add .git commitgit push操作。
  • 总的来说,这个插件的作用就是简化了部署博客时,所需要的git操作,让不懂git操作的小白也能快速部署博客。
1
npm i hexo-deployer-git -D//安装到开发依赖

再在_config.yml文件中进行相关配置

1
2
3
4
deploy:
type: git
repo: https://github.com/shiyuhuiya/shiyuhuiya.github.io.git #写自己的仓库路径
branch: master

然后执行

1
hexo deploy

如果部署失败,可以尝试开启steam++加速github再次部署。

优化方案:因为我们每次修改博客后,重新部署的时候,都要依次执行hexo clean ,hexo g,gulp,hexo deploy,这样未免太繁琐了,其实我们可以在package.json中配置:

1
2
3
4
5
6
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"server": "hexo server",
"deploy":"hexo clean && hexo generate && gulp && hexo deploy"
}

之后只需要执行npm run deploy,这四个命令就会自动按顺序串行执行,非常方便。

参考资料:【干货】Luke教你20分钟快速搭建个人博客系列(hexo篇) | 自动化部署在线编辑统统搞定 | 前端必会!_哔哩哔哩_bilibili

但是部署到github pages有一个明显的缺点,就是加载速度很慢。为什么加载速度很慢呢?因为github pages服务器在国外,离我们较远;距离越远,意味着要经过越多的网络结点,每个节点都会引入额外的延迟,遇到网络拥塞的概率也越大。

自定义github域名

为了能让我们的域名更有个性(不会提高网站的加载速度),我们可以选择自定义域名,这需要我们购买域名;域名购买国内国外的都可以,只要域名解析后对应的服务器不是中国境内的,就不需要备案。可参考国内域名一定要备案吗? - 知乎

国外域名购买流程比较简单,不需要实名认证,比较推荐的是namesilo

但是在namesilo购买域名也有缺点,就是国外域名无法备份,这就意味着无法给国外的域名接入国内的cdn服务,或者说无法将国外的域名映射到国内的服务器。

购买域名后,我们就能自定义我们的域名的dns解析方式;打开域名管理,在我们的域名下添加一条CNAME类型的dns记录,让对自定义域名的解析转变成对用户名.github.io域名的解析

具体域名解析方式已经在《前端面试---网络》一文中介绍,下面只给出几幅图(省略了本地dns服务器递归查询缓存的过程)。

未自定义域名前的请求流程

自定义域名后的请求简化流程,比如我在namesilo网站上购买了sanye.blog这个域名,并在我的域名的dns解析记录下添加一条CNAME记录,让对域名www.sanye.blog域名的解析指向用户名.github.io域名的解析

拿到ip后访问对应的githubpages服务器,请求中携带的hostwww.sanye.blog,因为我们在github上配置了,所以github知道对应的默认域名,然后就从默认域名中提取出用户名,根据用户名返回对应的资源。

添加自定义域名后,对原域名的访问也会重定向到自定义域名。在你的 GitHub Pages 仓库中,GitHub 会生成或更新一个名为 CNAME 的文件,其中包含你的自定义域名(例如 www.sanye.blog)。这个文件告诉 GitHub 你的站点应该通过哪个自定义域名访问。

建议在github pages中开启enfore https这样直接访问自定义域名也不会以http协议加载,而是强制用https协议访问,不会出现“网站不安全”的警告。

具体步骤参考文章:

Github 部署个人网页 | 自定义域名 - 知乎Github 部署个人网页 | 自定义域名 - 知乎

上述这篇文章“购买国内域名就要备份的说法是不准确的”

搞懂自定义域名 - 知乎

然而我并不建议给github pages自定义域名🤣,因为即便自定义了域名,也不会提高我们博客的访问速度,因为我们的博客还是部署在github服务器上的,访问速度还是很慢,为什么要浪费宝贵的域名呢

因为我们是给[用户名].github.io这个仓库自定义域名,这就意味着对[用户名].github.io这个域名的解析都会重定向到我们自定义的域名,这就导致,其他仓库,原本的访问url格式是,https://[username].github.io/[repo]即便不自定义域名,也会被重定向到https://自定义域名/[repo],所以最好还是把博客,迁移到vercel,然后给vercel服务器上的项目自定义域名,参考下文。

如何提高博客的加载速度

部署博客到其他平台

参考使用第三方托管平台部署博客 | Akilarの糖果屋;这篇文章介绍了其他的博客部署平台,
gitee:国内网站,但是目前pages功能停用了,除此之外还有其他问题;

Netlify:国外网站,免费,但是注册麻烦,需要hotmail或者gmail等国外常用邮箱,一顿操作下来发现还是白忙活🥲;

webify:腾讯的产品,要money,没有尝试过;

vercel:重点介绍。

打包静态资源

压缩js,css,html

参考文章:使用gulp压缩博客静态资源 | Akilarの糖果屋

执行hexo g指令后,我们可以看到public文件夹中生成了一系列的静态文件,比如html,css,js这些文件的内容格式是非常容易阅读的,显然,他们是未经过压缩的,我们可以借助打包工具gulp,实现对这些静态资源的打包,然后再部署到网站上去,能在一定程度上提高我们博客的加载速度。

  • 安装Gulp插件:在博客根目录[Blogroot]打开终端,输入:

    1
    2
    npm install --global gulp-cli #全局安装gulp指令集
    npm install gulp --save #安装gulp插件
  • 安装各个下属插件(安装到开发环境),以实现对各类静态资源的压缩:

    • 压缩HTML:

      1
      2
      3
      4
      npm install gulp-htmlclean --save-dev

      # 用gulp-html-minifier-terser可以压缩HTML中的ES6语法
      npm install gulp-html-minifier-terser --save-dev
    • 压缩CSS:

      1
      npm install gulp-clean-css --save-dev
    • 压缩js:

      1
      npm install gulp-terser --save-dev
  • 在博客根目录下新建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
    //用到的各个插件
    var gulp = require('gulp');
    var cleanCSS = require('gulp-clean-css');
    var htmlmin = require('gulp-html-minifier-terser');
    var htmlclean = require('gulp-htmlclean');
    // gulp-tester
    var terser = require('gulp-terser');
    // 压缩js
    gulp.task('compress', () =>{
    // './public/**/*.js'表示从当前目录下的public文件夹开始,匹配所有子目录(后代目录)中的.js文件
    // '!./public/**/*.min.js'是一个否定模式,意味着排除所有已经带有.min.js扩展名的文件
    gulp.src(['./public/**/*.js', '!./public/**/*.min.js'])
    .pipe(terser())
    .pipe(gulp.dest('./public'))
    //压缩后的js文件将会被放置回public文件夹下。
    //注意,这会覆盖原有的非最小化版本的js文件,除非你在前面的步骤中对文件进行了重命名(例如添加.min后缀),
    //以便与原始文件区分开来。
    });
    //压缩css
    gulp.task('minify-css', () => {
    return gulp.src(['./public/**/*.css'])
    .pipe(cleanCSS({
    compatibility: 'ie11'
    }))
    .pipe(gulp.dest('./public'));
    });
    //压缩html
    gulp.task('minify-html', () => {
    return gulp.src('./public/**/*.html')
    .pipe(htmlclean())
    .pipe(htmlmin({
    removeComments: true, //清除html注释
    collapseWhitespace: true, //压缩html
    collapseBooleanAttributes: true,
    //省略布尔属性的值,例如:<input checked="true"/> ==> <input />
    removeEmptyAttributes: true,
    //删除所有空格作属性值,例如:<input id="" /> ==> <input />
    removeScriptTypeAttributes: true,
    //删除<script>的type="text/javascript"
    removeStyleLinkTypeAttributes: true,
    //删除<style>和<link>的 type="text/css"
    minifyJS: true, //压缩页面 JS
    minifyCSS: true, //压缩页面 CSS
    minifyURLs: true //压缩页面URL
    }))
    .pipe(gulp.dest('./public'))
    });
    //注册一个名为'default'的任务。这个任务将会并行运行三个子任务:'compress'、'minify-css'和'minify-html'。
    //然后在控制台执行gulp,就会默认执行default任务
    gulp.task('default', gulp.parallel(
    'compress', 'minify-css', 'minify-html'
    ))

    上述代码中用到了一些gulp的api:

    • gulp.task:第一个参数传入任务名,第二个参数传入一个回调函数,指明任务内容
    • gulp.src:传入一个数组,数组中的元素是文件路径,指定要处理哪些文件,经常作为gulp压缩流程的起点
    • gulp.dest:传入文件输出路径,通常作为gulp压缩流的终点,传入最后一个pipe()操作中
    • gulp.parallel:Gulp 4.x版本引入的一个新特性,它允许指定一组任务以并行方式执行。

压缩图片

其实官方文档Butterfly 文檔(六) 進階教程 | Butterfly也提供了相关的打包方法,本质就是对本地的图片压缩后再使用,减小图片体积。

接入cdn加速

对于cdn,在《前端面试—网络》一文中以及介绍过了,不赘述。

接入cdn加速有几个问题:

  • 首先我们的博客可能部署在vercel或者GitHub,也就是说源服务器都不在国内,所以我们不需要备份,而接入cdn加速就是要国内的cdn服务器缓存我们的博客资源,这就需要花些时间
  • 接入cdn加速要money

可参考文章:Github 部署 | CDN 加速网页,速度嗖嗖的快! - 知乎

在上面的文章中,给我们的域名加速,给cdn服务器配置的源站地址github pages服务器的四个ip地址,查阅资料后发现,其实源站地址指定为用户名.github.io这个域名也是可行且容易理解的,所有形如用户名.github.io的域名都只会被解析到这4个ip地址中的一个也就是说这4个ip可以对应多个域名一个域名也能对应不止一个ip地址,这些 IP 地址通常指向相同的资源。具体来说,这些 IP 地址背后的服务器存储的资源是相同的,但它们可能分布在不同的地理位置或数据中心,以实现负载均衡和提高可用性

给我们的域名接入cdn加速后,会得到一个CNAME,然后我们在我们的域名的dns记录上添加对应的CNAME记录即可。

在阅读butterfly文档的过程中发现竟然提供了cdn加速的方案?了解之后发现是一些国内的网站,主动给一些国外的常用的前端资源提供了cdn加速,并免费提供访问的cdn链接,然而我们的博客生成静态资源显然是享受不到的这种免费的cdn加速的。但是如果你使用到了某些外部链接,可以考虑把他们替换为经过cdn加速的链接 ,从而提高博客加载速度。

Vercel

账号注册

注册账号简单且免费,直接使用github账号登录即可,在国内的访问速度也比github pages要快,并且何以导入git仓库,部署起来非常方便,后续在对应仓库中推送新的提交也会自动部署,省心省力。

部署步骤可参考文章:如何使用 Vercel 部署自己的 Hexo 博客_vercel部署博客-CSDN博客

自定义域名

在Vercel上部署好静态站点之后,会获得哦一个自动分配的域名,以vercel.app结尾,就像githubpages也会给默认域名一样,不幸的是,这个域名被dns污染了(被墙了),有人不想我们这么简单就能访问这个网站,我们无法通过这个自动分配的域名,访问到我们的项目;

但是我们还是有解决办法的,比较推荐的就是自定义域名,按照提示操作即可,又不幸的是,自定义域名使用到的cname-vercel-dns.com也存在被dns污染的问题,对此网上常见的解决办法是把cname-vercel-dns.com替换成固定的ip值,或者替换成cname-china.vercel-dns.com

还有一种方法是借助cloudfare,把对cname-vercel-dns.com的解析工作交给cloudfare,这个工作默认是你购买的域名的服务商提供的。

个人猜测:如果购买的是国内的域名,使用的就是国内提供的dns解析服务,就存在被墙的风险,所以推荐购买国外域名,使用国外的dns解析服务,也许就不会有这个问题,至少博主使用国外的域名暂时没遇到什么问题

可参考文章:Vercel部署个人静态之DNS污染劫持问题_vercel dns-CSDN博客

其中裸域名(根域名)比如sanye.blog指向的是76.76.21.21,我们直接访问这个ip,就等同于登录vercel

其中www.sanye.blog指向的是cname-vercel-dns.com或者76.223.126.88,也等同于直接登录vercel

自动打包

然后其实我们可以直接把项目的源码(比如vue项目)忽略掉node_modules目录后,push到github对应的仓库中去,然后再部署到vercel上去,vercel一般会自动识别vue项目,vercel 在部署时会执行以下步骤:

  1. 克隆代码:从 GitHub 仓库中拉取代码。
  2. 安装依赖:根据配置文件运行 npm installyarn install
  3. 构建项目:运行指定的构建命令(如 npm run build)。
  4. 部署静态文件:将构建后的静态文件部署到其全球 CDN。

从这一点出发的话,其实我们不需要借助hexo-deployer-git来部署我们的项目,因为这个工具只会将我们build后,生产的public文件下的资源部署到github对应的仓库上,这就要求我们手动进行打包(hexo g)。

支持history路由

我们知道,使用history路由的项目是需要后端配合的,但是我们项目部署的位置是vercel服务器,该如何让其配合我们呢?

Vercel 提供了一个简单的配置文件 vercel.json,可以用来指定如何处理路由。以下是一个示例配置:

1
2
3
4
5
6
7
8
{
"routes": [
{
"src": "/(.*)",
"dest": "/index.html"
}
]
}
  • 解释
    • src: "/(.*)":匹配所有的路径。
    • dest: "/index.html":将所有请求重定向到 index.html 文件。
    • 这样,无论用户访问什么路径,Vercel 都会返回 index.html,让前端路由接管。
  • 操作步骤
    1. 在项目的public目录下创建一个名为 vercel.json 的文件(确保打包后vercel.json 文件会在根目录的位置)。
    2. 将上述内容复制到文件中。
    3. 部署项目到 Vercel。

当然我们的博客项目并不需要使用history路由,因为我们的博客项目是多页面应用程序

根据项目部署的位置不同,上述json文件可能也需要做适当的修改,不过无论如何, vercel.json都应该放到服务器的根目录下,下面分享一下我部署项目编写的json文件:

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
{
"routes": [
{
// 所有以/bigEvent/assets/开头的请求都会被这个规则处理,而(.*)部分则捕获了/bigEvent/assets/之后的所有字符
"src": "/bigEvent/assets/(.*)",
// 使用$1来引用前面捕获组中的内容,如果请求是/bigEvent/assets/index.js,则$1就是index.js
"dest": "/bigEvent/assets/$1"
},
{
"src": "/bigEvent/favicon.ico",
"dest": "/bigEvent/favicon.ico"
},
{
"src": "/bigEvent/(.*)",
"dest": "/bigEvent/index.html"
},
{
"src": "/rabbit/assets/(.*)",
"dest": "/rabbit/assets/$1"
},
{
"src": "/rabbit/favicon.ico",
"dest": "/rabbit/favicon.ico"
},
{
"src": "/rabbit/(.*)",
"dest": "/rabbit/index.html"
}
]
}

优化加载速度

1
2
3
4
5
6
7
8
//_config.butterfly.yml
CDN:
# The CDN provider for internal and third-party scripts
# Options for both: local/jsdelivr/unpkg/cdnjs/custom
# Note: Dev version can only use 'local' for internal scripts
# Note: When setting third-party scripts to 'local', you need to install hexo-butterfly-extjs
internal_provider: local
third_party_provider: local

我们可以通过下载hexo-butterfly-extjs

1
npm i hexo-butterfly-extjs

并配置third_party_providerlocal,然后我们加载一些第三方的cssjs文件就不需要通过cdn了,因为我们已经把这些第三方文件下载到了本地,我们打包后,可以观察到public目录下多出了pluginsSrc目录。

按照上述的步骤配置后,我们就能明显的发现,开发和生产过程中,网站的加载速度变快了许多。

但是当我们部署博客的时候,发现出错了,并提示”路径 pluginsSrc/twikoo/dist/twikoo.all.min.js:2 存在一个 Tencent Cloud Secret ID”,因为GitHub 实施了一项名为“Push Protection”的安全功能,它会扫描你的提交以防止意外泄露敏感信息(例如 API 密钥、密码等)。在这个案例中,系统发现了 Tencent Cloud Secret ID,并阻止了这次推送。

所以怎么办?我们先cd.deploy_git,执行git reset --hard <上一次提交的哈希值> ,撤销这次包含敏感信息的提交(还有一种方法,就是直接删除.deploy_git目录,不过这样你之前的提交记录都没了),然后由于我们每次打包后,public目录下都会包含这个twikoo.all.min.js文件,所以我们需要自动删除它。

source目录下新建del.js文件:

1
2
3
4
5
6
7
8
const fs = require('fs');
const path = require('path')
try {
fs.unlinkSync(path.join(__dirname,'pluginsSrc/twikoo/dist/twikoo.all.min.js'));
console.log('文件删除成功');
} catch (err) {
console.error('文件删除失败:', err);
}

然后这个文件就会被打包到public目录的根目录下。

然后我们还要修改一下部署指令,每次部署的时候,自动删除这个文件

1
2
3
4
5
6
7
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"server": "hexo server",
"del": "node ./public/del.js",
"deploy": "hexo clean && hexo generate && npm run del && gulp && hexo deploy"
},

其他加速vercel的方法参考:Vercel 加速,快,不止更快 | Mystic Stars

感想与结语

hexo+butterfly框架能帮助我们迅速开发一个美观实用的博客,这些框架,主题,以及相关插件的诞生,基于开发者雄厚的html,css,js等前端知识积累;做为刚入们的前端开发者,我也知道给光看文档能学到的实在是有限,在网上找资料学到的也是偏向实践,想要深入了解原理,自己做出这些效果,还得多学多练,打好自己前端基础。

如果你还想进一步美化自己的博客,给自己的博客添加更多的功能,你可以选择不断的阅读butterfly官方文档,也可以去网上查找更多的资料,以下是我推荐的一些资料,希望能帮助到你。