低成本个人博客搭建全流程
vuepress-hope主题+github pages托管+github仓库图床+namesilo域名=低成本个人博客
个人平时喜欢写点东西,零零散散的也有不少,一直在找一款合适的笔记托管软件。最开始用的typora,很舒服但是可惜缺少同步功能,后来又用过语雀,飞书和notion这样的云文档,总体上体验都很好,尤其是notion现在也是我做个人笔记的首选。但是感觉自己的个人思考总归是比较局限的,后面开始在知乎上写过一些文章,但是知乎终归是个大众平台,内容鱼龙混杂容易分散注意力,而且markdown编辑器说实话也不好用,后来也尝试过掘金之类的技术论坛,但是奇葩的审核机制更加劝退。最终还是决定搭建个属于自己的个人博客,可以作为和志同道合的朋友交流的一个平台,也可以作为展示自己的一个窗口,当然如果博客的内容能够帮助到别的朋友那就更好了。
对于博客我个人的需求如下:
- 学生党,首当其冲成本越低越好;
- 静态页面即可,不需要与服务器进行额外的交互;
- 带个人域名,毕竟咱自己的名片;
- 需要有图床,不然markdown图片是个头疼的问题;
- 最好能托管在github上,降低运维负担。
前置知识:
- 熟悉markdown语法,写博客的前提;
- 熟悉json/yaml语法,修改配置文件;
- 一点点的前端知识(css/ts/js),用来自定义样式。
博客页面相关
确定博客主题
早期的github貌似只支持Jekyll主题,后来github引入action后基本上主流的博客主题框架都可以用了。目前静态博客框架主要包括以下几种:
- Jekyll:由Ruby实现,原生支持github pages;缺点是主题数不是很多,而且貌似页面变多构建速度会很慢;
- Hexo:由Node.js实现,性能很好,部署方便,主题丰富,还是中文社区;
- Hugo:由Go实现,性能也很好,而且主题很丰富;缺点是需要熟悉Golang;
- Vuepress:由Node.js实现,性能很好,部署也很方便,但是主题不是很多。
当然其实以上这些主题框架的优缺点都不太重要,重要的是主题要看对眼,符合自己的审美。我个人比较喜欢简洁清爽一点的,需要带有侧栏目录大纲,最好带一些丝滑的动画,挑来挑去最后相中了vuepress-theme-hope,后文主要就介绍该主题的安装配置。
创建博客项目
准备node.js开发环境
windows下直接去官网下载安装包,直接安装即可,注意选择LTS的版本。
linux下按照官网页面提示的命令安装。
检查是否安装成功:
$ node -v
v18.20.4
启用corepack,corepack是一个Node.js的包管理器,它允许你使用不同的Node.js版本环境和包管理器(如npm、pnpm和yarn,有点类似于anaconda)。
后续会使用pnpm作为包管理工具,因为VuePress主题都是通过pnpm来管理依赖的。
$ corepack enable
$ pnpm -v
9.12.3
pnpm官方源在国外,如果上一步卡住或者失败,可以尝试下换成国内镜像源:
# 查看当前源
$ pnpm get registry
# 更换为淘宝源
$ pnpm config set registry https://registry.npmmirror.com
# 还原为默认源
$ pnpm config set registry https://registry.npmjs.org
如果在windows下安装遇到:
报错:pnpm : 无法加载文件
D:\Nodejs\pnpm.ps1
,因为在此系统上禁止运行脚本。
可以在终端中输入一下命令解决:
$ set-executionpolicy remotesigned
创建项目
准备好运行环境之后就可以创建自己的博客项目了,后续弹出的选项根据自己的需求选择就可以了。其中询问的project开头的这些信息实际上就是后面博客的信息。
$ pnpm create vuepress-theme-hope my-blog
√ Select a language to display / 选择显示语言 English
√ Choose package manager pnpm
√ Which bundler do you want to use? vite
√ Your project name my-blog
√ Your project description A project of vuepress-theme-hope
√ Your project version 2.0.0
√ Your project license MIT
√ What type of project do you want to create? blog
√ Does the project need multiple languages? no
√ Initialize a git repository? yes
√ Do you need a GitHub workflow to deploy docs on GitHub pages? yes
√ Would you like to preview template now? yes
如果最后的立刻预览选择yes,直接打开浏览器http://localhost:8080/就可看到你的博客已经在运行了!🥳

项目文件结构
进入博客目录下,可以看到以下文件结构:
$ tree
.
|-- node_modules # 包安装目录
|-- package.json # 项目的清单文件
|-- pnpm-lock.yaml # 包管理器生成的锁文件,包括包及其依赖包的精确版本
|-- src # 页面相关源文件,用来增删改具体的页面
| |-- README.md
| |-- demo # 目录demo,和具体url路径绑定
| | |-- README.md # demo的目录页面
| | |-- disable.md # demo下的子页面
| | `-- page.md
| |-- intro.md
| `-- posts # 目录posts
| |-- apple # 子目录apple
| | |-- 1.md
| |-- banana
| | |-- 1.md
| `-- tomato.md
`-- tsconfig.json # Typescript的配置文件,一般不修改
项目根目录下有个隐藏的文件夹.vuepress
,里面包含了主题的配置文件:
$ tree
.
|-- config.ts # 全局配置文件
|-- navbar.ts # 导航栏配置文件
|-- public # 一些公共资产文件
| |-- assets
| | |-- icon
| | | |-- apple-icon-152.png
| | | `-- ms-icon-144.png
| | `-- images
| | |-- cover1.jpg
| | `-- cover3.jpg
| |-- favicon.ico
| |-- logo.png
| `-- logo.svg
|-- sidebar.ts # 侧边栏配置文件
|-- styles # 自定义样式文件
| |-- config.scss
| |-- index.scss
| `-- palette.scss
`-- theme.ts # 主题配置文件
执行以下命令进行构建:
$ pnpm docs:dev # 启动开发服务器,可以在浏览器预览
$ pnpm docs:build # 构建项目并输出静态页面,用来部署到服务器上
$ pnpm docs:clean-dev # 清除缓存并启动开发服务器
配置博客布局
这部分主要是修改.vuepress
目录下的内容。该目录下四个配置文件:
config.ts
:顶层的配置文件,定义博客的默认路径、语言、标签页的标题和描述、主题、插件等全局信息;theme.ts
:主题布局的配置文件,包括具体的页面属性和插件配置,基本上通过属性名称就可以理解其含义;navbar.ts
:导航栏的配置文件,这里需要定义导航栏中每一项与具体文件路径的映射关系;sidebar.ts
:侧边栏的配置文件,这里的侧边栏主要是页面左侧的目录栏,可以通过手动设置侧边栏下每个目录包含的子页面,或者直接将侧边栏目录的children
属性设置为structure
,这样就会根据本地文件自动生成侧边栏结构而无需手动设置。
详细的配置属性介绍可参考官方文档,因为内容很多这里就不再详细介绍。
添加博客页面
src
目录下每个markdown文件都对应的一个页面,使用文件夹对其进行层次化分组。在构建过程过程中会将markdown文件转化成相同名称的html文件,然后通过同样的相对路径访问,例如src/posts/banana/1.md
可以通过https://<url>/posts/banana/1.html
访问。每个目录下可以设置一个README.md
文件用来定义目录页面,例如src/posts/banana/README.md
就是https://<url>/posts/banana/
对应的页面。
每个markdown文件包括两个部分:frontmatter部分和正文部分。frontmatter一般位于文件的开头,主要做一些页面粒度的配置,包括页面的信息和主题相关属性等。正文部分就是扩展后的markdown语法了,功能十分丰富,可以参考官方文档自行研究。
注意
如果页面不需要左侧侧栏可以将sidebar
设置为false
,如果需要侧边栏的话将这个配置直接删除而不是设置为true
。
配置搜索插件
vuepress-hope内置了十分丰富的插件,像支持、评论、版权信息插件等大部分都是直接在theme.ts
中启用即可然后根据官方文档简单配置即可。这里主要介绍下搜索插件,配置起来要麻烦一些。
vuepress-hope提供了三个搜索插件,其中docsearch是第三方商业公司algolia提供的服务,也是最好用的一个。algolia为白嫖用户提供了一定的免费额度,而且支持自定义配置爬虫并提供友好的调试界面,非常适合个人博客使用。
安装插件
在vuepress-hope中使用docsearch需要先安装对应的包。在博客项目根目录下的package.json
中的devDependencies
添加上docsearch
包以及对应的版本:
"devDependencies": {
...
"@vuepress/plugin-docsearch": "2.0.0-rc.60",
...
},
然后使用pnpm进行安装即可:
$ pnpm install
接下来是配置algolia,这里有在线部署和本地部署两种方式。
在线部署algolia爬虫
- algolia提供了免费的在线文档爬虫服务,需要先在官网申请;

- 一两个工作日之后会收到一封审核通过的邮件,里面包含以下三个参数:应用id(
appId
)、api密钥(apiKey
)和新建的索引名称(indexName
),后面会用到;

- 进入algolia的官网并使用申请时用的邮箱注册,进入首页后点击左下角的数据库(data sources)图标进入数据管理界面,然后在左上角的application下拉框中选择
appId
对应的应用并进入爬虫(crawler)配置界面;


- 因为已经申请过,所以页面下方应该添加了对应的爬虫,如果没有的话可以自己新建一个,然后选择侧栏的编辑器(Editor)进入到爬虫的配置编辑器界面。

- algolia爬虫的原理就是根据博客页面html的标签、属性、类等设置选择器来抓取分层的信息,例如vue中带有
vp-content
属性的h1
标签中的文本就是标题,对应的选择器就是”[vp-content] h1”。不同的主题页面会有区别,这里将vuepress官方爬虫配置拷贝到编辑器,注意修改对应的信息,然后右上角启动爬虫; - 回到上一层等待爬虫执行完成,之后进入索引(indices)控制台,这里可以看到刚才爬虫更新的索引,点进去之后可以看到爬取的所有信息,然后可以输入关键字进行调试,十分的方便。


本地部署algolia爬虫
在线部署基本上开箱即用,比较简单,但是如果中间出了点意外比如像我不小心删除了爬虫然后发现无法自己创建,就只能联系客服恢复,而商业软件免费版本的客服基本都不太友好。后面发现还有本地部署的替代方案就换过来了,发现也挺好用:)。
- 本地部署需要有docker环境,还需要安装jq包(一个json解析器):
apt install jq
- 创建环境变量文件
.env
存放APPLICATION_ID
和API_KEY
,可以使用原来的或者新建的app对应的appId
和apiKey
(新建不了爬虫但是可以创建新app。。。):
APPLICATION_ID=【Your appId】
API_KEY=【Your apiKey】
- 创建爬虫的配置文件
crawler.json
,可以参考官方的文档进行配置。之后添加了这个属性还不够,还需要在所有的选择器中添加lang
字段,其值为zh_CN
;
注意
vuepress的docsearch配置文档中有个说说明,要求attributesForFaceting
属性中必须要包含lang
字段,因为vuepress发送的请求中会携带lang=zh_CN
,如果没有这个参数algolia的服务器就始终返回为空,而algolia的这篇官方文档里面我翻来覆去也没找到这个,甚至一度怀疑这个docker版本配置文件版本落后或许根本不支持这个属性,过了很久我才注意到custom_settings
下面有个二级页面,点进去一看果然在里面,当时心中有一万只**🐴飞过。


- 后面就是运行docker,下载algolia提供的镜像,注意因为众所周知的原因下载镜像时需要挂梯子。可以在终端看到每个页面爬取的数据索引,这些索引也会同步更新到algolia的控制台;
# 第一次运行创建容器
$ docker run --name crawler -it --env-file=.env -e "CONFIG=$(cat ./crawler.json | jq -r tostring)" algolia/docsearch-scraper
# 重新执行容器更新索引
$ docker start crawler
# 查看输出日志
$ docker logs --tail 30 crawler

- 进入algolia控制台可以看到已经爬取到了数据(偷懒直接用前面的图了),每次修改文档后都需要手动运行下爬虫,当然也可以使用万能的github action,具体做法可以参考这篇文章。

最后附一下我自己折腾挺久的最终爬虫配置文件吧,仅供参考:
{
"index_name": "reolee",
"start_urls": [
{
"url": "https://reolee.me/blog/",
"selectors_key": "blog",
"lang": "zh-CN"
},
{
"url": "https://reolee.me/paper/",
"selectors_key": "paper",
"lang": "zh-CN"
},
{
"url": "https://reolee.me/book/",
"selectors_key": "book",
"lang": "zh-CN"
},
{
"url": "https://reolee.me/about/",
"selectors_key": "about",
"lang": "zh-CN"
}
],
"custom_settings": {
"attributesForFaceting": [
"type",
"lang",
"language",
"version",
"docusaurus_tag"
],
"attributesToRetrieve": [
"hierarchy",
"content",
"anchor",
"url"
],
"attributesToHighlight": [
"hierarchy",
"hierarchy_camel",
"content"
],
"attributesToSnippet": [
"content:10"
],
"camelCaseAttributes": [
"hierarchy",
"hierarchy_radio",
"content"
],
"searchableAttributes": [
"unordered(hierarchy_radio.lvl0)",
"unordered(hierarchy_radio.lvl1)",
"unordered(hierarchy_radio.lvl2)",
"unordered(hierarchy_radio.lvl3)",
"unordered(hierarchy_radio.lvl4)",
"unordered(hierarchy_radio.lvl5)",
"unordered(hierarchy_radio.lvl6)",
"content"
],
"distinct": true,
"attributeForDistinct": "url",
"customRanking": [
"desc(weight.pageRank)",
"desc(weight.level)",
"asc(weight.position)"
],
"ranking": [
"words",
"filters",
"typo",
"attribute",
"proximity",
"exact",
"custom"
],
"highlightPreTag": "<span class=\"algolia-docsearch-suggestion--highlight\">",
"highlightPostTag": "</span>",
"typoTolerance": false,
"ignorePlurals": true,
"advancedSyntax": true,
"attributeCriteriaComputedByMinProximity": true,
"removeWordsIfNoResults": "allOptional"
},
"selectors_exclude": [
".vp-catalog"
],
"selectors": {
"blog": {
"lvl0": {
"selector": ".vp-sidebar-heading.active",
"default_value": "日常博客"
},
"lvl1": ".vp-page-title h1",
"lvl2": "[vp-content] h2",
"lvl3": "[vp-content] h3",
"lvl4": "[vp-content] h4",
"lvl5": "[vp-content] h5",
"lvl6": "[vp-content] h6",
"content": "[vp-content] p, [vp-content] li",
"lang": {
"selector": "",
"default_value": "zh-CN"
}
},
"paper": {
"lvl0": {
"selector": ".vp-sidebar-heading.active",
"default_value": "论文笔记"
},
"lvl1": ".vp-page-title h1",
"lvl2": "[vp-content] h2",
"lvl3": "[vp-content] h3",
"lvl4": "[vp-content] h4",
"lvl5": "[vp-content] h5",
"lvl6": "[vp-content] h6",
"content": "[vp-content] p, [vp-content] li",
"lang": {
"selector": "",
"default_value": "zh-CN"
}
},
"book": {
"lvl0": {
"selector": ".vp-sidebar-heading.active",
"default_value": "读书笔记"
},
"lvl1": ".vp-page-title h1",
"lvl2": "[vp-content] h2",
"lvl3": "[vp-content] h3",
"lvl4": "[vp-content] h4",
"lvl5": "[vp-content] h5",
"lvl6": "[vp-content] h6",
"content": "[vp-content] p, [vp-content] li",
"lang": {
"selector": "",
"default_value": "zh-CN"
}
},
"about": {
"lvl0": {
"selector": ".vp-sidebar-heading.active",
"default_value": "关于"
},
"lvl1": ".vp-page-title h1",
"lvl2": "[vp-content] h2",
"lvl3": "[vp-content] h3",
"lvl4": "[vp-content] h4",
"lvl5": "[vp-content] h5",
"lvl6": "[vp-content] h6",
"content": "[vp-content] p, [vp-content] li",
"lang": {
"selector": "",
"default_value": "zh-CN"
}
},
"default": {
"lvl0": {
"selector": ".vp-sidebar-heading.active",
"default_value": "其他"
},
"lvl1": ".vp-page-title h1",
"lvl2": "[vp-content] h2",
"lvl3": "[vp-content] h3",
"lvl4": "[vp-content] h4",
"lvl5": "[vp-content] h5",
"lvl6": "[vp-content] h6",
"content": "[vp-content] p, [vp-content] li",
"lang": {
"selector": "",
"default_value": "zh-CN"
}
}
}
}
启用插件
配置好docsearch之后还需要在theme.ts
中的plugins
字段添加如下配置以调用docsearch插件:
{
"docsearch": {
"appId": "<Your appId>",
"apiKey": "<Your apiKey>",
"indexName": "<Your indexName>",
// 汉化部分
"locales": {
"/": {
"placeholder": "搜索文档",
"translations": {
"button": {
"buttonText": "搜索文档",
},
"modal": {
"searchBox": {
"resetButtonTitle": "清理搜索结果",
"resetButtonAriaLabel": "清理搜索结果",
"cancelButtonText": "取消",
"cancelButtonAriaLabel": "取消",
"searchInputLabel": "搜索",
},
"startScreen": {
"recentSearchesTitle": "最近搜索记录",
"noRecentSearchesText": "无最近搜索记录",
"favoriteSearchesTitle": "收藏",
"removeFavoriteSearchButtonTitle": "从收藏中移除",
},
"errorScreen": {
"titleText": "无法获取结果",
"helpText": "无网络连接",
},
"footer": {
"selectText": "选择",
"navigateText": "切换结果",
"closeText": "关闭",
"searchByText": "",
},
"noResultsScreen": {
"noResultsText": "未找到",
"suggestedQueryText": "尝试搜索",
},
},
},
},
},
// 关闭搜索历史
"disableUserPersonalization": true,
// 搜索结果分组中每组的最大项数
"maxResultsPerGroup": 10,
},
}
最后的实现效果:

域名相关
选择域名
推荐去国外的域名网站购买,当然图便宜在国内买也可以,只不过备案流程比较麻烦。我自己的域名是在namesilo买的,第一年$6,续费每年$17,有点小贵但是也能接受,这是整篇文章中唯一的财务开支。
配置解析地址
需要将自己的域名解析到github的ip地址,流程可以参考github pages提供的官方文档。这里就以namesilo的配置为例来介绍。
- 进入namesilo的控制台(不得不说ui非常的复古),选择域名最右侧的”Manage IP for this domain“图标;

- 添加
A
记录将域名解析至GitHub Pages下面4个IP地址,最好还添加一个将www
子域解析到<用户名>.github.io
的CNAME记录;
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153

- 进入静态页面部署的仓库”setting→pages→custom domain“,输入自己的域名,如果下面的DNS测试成功就说明正确解析,保存之后刷新就可以看到上面会显示自定义域名已配置完成。

申请HTTPS证书
可以在配置域名的github页面下方直接勾选”enforce https“,这样github会为你自动申请由Let’s Encrypt颁发的免费证书。也可以使用阿里云提供的由DigiCert颁发的免费证书,具体的申请流程见官方文档。
博客部署相关
先说下我自己的部署方式:我在github上创建两个仓库,一个为包含markdown源文件和插件私钥的私有仓库A,另一个为部署静态页面的公开仓库B。每次要发布博客时我会将编辑好的本地文件push到仓库A,然后由A仓库的action脚本自动构建并部署到仓库B。我的个人域名会被解析到<github用户名>.github.io
,该域名与仓库B绑定,这样就能实现通过自己的域名访问仓库B中的静态页面。
配置部署脚本
github action是一个很强大的扩展功能,它可以监听git事件然后执行预先设置的脚本命令,很大程度上提高了github的可玩性,是自动化部署pages的前提。github action还提供了一个在线插件hub,里面包含了海量的各式各样的插件和开箱即用的配置脚本,可以根据自己的需求选择。
在使用vuepress-hope创建项目A时如果勾选了github workflow选项就会自动生成.github
目录,其中workflows
目录下的yaml文件就是github action执行的配置文件,已经实现了从push到远程仓库然后部署的全流程。因为我们要做跨仓库部署,所以还需要使用github-pages-deploy-action插件。这个插件需要用到包含仓库操作权限的密钥token,可以在通过进入任意仓库的”settings→Secrets and variables→Actions→New repository secret“创建,然后用设置的secret名称替换掉配置文件中key名称即可。
以下是我的action配置文件,仅供参考:
name: Deploy Docs
on:
push:
branches:
- main
permissions:
contents: write
jobs:
deploy-gh-pages:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# if your docs needs submodules, uncomment the following line
# submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18.20.4
cache: pnpm
- name: Install Deps
run: |
corepack enable
pnpm install --frozen-lockfile
- name: Build Docs
env:
NODE_OPTIONS: --max_old_space_size=8192
run: |-
pnpm run docs:build
> src/.vuepress/dist/.nojekyll
- name: Deploy Docs
uses: JamesIves/github-pages-deploy-action@v4
with:
# Deploy Docs
branch: main
folder: src/.vuepress/dist
repository-name: 【用户名】/【部署仓库名】
token: ${{ secrets.【key名称】 }}
silent: true
commit-message: "【每次自动commit的信息】"
配置好github action后,每次修改完commit再push到A仓库后,A仓库会自动执行构建然后部署到仓库B的全流程,非常的方便。
图床相关
之前markdown的图片存放一直是个头疼的问题,直到后面我发现有图床这个工具,感觉真的很好用。图床就是用来存放图片的地方,可以通过不带身份校验参数的http请求(直链)直接访问图片对象。一般图床有两种方案:一种是采用国内云服务商提供的对象存储(OSS)服务,访问速度稳定而且价格也不贵,但是要按月付费并且如果后续更换服务商迁移起来有些麻烦;另一种方案就是白嫖免费的github仓库作为图床,零成本就是每次上传删除照片都需要git commit
导致很多无意义的提交,但是能白嫖这点倒也不是不能接受。
有了图床还需要有上传图片的工具,这里我是用的picgo的vscode插件,picgo是一个开源的用来快速上传图片并获取图片url的工具。接下来主要介绍下github仓库+picgo vscode插件组成的图床方案:
- 创建一个github仓库作为存放图片的地方;
- 进入github设置创建针对该仓库查看修改的细粒度权限的token;
- 在vscode中下载picgo插件并按如下配置:
{
"picgo.picBed.current": "github", // 图床选择为github
"picgo.picBed.github.branch": "main",
"picgo.picBed.github.customUrl": "https://cdn.jsdelivr.net/gh/【用户名】/【仓库名】",// 白嫖jsdelivr的cdn
"picgo.picBed.github.path": "blogs/", // 图片存放的子目录
"picgo.picBed.github.repo": "【用户名】/【仓库名】",
"picgo.picBed.github.token": "【token】", // github创建的token
"picgo.customUploadName": "${mdFileName}-${dateTime}.png", // 上传图片文件命名格式
}
- 复制要上传的图片,在vscode的markdown编辑器中将光标移动到要插入位置,使用ctrl+alt+u快捷键即可上传图片并创建对应的链接。
提示
vscode picgo插件目前还不支持ssh远程连接中使用,可以更换为本地上传也是一样的方便。
结语
博客搭建前后花了不到一星期时间,这篇文章倒是写了快三周,中间写着写着愈发自己好像不太擅长写文章,可能平时写的比较随意习惯了🤦♂️。博客搭建只是代表个开始,真正难的是能够长久不断地坚持下去,希望自己能做到吧。
参考链接
- https://theme-hope.vuejs.press/zh/guide/
- https://lexcao.io/posts/jekyll-hugo-hexo/
- https://imageslr.com/2023/jekyll-netlify
- https://ihoneys.github.io/docusaurus-algolia/
- https://nstudy.org/p2024/12/vuepress/148a1e.html#_4-后台查看数据
- https://www.shaking.site/docs/Docusaurus-搜索配置/#配置-docsearchjson
- https://juejin.cn/post/7265265606436962341