
如果你最近也在“个人博客到底用什么搭”这个坑里纠结大概率绕不开两个名字Jekyll和Hexo。这个话题太经典了经典到每过一段时间就有人重新问一次“jekyll和hexo哪个好”。我自己的答案是如果你只想老老实实写文章、不想折腾服务器、还想省掉所有运维成本那 Jekyll GitHub Pages 是目前最省心、最长久的组合之一。这篇文章我会完整走一遍从环境安装、站点生成、本地预览到部署上线的全流程顺便把主题替换、写作发布、评论统计这些日常高频操作讲细最后再把我在实际使用中踩过的坑和排查思路列出来。全程适合零基础起步也适合已经搭过但想搞明白“为什么当时那么做”的人。1. 先说结论Jekyll和Hexo到底选哪个1.1 这套组合的本质免费、静态、一切归你有不少人第一次接触 Jekyll 时都会问它到底是个什么东西你可以把它理解成一个“私人化排版车间”你负责用 Markdown 写文章Jekyll 负责把 Markdown 套进 HTML 模板里最后吐出一个完整静态网站。什么是静态网站就是没有后端数据库每次访问时服务器直接返回预先生成的 HTML 文件不查数据库、不跑程序所以加载速度天然快也不容易被攻击。GitHub Pages 的角色则是一台永远在线的免费托管机器。你把 Jekyll 生成的源码推到 GitHub 仓库它会自动构建并把最终静态页面放到 CDN 上全世界访问都不需要你自己买服务器。这套组合的运行逻辑非常直白本地写作、Git 提交、云端构建、线上发布。对一个内容创作者来说这台“发布流水线”已经接近完美因为你只需要关心写作剩下的都是自动化。我个人更看重的一点是“数据主权”。折腾了三五年博客之后你会发现任何平台都可能跑路或者改规则但你在自己仓库里的 Markdown 文件永远在。只要文件在换任何框架、任何平台都能一键迁移。Jekyll GitHub Pages 把“写作内容”和“发布方式”解耦得很彻底这才是它最大的价值。1.2 和Hexo的对比一张表看清差异既然大家都在问“jekyll和hexo哪个好”这里直接给一份我用了三年多总结出的对比对比项JekyllHexo底层语言RubyNode.js与 GitHub Pages 的关系GitHub 官方原生支持push 源码即可自动构建官方不直接支持一般需构建后推送静态文件或用 Actions 部署主题生态主题数量偏少质量两极分化中文社区主题丰富插件很多构建速度文章数量多后构建变慢构建速度相对更快插件体系基于 RubyGemGitHub Pages 只允许白名单插件基于 npm插件丰富但版本升级容易踩坑入门门槛需要装 RubyWindows 上稍微费点劲需要装 Node.js国内用户用得更多折腾空间模板结构清晰适合愿意学一点 Ruby 的人插件和主题多适合喜欢玩花活的人如果只看“谁能零成本跑在 GitHub Pages 上”那 Jekyll 有天然优势GitHub 官方默认就认它的源码格式你只需要把仓库地址设置好push 上去它就自动构建发布。Hexo 则需要多一步操作要么本地生成public目录再手动推送要么借助 GitHub Actions 完成构建流程本身也不算难但确实绕了一圈。我的真实感受是选型不是看谁技术更强而是看谁更贴合你的习惯。你把博客搭出来最终目的是持续更新不是比框架复杂度。如果你已经熟练使用 Node.jsHexo 很顺手如果你平时主要在 GitHub 上活动、想省掉一切中间环节Jekyll 是更“原生”的选择。1.3 什么情况下别选Jekyll没有一种方案是万能的。如果你打算做“动态”功能比如用户注册、评论审核、在线支付、会员系统那就别指望 Jekyll 或者 Hexo老老实实上 WordPress 或其他后端框架。静态博客再折腾也变不出服务器端程序它的边界在于“只展示内容”。如果你对页面交互特别敏感希望每个页面都塞满复杂动效和实时请求静态站生成器也不是最优解。还有一类情况是博客体量极大几千篇文章还带大量标签和分类页面Jekyll 的构建速度会肉眼可见下降这时可以看看 HugoGo 语言或者直接在服务器上用动态方案。所以我的结论很明确个人博客、技术文档、小型作品集这三类场景下 Jekyll GitHub Pages 的性价比最高一旦需求超出“展示内容”的范围再换其他方案也不迟。2. 从零到本地预览环境、安装、目录结构一次讲清2.1 三个平台装RubyWindows、macOS、Linux各走各的路Jekyll 是 Ruby 写的所以第一步是搞定 Ruby 环境。先说最容易踩坑的 Windows 平台去 RubyInstaller 官网下载带 DevKit 的安装包安装全过程务必勾选“Add Ruby executables to your PATH”否则后面命令行找不到ruby命令会很抓狂。装完 DevKit 后在终端执行ridk install按提示选 3 安装 MSYS2 基础工具链。macOS 用户要小心一件事系统自带的 Ruby 版本往往偏旧直接用它装 Jekyll 经常会出现各种权限和依赖报错。建议先用 Homebrew 安装当前较新的 Ruby 版本或者用 rbenv 做版本管理。简单省事的做法是brew install ruby装完记得看终端提示里的 PATH 设置让新版本 Ruby 优先生效。LinuxUbuntu/Debian 系比较直接sudo apt update sudo apt install ruby-full build-essential zlib1g-dev装完后确认一下版本ruby -v gem -v看到输出类似ruby 3.x.x就说明环境没问题。这里有个细节很多安装失败案例都是缺少编译工具链导致的所以 Debian 系必须要装build-essentialmacOS 则要确保 Command Line Tools 已经就绪。2.2 安装Jekyll并生成站点骨架Ruby 环境就位后安装 Jekyll 只需要两行命令gem install jekyll bundler jekyll new my-blogjekyll new会自动生成一套完整的博客骨架包括默认主题、示例文章、配置文件等。生成之后进入目录先把依赖装好cd my-blog bundle install然后启动本地预览bundle exec jekyll serve --livereload这时访问http://localhost:4000就能看到博客已经跑起来了。--livereload参数很实用改完文件浏览器会自动刷新省去手工刷新的功夫。很多新手会好奇为什么命令前面要加bundle exec因为 Jekyll 项目用 Bundler 锁定依赖版本不同项目可能依赖不同版本的 gembundle exec的作用是确保当前环境使用Gemfile里严格指定的版本避免全局版本冲突。这是 Jekyll 官方推荐的做法能极大减少“我本地正常但别人跑不起来”的问题。2.3 第一次看目录结构每个文件夹都有任务jekyll new生成的目录结构并不复杂但每个目录的职责最好一开始就搞清楚_config.yml全局配置比如站点名称、描述、URL、时区改完需要重启服务才生效。_posts博客文章都放这里文件名必须按年-月-日-标题.md的格式命名。_layouts页面模板post.html是文章页模板default.html是全局默认布局。_includes可复用的片段比如页头、页脚、统计代码通过{% include xxx.html %}引入。_sass样式源码SCSSJekyll 会编译成 CSS。assets放图片、JS、自定义样式等静态资源。Gemfile声明项目依赖的 gem 包。index.md博客首页会按时间倒序列出文章列表。理解了这套结构之后你会发现 Jekyll 的学习曲线其实不陡它就是一个“模板 内容”的组合模型。你写的文章是内容_layouts里的是模板Jekyll 在构建时把它们拼接到一起。2.4 写第一篇文章文件名和front matter是核心现在试着写第一篇文章。进入_posts目录新建一个文件命名格式务必规范例如2024-01-01-hello-jekyll.md。这个文件名里的日期决定了文章的发布时间排序如果写错文章可能不在预期位置出现。文件开头有一段名为 front matter 的 YAML 块类似这样--- layout: post title: 我的第一篇Jekyll博客 date: 2024-01-01 10:00:00 0800 categories: life tags: [随笔, 博客] ---layout指定使用哪个模板title是页面标题date建议写带时区的完整时间categories和tags用来做内容归类。front matter 之后就是正文的 Markdown 内容。保存后访问http://localhost:4000/2024/01/01/hello-jekyll.html具体路径取决于你的配置文章就出现了。我特别提醒一下时区写法格式里0800表示东八区不写时区的话只能在_config.yml里通过timezone统一设置但有些老版本对本地时区识别不稳定导致文章显示时间比预期晚或早。固定写法是date: 2024-01-01 10:00:00 0800最稳妥。3. 部署到GitHub Pages三条路线里哪条最省心3.1 用户名仓库的最简路线部署到 GitHub Pages 有个特殊规则如果你创建的仓库名是用户名.github.io比如zhangsan.github.io那这个仓库会被自动识别为个人主页站。你不用在设置里做任何多余操作只要把 Jekyll 源码推送到main分支GitHub 就会自动完成构建并通过https://zhangsan.github.io对外发布。操作流程非常简单git init git add . git commit -m first commit git branch -M main git remote add origin https://github.com/你的用户名/你的用户名.github.io.git git push -u origin main推完稍等一两分钟打开https://你的用户名.github.io就能看到自己的博客。这套路线零配置、零成本最适合第一次接触 GitHub Pages 的新人。需要注意一点GitHub Pages 只支持公开仓库免费的私有仓库无法启用 Pages 服务如果博客里涉及不想公开的内容就得换个托管方案。3.2 项目仓库的分支发布如果你不想建一个专门的个人主页仓库而是想把博客代码放在某个普通项目的仓库里路径也通。普通项目仓库需要在仓库 Settings 里的 Pages 选项中选择分支作为发布源。具体操作是进入仓库Settings - Pages - Build and deployment - Source选择Deploy from a branch然后指定main分支和根目录/点保存。发布之后博客的访问地址会变成https://你的用户名.github.io/仓库名/。这个模式下有个容易忽略的点站点的 CSS、图片等静态资源路径会变成根路径下的子目录所以在生成站点时_config.yml里的baseurl必须设置成仓库名前缀否则页面建立后一片裸奔图片全挂。这个坑我在后面第 5 章会详细展开。3.3 用GitHub Actions自动构建我更推荐的路线如果你的博客引入了 GitHub Pages 不支持的自定义插件或者你希望更清楚地看到每次构建的日志那我更推荐直接用 GitHub Actions 来构建。这种方式不依赖 GitHub 原生的 Jekyll 构建器而是用服务器上最新最全的 Ruby 环境按你的配置构建灵活性高很多。在仓库中新增文件.github/workflows/jekyll.yml填入以下内容name: Build and deploy Jekyll site to GitHub Pages on: push: branches: [main] workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: pages cancel-in-progress: true jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkoutv4 - name: Setup Ruby uses: ruby/setup-rubyv1 with: ruby-version: 3.2 - name: Install dependencies run: bundle install - name: Build site run: bundle exec jekyll build - name: Upload artifact uses: actions/upload-pages-artifactv3 deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pagesv4提交之后在仓库的 Actions 标签页能看到每次构建和部署的完整日志。如果构建失败日志会直接指出是哪一步出的问题比原生构建的黑盒体验好太多。这也是我后来切换到此方案的主要原因可观测、可控制、不依赖线上环境。使用 Actions 时记得在仓库Settings - Pages里的 Source 选择GitHub Actions这样系统才会接收 Actions 产生的新站点版本。3.4 绑定自定义域名博客上线后总希望访问地址像www.example.com而不是用户名.github.io这个操作也不难只需要三步。第一步在域名服务商那边添加一条 DNS 记录。常见做法是把www子域解析为你的用户名.github.io类型选择 CNAME裸域比如根域名example.com有些 DNS 服务商支持 ALIAS 或 ANAME 记录没有的话老老实实用www子域最省事。第二步在 GitHub 仓库的Settings - Pages - Custom domain里填入你的域名保存。GitHub 会提示你确认 DNS 生效同时页面会自动生成一个CNAME文件里面写着你的域名。这里有个小技巧建议直接在源码目录里新建一个CNAME文件并提交内容就是你的域名这样以后重新部署时不会意外丢掉域名配置。第三步在 Custom domain 设置页面勾选 Enforce HTTPS等待 GitHub 自动签发 HTTPS 证书。这一步可能需要几分钟甚至几小时期间不要反复取消重开否则证书申请会被打断。等状态显示“HTTPS is enabled”访问https://www.example.com就正常了。4. 让博客真正属于你主题、配置与日常写作流4.1 换主题从默认的minima到第三方主题新站默认使用的是 minima 主题优点是干净简洁缺点是千篇一律一打开就知道是 Jekyll 默认站。想换个风格可以先用jekyll new自带的主题再看看或者使用更丰富的第三方主题。Jekyll 的主题替换有两种方式。第一种是直接在Gemfile里引入主题 gem但你需要手动把主题的模板文件复制到项目目录里才能修改步骤繁琐。第二种方式是我更常用的使用remote_theme配置比如在_config.yml里写remote_theme: jekyll-theme-chirpy/jekyll-theme-chirpy然后运行bundle install重启本地服务主题就换了。remote_theme的好处是直接从 GitHub 拉取主题源码不需要手动维护整个主题文件主题作者更新了你重新构建时也能接收到改动。前提是Gemfile里要有github-pages或jekyll-remote-theme之类的依赖支持。选主题有几个标准作者是否持续维护、是否适配移动端、页面权重是否足够快、要不要引入大量 JS。很多主题“看起来炫”但塞了一堆动画库博客内容加载半天个人博客没必要追求这种效果。4.2 _config.yml 里值得改的参数_config.yml是整个博客的中央控制台几个核心参数值得好好设置title: 我的博客 email: yournameexample.com description: 这里写一句话介绍你的博客会出现在搜索引擎结果和站点首页。 baseurl: url: https://你的用户名.github.io twitter_username: 你的Twitter账号 github_username: 你的GitHub账号 timezone: Asia/Shanghai lang: zh-CNbaseurl在用户名仓库下填空字符串即可在项目仓库下要填仓库名前缀。url是最终线上访问地址务必配置否则 SEO 插件、地图生成等工具无法正确拼接绝对地址。timezone也强烈建议加上否则首页和侧边栏显示时间可能跟你的本地时间差好几个小时。lang字段在标准构建里一般不会直接影响展示但对于 SEO 搜索理解页面语言有帮助。4.3 我的日常写作发布流水线博客上线之后日常更新应该回归到最简单的循环。我现在每次写作的路径是这样的在_drafts目录里新建 Markdown 文件文件名就是文章的 slug比如new-git-workflow.md。草稿不写日期Jekyll 不会把它构建成正式页面适合还没写完的文章。用 VS Code 或者 Typora 打开文件专心写内容。写完后准备发布时把文件移动到_posts目录按规范重命名为2024-01-01-new-git-workflow.md再补上 front matter 里的title、categories、tags。本地执行bundle exec jekyll serve快速预览检查格式和图片路径。确认没问题后执行git add . git commit -m post: xxx git push剩下交给 GitHub 完成构建部署。这套流水线我坚持了三年最大的感受是不要让“发布”变成一种负担。写博客最怕本末倒置花两小时折腾框架却只剩十分钟写内容。把发布流程固定下来每天坐下来想写就写写完推一下就完事。4.4 评论系统和访问统计让博客活起来静态博客没有数据库评论区需要靠第三方服务。我用过一段时间 Disqus确实功能强大但加载速度在国内不太乐观而且页面非常“重”。后来换成了 utterances一个基于 GitHub Issues 的评论系统。读者的评论会以 Issue 的形式出现在你的仓库里不需要额外服务器界面也很轻量。接入方式很简单先在 GitHub 应用市场安装 utterances设置好允许评论的仓库然后在_layouts/post.html里加入它的 script 标签。如果你不希望每个页面都用同一个问题跟踪也可以配置为每个页面自动关联一个 Issue。访问统计我同时放了两套Google Analytics 4 和百度统计。前者覆盖面广、分析能力强后者在国内访问时的数据更贴近真实用户。两套统计代码都放在_includes/analytics.html里再在default.html的head区域引入这样一个页面加载时能同时上报两套统计。我建议你按自己读者的地域分布决定主力读者在国内就用百度或友盟海外居多就专心用 Google Analytics不要两套全放会影响加载速度。5. 本地没事线上崩我踩过的构建与路径大坑5.1 本地正常、线上构建失败的排查链路这个问题可能每个 Jekyll 用户都遇到过也是最让人困惑的一类问题。你本地bundle exec jekyll serve跑得好好的推到 GitHub 上却收到构建失败的邮件提醒。根本原因通常是“本地环境和线上环境不一致”。GitHub Pages 原生构建使用的是一套固定版本的依赖环境它只允许白名单内的插件。如果你本地用了Gemfile里的外部插件比如jekyll-algolia、jekyll-pdf-embed线上不认识构建自然失败。解决思路很清晰要确认线上是否支持你用的插件最好的办法是本地也模拟线上环境。具体做法是打开Gemfile把gem jekyll换成gem github-pages, group: :jekyll_plugins然后重新执行bundle install。这样本地用的就是 GitHub Pages 官方维护的那套依赖凡是本地能构建成功的项目线上也一定能跑通。改用 GitHub Actions 路线后这个问题会少很多因为 Actions 环境完全按你的Gemfile装依赖不再受白名单限制。排查时如果实在找不到原因直接去仓库的 Actions 标签页看红色日志错误信息通常已经明确到某个文件某一行比“构建失败”四个字有用得多。5.2 时区错乱为什么文章时间总不对博客文章显示时间不对是另一个高频问题。表现常见为文章显示日期比实际发布晚 8 个小时或者首页同一篇文章出现在日期排序的奇怪位置。原因多半是 front matter 里的日期没有指定时区。比如你写date: 2024-01-01 10:00:00Jekyll 会按系统默认 UTC 时区解析转换成北京时间就成了2024-01-01 18:00:00。如果你的博客读者在国内这 8 小时偏差会造成不小的困扰。解决方案有两个一是像第 2 章写的那样在每个 front matter 里明确带0800后缀二是在_config.yml里设置全局timezone: Asia/Shanghai。我建议两者都做双保险。5.3 baseurl引发的图片路径事故图片挂掉、CSS 丢失是博客部署后第一眼就能发现的难看问题。尤其是项目仓库非用户名仓库部署时站点访问路径是https://用户名.github.io/仓库名/所有静态资源默认从根路径/assets/...查找自然就找不到。正确做法是在页面模板里引用资源时加上site.baseurl变量img src{{ site.baseurl }}/assets/img/example.png alt示例图片或者是 Markdown 里这样写同时确保_config.yml里的baseurl配置正确。本地预览时baseurl为空所以/assets/...能正常访问一旦部署到项目仓库baseurl变成/仓库名前端代码里所有资源路径都要带上前缀。这就是为什么同一套源码本地好好的上传后图片一片红叉的原因。5.4 中文文件名、分页和其他容易忽略的细节还有几个零碎问题穿插讲讲。第一文章文件名别用中文。Jekyll 理论上支持中文文件名但 URL 编码问题会让你在自定义链接、RSS 输出时遇到各种奇怪状况。建议统一用带 slug 的英文文件名标题显示中文完全没问题。第二分页功能。如果你想首页只显示每页 5 篇文章需要在Gemfile里加入jekyll-paginate插件GitHub Pages 原生支持然后在_config.yml里设置paginate: 5并把首页的模板改成使用paginator变量。很多主题已经帮我处理好了但如果你自己写首页模板这里容易踩坑。第三Markdown 与纯 HTML 混排时的缩进问题。Jekyll 默认使用 kramdown 解析 Markdown如果你的 Markdown 里嵌入了div或img标签要特别注意缩进层级。缩进错误会导致 HTML 被解析成代码块页面看起来完全失控。5.5 本地与线上版本不一致的终极对策梳理一下其实所有诡异的线上问题最终都能归结为一个核心原则尽量让本地环境和线上环境一致。如果你走 GitHub Pages 原生构建就用github-pagesgem如果你走 Actions 构建就让 Actions 里的 Ruby 版本和本地保持一致。我曾经因为本地 Ruby 升到 3.2 而线上还是 2.7导致某个依赖 API 行为不同文章列表排序全乱了。后来在 Actions 的ruby/setup-ruby步骤里明确锁定了ruby-version: 3.2再把本地 Ruby 也统一到 3.2问题就再也没有出现过。这类问题排查起来很耗时间但对治措施就这么简单版本锁定全链路统一。6. 上线之后的扩展方向搜索、SEO与更多部署选择6.1 给博客加一个可以用的站内搜索Jekyll 默认没有站内搜索文章多了之后找东西全靠浏览器搜索或外部搜索引擎。轻量级方案是生成一个search.json把所有文章标题、内容和链接聚合到一个 JSON 文件里然后在前端用 JavaScript 做过滤匹配。Jekyll 天然支持这种玩法只需要在项目根目录建一个search.json文件内容大致如下--- layout: null --- [ {% for post in site.posts %} { title: {{ post.title | escape }}, url: {{ site.baseurl }}{{ post.url }}, content: {{ post.content | strip_html | strip_newlines | truncate: 300 }} }{% unless forloop.last %},{% endunless %} {% endfor %} ]构建之后访问https://你的域名/search.json就能看到结构化文本再配合一个输入框和几行过滤代码站内搜索就跑起来了。更复杂的方案是用 Algolia 之类的第三方索引服务但免费额度有限个人博客用search.json完全够用。6.2 SEO基础把细节一次做对静态博客天生对搜索引擎友好因为页面预先生成好权重加载快。但如果你想在搜索结果里看到自己的文章还得自己做好几个基础细节。首先是_config.yml里的title和description这是搜索引擎理解你站点主题的第一手材料。其次是每篇文章的 front matter 里要维护好title和descriptionJekyll 的 SEO 插件能自动生成 meta 标签。然后是 URL 结构。默认情况下 Jekyll 的文章路径可能是/2024/01/01/xxx.html如果你希望更简洁可以在_config.yml里设置permalink: /:year/:month/:title/这样链接变成/2024/01/hello-jekyll/看起来更干净也更利于分享和记忆。最后是sitemap.xml。很多主题模板自带 sitemap 生成如果没有可以装jekyll-sitemap插件GitHub Pages 白名单内或手动在项目根目录建一个模板文件完成后提交到 Google Search Console 和百度站长平台收录速度会明显加快。6.3 如果有一天想离开GitHub PagesGitHub Pages 零成本是很香但它也不是没有限制只支持公开仓库、免费版本对构建频率有一定限制、国内访问速度偶尔不稳定。如果你日后想迁移Jekyll 的静态构建特性会让这件事变得异常简单因为网站最终就是一堆 HTML 文件。备选方案很多Netlify、Vercel、Cloudflare Pages 都支持一键导入 GitHub 仓库并自动构建 Jekyll 项目。迁移时只需要删掉 GitHub Pages 相关的配置在目标平台新建一个项目连接同一个仓库设置好构建命令bundle exec jekyll build和输出目录_site发布就算完成了。本质上你换的只是“托管方”写作方式和内容文件完全不用动。不过从我个人的经验来看除非遇到非常具体的限制GitHub Pages 依然是个人博客最稳定的归属。免费的全球 CDN、Git 原生流程、零运维这套搭配已经足够一个写作者用很多年。最后说点掏心窝的话。很多人搭博客时会把大量时间花在换主题、调样式、试各种插件上仿佛这一步做不好博客就开不下去了。可等你真的发布完第一篇文章回想起来就会发现真正能让你持续写下去的动力还是“打开 Markdown 文件直接敲字”这个动作够不够顺畅。Jekyll GitHub Pages 这组方案最大的优点就是它能安安稳稳地站在后台不打断你的写作节奏也不给你惹麻烦。希望这篇文章能帮你把博客平稳搭起来然后剩下的就是把时间留给写作本身。