从零搭建自己的技术博客:Hexo + GitHub Pages + 免费短域名实战
把自己写的东西放到一个属于自己的网址上,是一件比想象中简单的事。这篇文章记录我从零搭起这个博客、并给它绑上一个免费短域名的完整过程,包括所有踩过的坑。
本站就是这套方案的产物:源码用 Hexo,托管在 GitHub Pages,短域名 crui.is-a.dev 通过 is-a.dev 免费申请。整个过程零服务器、零费用、不需要备案。
一、先想清楚要什么
在动手之前,先明确三个问题,它们决定了技术选型:
| 问题 | 本方案的选择 | 为什么 |
|---|---|---|
| 内容会频繁变动吗? | 不会,主要是写文章 | 静态站点足够,不需要数据库 |
| 有服务器吗? | 没有 | 用 GitHub Pages 免费托管 |
| 需要动态功能吗? | 不需要 | 纯静态最省心,也最快 |
静态博客 + 免费托管的组合,是目前个人技术博客性价比最高的方案:
- 零成本:GitHub Pages 免费,自定义域名也可以免费
- 零运维:不用管服务器、不用配 Nginx、不用续费
- 免备案:GitHub 服务器在海外,无需 ICP 备案
- 版本可追溯:文章就是 Markdown 文件,扔进 Git 仓库,写错了随时回滚
代价是:纯静态、没有后台管理界面、国内访问速度一般。但对技术博客来说,这些都不算问题。
二、准备环境
需要三样东西:
1 | # 1. Node.js(Hexo 的运行环境),建议 18 以上 |
Node.js 装好之后,全局安装 Hexo 命令行工具:
1 | npm install -g hexo-cli |
国内网络如果
npm很慢,可以临时换成镜像源:npm config set registry https://registry.npmmirror.com
三、本地把站点跑起来
1 | # 初始化一个博客项目,会在当前目录创建 blog 文件夹 |
打开 http://localhost:4000,应该能看到一个默认的 Hello World 页面。此时目录结构大致是:
1 | blog/ |
理解 source/ 是源文件、public/ 是编译产物,这一点很关键——后面所有配置错误导致的”页面样式全丢”,基本都是这两个目录的关系没搞对。
四、换一个好看的主题
Hexo 自带的主题比较朴素。我用的是 Butterfly,功能全、文档好、中文社区活跃。
1 | cd blog |
然后在 _config.yml 里启用:
1 | theme: butterfly |
Butterfly 的配置不要直接改主题目录里的文件,正确做法是把 themes/butterfly/_config.yml 复制一份到博客根目录,命名为 _config.butterfly.yml,之后只改这一份。这样以后升级主题不会覆盖你的配置。
五、必须改的站点配置
打开 _config.yml,以下几项不改会显得很业余(尤其是申请免费域名时会被拒,后面会讲):
1 | # Site |
url 和 root 的对应关系,是新手翻车第一名:
| 你的站点地址 | url | root |
|---|---|---|
https://用户名.github.io/ |
https://用户名.github.io/ |
/ |
https://用户名.github.io/仓库名/ |
https://用户名.github.io/仓库名/ |
/仓库名/ |
https://自定义域名/ |
https://自定义域名/ |
/ |
只要绑定了自定义域名,root 就必须是 /。写错了不会报错,但整站的 CSS、JS、图片全部 404,页面会变成纯文字——这是最常见的求助帖。
另外顺手检查一下主题配置里的社交链接,模板自带的占位符长这样,记得换成你自己的:
1 | social: |
六、部署到 GitHub Pages
GitHub Pages 有两种仓库形态,选错了地址会多出一截:
| 仓库名 | 访问地址 |
|---|---|
用户名.github.io |
https://用户名.github.io/ |
其他任意名字(如 blog、my-site) |
https://用户名.github.io/仓库名/ |
也就是说,只有仓库名严格等于 用户名.github.io 时,站点才挂在域名根路径下。
下面按”普通仓库”演示(本博客就是这种,仓库名叫 chengrui.github.io,但用户名是 hccc044-bit,所以地址里带了一层后缀)。
1. 在 GitHub 上创建仓库
新建一个公开仓库。如果打算用根路径访问,仓库名就填 你的用户名.github.io。
2. 配置 SSH 免密推送
1 | # 生成密钥(一路回车即可) |
把复制的内容粘贴到 GitHub → Settings → SSH and GPG keys → New SSH key。然后验证:
1 | ssh -T git@github.com |
3. 安装部署插件并配置
1 | npm install hexo-deployer-git --save |
在 _config.yml 末尾加上:
1 | deploy: |
4. 构建并部署
1 | hexo clean && hexo generate && hexo deploy |
三个命令的作用分别是:清空编译产物 → 重新编译 → 推送到 GitHub。顺序不要变,clean 放在最前面能避免很多诡异的缓存问题。
部署完等一两分钟,去 https://用户名.github.io/仓库名/ 就能看到了。
如果页面能打开但没有样式,回去看第五节的
url/root配置。
七、给它绑一个短域名
到这一步,你的地址大概长这样:
1 | https://hccc044-bit.github.io/chengrui.github.io/ ← 41 个字符,实在不像个正经网址 |
有两条路可以缩短它。
路线 A:免费申请 is-a.dev 子域名(本博客采用)
is-a.dev 是一个面向开发者的免费子域名服务,提供给技术相关的个人站点使用。你能拿到形如 你的名字.is-a.dev 的地址,完全免费、自带 HTTPS、由 Cloudflare 提供 DNS。
先说一个被排除的选项:
js.org看起来更香(名字.js.org才 10 个字符),但它官方已明确规定不再接受个人博客和作品集,只收 npm 包、JS 库、工具类项目。个人博客提交了也会被拒,不用浪费时间。
申请流程
① 想好名字
子域名结构是 你起的名字.is-a.dev,中间那截 is-a 是固定的、不能拆。
② Fork 官方仓库
打开 github.com/is-a-dev/register,点右上角 Fork。
③ 在你的 fork 里创建申请文件
进入 domains/ 文件夹 → 右上角 Add file → Create new file → 文件名填 你的名字.json(因为在 domains/ 里,完整路径自动就是 domains/你的名字.json)。
内容如下:
1 | { |
这两个地方写错必被拒:
- 字段名是
records(复数),写成record会 fail validation - CNAME 的值不能带仓库名,必须正好是
用户名.github.io。写成用户名.github.io/仓库名是错的
④ 提交 Pull Request
在提交页面,选择 Create a new branch for this commit and start a pull request(此时按钮文字会从 Commit changes 变成 Propose changes,两者都对,取决于你选的选项)。
然后跳转到 PR 页面,确认左侧的 base repository 是 is-a-dev/register。如果你停在 你的用户名/register/compare/...,那是 fork 内部对比页,会变成”自己合并到自己”,官方收不到。正确地址格式:
1 | https://github.com/is-a-dev/register/compare/main...你的用户名:分支名?expand=1 |
也可以用更省事的办法:回到 fork 首页,顶部黄色横幅 Compare & pull request 按钮会帮你选好正确方向。
⑤ 填写 PR 模板(这一步决定生死)
模板里有 7 个 Requirements 复选框,必须全部勾上,而且只能改方括号:
1 | - [x] ← 正确 |
编辑方法:找到自己那条 PR 描述 → 右上角 ... → Edit → 改完后点 Update comment。
模板明确警告:除了复选框,那个区域里其它内容一个字都不能动(包括 <!-- 注释 -->),否则会 fail validation。
另外两处要填的:
WEBSITE_PREVIEW之间填你网站当前已在使用的地址(不是还没生效的新域名),例如https://用户名.github.io/仓库名/WEBSITE_PURPOSE之间用一句英文说明网站用途
⑥ 等维护者审核合并
快则几分钟,慢则一两天。这期间可以顺手把网站内容完善一下——模板明确不接受”Hello, world!”或仅做最小改动的模板站,审核者打开你的站点会人工判断。建议提交前清理这些模板痕迹:
_config.yml里还是默认的title: Hexo、空的description、keywords: First Blog Test- 主题自带的蝴蝶 logo 还当着头像用
- 还留着 Hexo 默认的
hello-world文章
⑦ 判断是否已合并(重要,别用 DNS 判断)
先说一个坑:*.is-a.dev 是泛解析。任何不存在的子域都会解析到 Cloudflare 的 IP(104.18.4.103 / 104.18.5.103),所以**”能 ping 通”完全不能说明域名已生效**。
唯一可靠的判据是看申请文件有没有进主分支:
1 | curl -s -o /dev/null -w "%{http_code}\n" \ |
返回 404 = 还没合并;返回 200 = 已合并,可以继续下一步了。
合并之后:切换配置(顺序不能错)
① 改 _config.yml
1 | url: https://你的名字.is-a.dev/ |
② 新建 source/CNAME
内容就一行:
1 | 你的名字.is-a.dev |
为什么必须放
source/而不是public/:hexo clean会清空public/,放那里下次部署绑定就失效了。
⚠️ 一个必须知道的陷阱:只要
source/CNAME被部署到发布分支,GitHub Pages 会立刻把它当成自定义域名设置。如果此时 DNS 还没生效,你的旧地址也会打不开。所以在 PR 合并之前不要提前把这个文件部署上去——如果需要在审核期间更新站点内容,先把source/CNAME移出部署范围(比如移到_local_backup/),等合并后再移回来。
③ 重新构建部署
1 | hexo clean && hexo generate && hexo deploy |
④ 开启强制 HTTPS
去仓库的 Settings → Pages,确认 Custom domain 已经是你申请的域名,然后勾选 Enforce HTTPS。
证书签发通常几分钟内完成,但那个勾选框要手动点。在勾上之前,你的旧地址会 301 跳转到 http://(注意是 http),访客可能看到”不安全”提示,所以这一步别拖太久。
路线 B:自己买一个域名
如果想要更短或者更正式的名字,就买一个。绑定 GitHub Pages 需要的 DNS 记录如下:
| 类型 | 主机记录 | 记录值 |
|---|---|---|
| A | @ |
185.199.108.153 |
| A | @ |
185.199.109.153 |
| A | @ |
185.199.110.153 |
| A | @ |
185.199.111.153 |
| CNAME | www |
你的用户名.github.io |
其余步骤(url / root、source/CNAME、Enforce HTTPS)与路线 A 完全一致。
几个现实提醒:
- 字母越少越贵。3 字母以内的
.com基本被抢光,能注册到的多是冷门后缀,而且很可能被注册局标为溢价域名——结账页显示的价格可能是几百上千元/年 - 查域名是否被注册可以用 RDAP:
https://rdap.org/domain/要查的域名,返回 404 即未被注册 - 但**”未被注册”不等于”便宜”**,真实价格以注册商结账页为准
八、踩坑清单
把这次遇到的所有问题汇总成一张表,供快速查阅:
| 现象 | 原因 | 解决 |
|---|---|---|
| 全站没有样式,只有纯文字 | 绑定域名后 root 还是 /仓库名/ |
root 改成 / |
hexo deploy 报 SAFE_DELETE_BULK_CONFIRM_REQUIRED |
某些环境有批量删除保护,部署要清空 .deploy_git 时被拦 |
见下方说明 |
| 绑了域名但访问不了 | DNS 还没生效 | 等几分钟到 24 小时 |
xxx.is-a.dev 能解析但打不开 |
is-a.dev 是泛解析,解析成功不代表已生效 | 用 API 查 domains/xxx.json 是否返回 200 |
| 旧地址打开了”不安全”提示 | 还没勾 Enforce HTTPS | Settings → Pages 勾上 |
| 新域名生效后旧地址自动跳转 | 正常行为,不用管 | GitHub 自动 301 |
| 换主题后配置被覆盖 | 直接改了主题目录里的 _config.yml |
配置放博客根目录的 _config.butterfly.yml |
关于部署被拦截:hexo-deployer-git 在推送前需要清空 .deploy_git 目录下的所有文件,文件较多时会触发环境的批量删除保护。此时 .deploy_git 里通常已经生成了内容正确的提交,可以绕过清理步骤直接推送:
1 | cd .deploy_git |
确认是快进推送,不要加 -f。
九、日常写作与发布
建站只是开始,真正要做的是持续写。日常流程只有三步:
1 | # 1. 新建文章(会在 source/_posts/ 下生成带模板的 md 文件) |
文章开头的 front matter 是元信息:
1 |
|
文章里插入图片,把图片放进 source/images/,然后按绝对路径引用:
1 |  |
注意是 /images/ 而不是 images/——开头的斜杠不能少,否则非首页的文章会找不到图片。
十、本博客的最终架构
回头看整个方案,所有组件都是免费或开源的:
| 环节 | 用到的服务 | 成本 |
|---|---|---|
| 静态站点生成 | Hexo + Butterfly 主题 | 免费开源 |
| 代码与内容托管 | GitHub 仓库 | 免费 |
| 网站托管 | GitHub Pages | 免费 |
| 域名 | is-a.dev 子域名 | 免费 |
| DNS | Cloudflare(is-a.dev 提供) | 免费 |
| HTTPS 证书 | Let’s Encrypt(GitHub 自动配置) | 免费,自动续期 |
最终地址是 https://crui.is-a.dev——16 个字符,纯 HTTPS,证书自动续期。
从 41 个字符到 16 个字符,中间隔着一个 Fork、一次 PR、七个复选框、两次配置修改和一个勾选框。不算复杂,但每一步都有自己的坑。
希望这篇文档能帮你少走一些弯路。如果对文中某个环节有疑问,欢迎通过首页的邮箱联系我。