接上一篇:本地博客已经能在localhost:4000跑起来了,但那是你自己电脑上的服务,别人访问不了。这一篇要解决的,就是把本地Hexo生成的静态页面推到GitHub,用GitHub Pages把博客公开到互联网上。所有工作围绕三个核心动作展开:配好SSH访问、创建一个公开仓库、在配置文件里写好部署目标。整个过程不算复杂,但里面有几个细节一旦写错,后果就是页面404或者样式全丢,而且报错信息还不太直观,排查起来很费时间。
我是在做这个步骤时踩过不少坑的,所以这篇会写得细一点,把操作步骤、配置文件里的每个字段、以及我当时排错的经验都写清楚。看完这篇,你就能顺利走通hexo clean && hexo g -d这条命令,把博客从本地搬到线上。
1. 部署前的准备工作:环境检查和SSH密钥配置
1.1 三条命令确认本地环境
我见过不少人在部署阶段卡住,最后发现是最基础的东西没装齐。所以第一步别急着建仓库,先在命令行里敲三条命令,确认环境没问题:
node -v git --version hexo version三条命令分别看的是Node.js、Git、Hexo的版本。正常情况下都会有版本号输出,比如v20.11.0、git version 2.43.0这样的内容。如果哪条提示command not found,那就先补装对应的工具,别往下走。
检查完版本,还要确认两件事。
第一,你的Hexo项目目录结构是完整的。所谓“完整”,就是目录下能看到_config.yml、package.json、source、themes这几个关键文件或文件夹。这是Hexo项目的基本盘,缺一不可。
第二,确认主题已经生效。打开项目根目录下的_config.yml,找到theme:这一行,确认它写的是theme: fan,不是别的主题名。检查方法很简单:在项目目录跑hexo s,然后本地访问localhost:4000,如果页面显示的是你选定的主题样式,说明本地环境正常。这一步多花两分钟,能帮你把“部署后才暴露的样式问题”提前暴露在本地。
1.2 SSH密钥:一次配置长期受益
部署Hexo到GitHub,需要在本地和GitHub之间建立安全连接。我强烈建议直接用SSH协议,而不是HTTPS。原因有两个:
- 用SSH推送时不需要每次输入用户名和密码,一条
hexo d直接推完; - 用HTTPS方式推送,如果开了双重验证,GitHub密码就不好使了,还得额外生成personal access token,麻烦。
检查本地是否已经有SSH密钥,执行:
ls -al ~/.ssh如果看到id_ed25519或id_rsa以及对应的.pub文件,说明之前生成过密钥。如果没看到,就执行下面的命令生成一对新的:
ssh-keygen -t ed25519 -C "你的邮箱"执行过程中会问你保存路径和密码短语,直接一路回车即可。密码短语那一步不用填,填了的话以后推送还要输密码,反而违背了我们用SSH的初衷。
生成完成后,把公钥内容复制到剪贴板。在macOS上可以用pbcopy < ~/.ssh/id_ed25519.pub,Windows的话用type C:\Users\你的用户名\.ssh\id_ed25519.pub或者直接用文本编辑器打开复制。
然后登录GitHub,进入Settings→SSH and GPG keys→New SSH key,把公钥粘贴进去,Title随意,保存后执行验证:
ssh -T git@github.com看到Hi 你的用户名! You've successfully authenticated, but GitHub does not provide shell access.就说明连接成功了。这一步是整个部署流程的“地基”,地基没打好,后面所有推送都会报权限错误。我见过有人把公钥和私钥搞混,也见过有人把私钥贴到GitHub上导致验证失败,注意.pub才是公钥,私钥(不带后缀的那个)永远不要给任何人。
2. 在GitHub上创建仓库与确定部署分支
2.1 仓库命名规则:用户页和项目页的区别
GitHub Pages提供了两种托管模式,理解这两种模式的区别,对你的仓库命名至关重要。
第一种叫用户页(User Page),仓库名必须是你的用户名.github.io。这是GitHub Pages的“特权仓库”,整个账号只能建一个,建好之后博客访问地址就是https://你的用户名.github.io,不需要加额外的路径,干净利落。我个人推荐用这种方式,因为博客地址最短,分享给别人时也不容易打错。
第二种叫项目页(Project Page),仓库名可以随便起,比如my-blog、hexo-blog,访问地址是https://你的用户名.github.io/仓库名/。如果你的GitHub账号下面还有别的项目要展示,或者你想把博客源码和别的repo区分开,可以选这种方式。不过要注意,使用项目页时,站点根路径会带上仓库名,后面配置root字段时稍不注意就会踩坑。
注意:仓库可见性必须设为Public。免费版账号的GitHub Pages不支持Private仓库,如果你选了Private,后面在Pages设置里根本找不到开启的选项。
创建仓库的时候,还有一个细节很多人忽略:GitHub会让你选择是否初始化README文件。这一步一定不要勾选,保持“空仓库”状态。原因很简单,如果GitHub帮你生成了一个带README的初始提交,你本地Hexo项目在推送时就会和远程仓库产生冲突,推送时还需要先git pull合并,白白增加操作复杂度。
2.2 分支选择:main还是master
这个问题看起来很小,但恰恰是初学者最容易搞混的地方。GitHub新建仓库时默认分支名是main,但在2020年之前创建的仓库默认分支是master,很多老教程里写的都是推到master分支。如果照抄老教程,你很可能把博客推送到了main分支上,但Pages配置里选的是master,最后页面直接404。
我的建议是在_config.yml的部署配置里明确写清楚分支名,统一用main。原因有两个:一是新仓库默认就是main,顺水推舟;二是以后不管谁接手你的博客,看配置就知道该去哪个分支看部署产物。
仓库建好之后,顺便打开仓库的Settings→Pages,在Branch区域选择main分支,根目录保持/(root)不变,然后保存。这一步不是必须提前做,但提前把页面托管的分支定下来,后续部署完就不会有“部署成功但无法访问”的困惑。更新的GitHub界面把部署分支选择放在了Pages设置页的Build and deployment区域,ensuring选择Deploy from a branch模式即可。
3. 修改配置文件:部署的核心三行
3.1 安装hexo-deployer-git插件
直接执行hexo d是不能把站点推送上去的,因为Hexo默认不带Git部署功能。需要在项目根目录下安装部署插件:
npm install hexo-deployer-git --save这一步会下载部署插件并自动写入package.json依赖。建议安装命令带上--save参数,这样以后换电脑重新部署时,执行npm install就能一键恢复所有依赖。
安装完成后,可以用npm ls hexo-deployer-git确认插件是否正常安装。输出里能看到版本号说明安装成功。这里提醒一句:如果你用了某个“全家桶”主题或博客框架,主题文档里可能已经指定了你需要用的部署插件版本,以官方文档为准,避免版本冲突。
3.2 根目录_config.yml中的deploy配置
打开项目根目录下的_config.yml,找到deploy配置项。如果没有,就在文件末尾追加。核心配置如下:
deploy: type: git repo: git@github.com:你的用户名/你的用户名.github.io.git branch: main逐行解释一下:
type: git告诉Hexo,部署方式使用Git;repo是远程仓库的SSH地址,注意是git@github.com:开头的SSH格式,不是https://github.com/开头的HTTPS地址。如果用了HTTPS地址配合SSH密钥,Git依然会在推送时要求输入密码,等于白配了密钥;branch: main指定推送目标分支,必须和你在GitHub Pages设置里选择的分支一致。
写完之后,建议用支持YAML语法检查的编辑器看一眼格式。YAML对缩进极其敏感,deploy:下面的子项目必须保持相同的缩进级别,混用Tab和空格也会导致解析失败。我见过最典型的错误是repo:前面多写了一个空格,结果Hexo启动时直接报“YAMLException”,半天找不到原因。
另外,_config.yml文件里还有一个url字段也特别重要,它默认是一串注释掉的示例,形如url: http://example.com。部署到GitHub Pages后,需要把它改成你的真实访问地址:
url: https://你的用户名.github.io这个字段影响的不只是浏览器地址栏显示,更关键的是RSS订阅、sitemap插件、以及部分主题里的分享功能都会调用它生成绝对链接。如果不改,很可能会出现“文章内站内链接指向localhost”这种诡异问题,复制给别人的链接打不开,自己浏览器却显示正常。
3.3 主题与站点URL配置:确保页面样式不崩
接下来是部署环节最容易被忽略的一步:root路径配置。
root字段位于根目录_config.yml里,它的值取决于你在2.1节选择的仓库类型。
- 如果仓库名是
你的用户名.github.io(用户页),root直接写成/; - 如果仓库名是普通仓库名(项目页),
root要写成/仓库名/。
这个字段的作用是告诉Hexo,站点资源(CSS、JS、图片)放在什么路径下面。HTML里加载CSS的路径就是由root拼接出来的。如果root配错,典型症状就是:页面文字和结构在,但所有样式全部丢失,纯文本裸奔在页面上。浏览器地址是https://你的用户名.github.io/repo-name/,CSS却指向了https://你的用户名.github.io/css/style.css,自然404。
这里再多说一句关于主题的配置。Fan主题(及其他绝大多数Hexo主题)自己也有一个_config.yml,位于themes/fan/_config.yml下。和部署直接相关的其实不多,但有几个地方值得检查:
- 主题文件里的菜单链接、社交链接,如果写的是绝对路径,要确认引用了正确的
url; - 主题内部如果有
favicon、logo、avatar等图片设置,建议填写相对于root的路径,不要写成以盘符开头的本地路径; - 如果主题支持文章阅读量统计、评论系统等第三方服务,这些服务的站点地址也需要和线上域名一致。
有些主题在本地预览和线上部署后表现不一样,根本原因是本地预览时localhost:4000下页面路径和线上的平台路径不同。一个简单验证方法:在本地跑hexo s,按Ctrl+Shift+I打开开发者工具,看Console里有没有红色的404报错。如果本地没有报错但部署后样式崩了,九成是root的问题。
4. 执行部署:三行命令搞定
4.1 clean、generate、deploy的前后顺序
配置全部完成后,正式的部署命令非常简单,就三步:
hexo clean hexo generate hexo deploy也可以连写:
hexo clean && hexo generate && hexo deploy解释一下这三条命令分别做了什么,以及为什么顺序不能乱。
hexo clean会清除之前生成的静态文件缓存,具体来说是删除public目录和.db.json缓存文件。我在实际操作中强烈建议每次部署前都执行一次clean,因为如果你改过文章、换过主题、调整过配置文件,旧缓存可能导致线上出现“文章列表还是旧的”“分类页和标签页对不上”这类说不清的问题。clean之后一切从零开始,干净。
hexo generate是核心构建命令,它会读取source目录下的Markdown文章、主题模板、配置文件,最终生成完整的静态网站到public目录。你可以打开public目录看一下,里面就是纯粹的HTML/CSS/JS/图片文件,没有任何依赖Node.js的运行时逻辑,这正是静态博客的优势——部署到哪里都能跑,不需要服务器端动态执行。
hexo deploy则调用我们前面安装的hexo-deployer-git插件,把public目录里的所有文件推送到GitHub仓库的main分支。注意,它不是把整个Hexo项目推上去,只推public构建产物。所以你不用担心博客源码泄露出去,或者说,如果你希望源码也保存在GitHub上,需要另外单独处理,通常做法是给源码建一个独立仓库或独立分支。
部署命令执行后,终端会出现类似下面的输出:
INFO Deployed: https://你的用户名.github.io/看到这行信息,说明推送成功。这时打开浏览器,访问你的GitHub Pages地址,如果一切正常,看到和本地一样的博客页面就大功告成了。我第一次部署成功后,终端提示Deployed,但浏览器打开却显示404,当时一头雾水,后来才意识到是GitHub Pages的构建和发布并非即时生效,需要等待一两分钟。
4.2 首次访问与日常更新流程
GitHub Pages的更新有个特点:部署不是秒级生效。即使hexo deploy已经成功推送,页面要过一两分钟才能刷新出最新内容。这是因为GitHub收到推送后需要重新构建并发布静态站点,首次部署等待时间尤其明显,有时候要等3到5分钟。我建议你部署完成后不要频繁刷新,去做点别的事,过几分钟再看。
日常更新的操作流程非常简单,每次写完新文章,只需要执行:
hexo clean && hexo generate && hexo deploy也就是“清理一遍、重新生成、推到远程、等待生效”。这就是整套博客维护的全部日常操作。
我自己还有一个习惯:部署前先用hexo s本地预览一遍,确认新文章排版正确、分类标签没写错,再执行部署。虽然每次多花30秒,但能帮你把绝大多数问题拦截在本地。毕竟线上出了问题,要经历“发现问题→修改→重新部署→等生效”这个完整循环,至少5分钟,远不如本地预览来得快。
还有一个小细节:修改了配置文件或主题文件之后,很多人喜欢只执行hexo d或者只执行hexo g -d,不先clean。这种做法偶尔能成功,但一旦遇到“页面样式还是旧的”“新增的菜单没出现”这类现象,基本就是缓存惹的祸。与其花时间排查,不如老老实实全套三步走。你可以把命令定义成别名,比如在shell里写alias hd='hexo clean && hexo g -d',省得每次敲三遍。
5. 常见问题排查与避坑经验
5.1 权限报错与SSH调试
部署时最常见的报错是:
Permission denied (publickey). fatal: Could not read from remote repository.看到这个提示,优先检查三处。
第一,确认SSH密钥是否在本地生效。执行ssh -T git@github.com,如果显示的是“Permission denied”而不是“You've successfully authenticated”,说明密钥没有被Git使用。Windows用户常遇到这个问题,因为系统自带的ssh-agent服务没有启动,或者没有把私钥添加进去:
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519macOS用户如果在钥匙串访问里找不到SSH密钥,也可以执行同样的命令手动注册。
第二,确认密钥文件权限没问题。我自己有一次在Linux服务器上部署时,发现私钥文件权限太开放,Git直接拒绝使用。需要把私钥权限收紧:
chmod 600 ~/.ssh/id_ed25519这个操作在macOS和Linux上尤其重要,Windows用户通常通过界面勾选的路径不容易出现权限问题。
第三,确认GitHub账户里添加的是公钥,不是私钥。公钥以.pub结尾,内容是一长串字符,开头一般是ssh-ed25519或ssh-rsa。私钥的内容看起来类似,但没有.pub后缀,而且在创建时可能还包含类似“BEGIN OPENSSH PRIVATE KEY”的字样。两者长得很像,但功能和保密级别完全不同。
5.2 页面404或样式丢失的排查
部署成功但页面打不开,这是第二类高频问题。整理成表格方便对照排查。
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 访问主页404 | Pages设置里的分支不对 | 确认Settings→Pages里的分支是main |
| 首次部署后短时间404 | GitHub Pages构建有延迟 | 等待3~5分钟再刷新 |
| 文章页能开,但CSS/JS全部失效 | root路径配置错误 | 按仓库类型设置root: /或/仓库名/ |
| 页面能显示,但图片裂开 | 图片路径用了盘符或相对路径 | 在文章中使用/图片名.jpg或的相对格式 |
| 页面文字正常,菜单点击后404 | 分类/标签页路由路径和主题不匹配 | 检查主题文档,确认menu路径配置正确 |
样式丢失这个问题值得展开说一下。它的本质是浏览器按root拼接出的静态资源URL和实际部署位置对不上。排查方法是:打开浏览器开发者工具,切到Network或Console,找到加载失败的CSS请求,看看它请求的完整URL,再对比真实的CSS文件地址。只要这一步能定位到URL差异,解决起来就很直接——改root配置,重新clean && g -d。
很多人会忽略的另一个点:浏览器缓存。部署完成后,即使线上页面已经更新,浏览器可能还在用本地缓存的旧CSS和旧HTML。尤其是那些“我在线上一看明明一切正常,但手机上就是旧页面”的情况,几乎都是这个原因。解决办法有两个:强制刷新(Ctrl+F5或Cmd+Shift+R),或用无痕模式打开验证。记住判断原则:如果无痕窗口能看到新页面,问题就在浏览器缓存,不是部署问题。
5.3 部署流程中的其他坑
再分享几个我实际遇到过、且不太容易搜到答案的问题。
第一个坑:fatal: repository not found。仓库名和用户名拼错、仓库还没创建、或者用了不存在的仓库地址,都会报这个错。解决办法很简单,检查_config.yml中repo的SSH地址和实际仓库名是否一致。注意GitHub用户名的大小写并不影响SSH地址,但仓库名的大小写是敏感的,最好直接复制仓库页面的SSH地址,不要手打。
第二个坑:部署完成后,GitHub仓库里能看到提交,但提交人是“noreply”地址。这是因为GitHub把用户的邮箱地址用隐私代理代替了。如果介意,可以在本地设置Git的用户信息:
git config --global user.name "你的用户名" git config --global user.email "你的邮箱"或者单独在Hexo项目目录里设置局部用户信息,避免影响全局配置。
第三个坑,也很隐蔽:public目录被提交到了源码仓库。有些刚开始玩Git的读者,喜欢把整个博客目录都git add并推送到GitHub,结果public目录也被一起提交了。由于部署插件每次部署都会重新生成public内容并强制推送,在源码仓库里出现public目录会造成不必要的混乱。解决方法是回到项目根目录,编辑.gitignore文件,确认里面包含public/这一行。这样源码仓库就不会跟踪构建产物了。
第四个坑:个别主题需要额外的部署后操作。比如有些主题依赖搜索索引文件或JSON数据文件,需要在部署前先执行对应的生成命令。Fan主题在这类需求上比较常规,但如果你日后换用其他主题,记得看主题文档的“部署前需要做什么”章节,别只盯着通用部署流程。
还有一个我折腾过很久的点:CNAME自定义域名。如果你手里有自定义域名,比如blog.example.com,需要在仓库的设置里配置自定义域名,并且在source目录下放一个名为CNAME的文件,内容只写你的域名。这样每次部署时,CNAME文件都会被自动带进public目录,推送到线上,自定义域名才不会因为部署更新而丢失。这个文件放在source/CNAME下,不要手动放到public里,因为public每次生成都会被清空重来。
到这儿,Hexo加GitHub Pages的部署流程就算完整走通了。整个过程核心就一句话:把本地生成的静态页面推到GitHub指定仓库的指定分支,再让GitHub Pages把这个分支托管为网站。SSH密钥、仓库分支、_config.yml里的deploy和root配置是四个关键控制点,只要这四个点没有搞混,部署基本不会出大问题。
我在实际部署中最大的体会是:不要急着追求自动化部署那些“高级操作”,先把最基础的手动流程跑通。手动流程能稳定复现之后,你再去做“在服务器上自动构建、利用Webhook触发部署”这些进阶操作,会顺手很多。手动部署是地基,自动化是锦上添花。下一篇我打算写一些进阶的玩法,比如如何用GitHub Actions实现“推文章自动部署”,以及多设备之间如何同步博客源码,这些都是基于你已经手动部署成功、并且理解了这套机制之后才能顺畅上手的。