﻿---
title: 为Hexo添加热更新
date: 2026-04-08
excerpt: 通过一个轻量脚本，为 Hexo 本地开发服务器增加文章保存后自动刷新浏览器的能力。
tags:
  - Blog
  - Hexo
  - JavaScript
  - Workflow
updated: 2026-07-12 09:54:38
i18n:
  en: /en/hexo_hot_reload
  translation: 2
---

<script type="module" src="/js/components/chat.js"></script>

`{sh} hexo server` 虽然原生支持监听文件变化并重新生成页面，但遗憾的是它不会主动通知浏览器刷新。每次修改完文章，我们还是得手动切回浏览器按下 <kbd>F5</kbd> or <kbd>⌘ + R</kbd>。

之前用过 Astro，它能做到这一点。既然是可行的，于是就让 CodeX 输出脚本：用户更新 `.md` -> Hexo 重新生成页面 -> 浏览器会自动刷新当前 `http://localhost:4000` 页面。

虽然没系统学过前端，不过之前用Astro、Vite 等前端工具时，也体验过“热更新”这个 feature，大概能确认这个需求是可行的，于是就让CodeX 帮忙写一个脚本，实现：**用户更新 .md -> Hexo 重新生成页面 -> 浏览器自动刷新。**

## Experiences：相见恨晚

引入脚本后，现在也可以电脑分屏，右边放 修改 `md` 文件 ，左边放 Edge/Chrome/Zen/Safari 看实时预览。体验方面的提升可以这样概括：

1. 最直接的，不需要手动刷新。过去由于 Blog 不能在浏览器热更新，通常就凑合着用编辑器内置的预览
2. 在浏览器中预览，是真正的、开箱即用的**所见即所得**

### 所见即所得：Browser or Editors?

关于所见即所得这点，现在主流编辑器都内置Markdown预览，但效果差强人意。Typora 比较美观，但编辑体验略逊于 Obsidian/VSCode/NeoVim/Zed。

另外，为了追求更好的 [UI/UX](/tags/UIUX)，我经常会在 Markdown 里缝合一些 **HTML 代码**。大多数编辑器的预览引擎在处理这些自定义标签时都会“翻车”，无法正确渲染，当然这不是编辑器本身的缺点。

列出一些无法正确渲染的情况：

- Shiki 代码高亮的 Transformers： 目前在 Obsidian 中使用插件可部分渲染，但语法采是基于Expressive Code 的标准
- 部分markdown-it 插件：其中比较流行的 callout/math/mermaid 在 Obsidian/Typora 均能预览，但是相对小众但却很实用的 tab 组件却不能。此外 Obsidian 的双链也是如此
- 自定义的 HTML Elements(Web Components): 自定义 HTML Elements 的用法可以见[[custom_elements|这篇文章]]；本站所自定义的组件陈列在[[test_components|该页面]]
- Markdown 内嵌入的 CSS 样式同样不能预览

## Appendix: Source Code

Hexo 会自动加载项目根目录/主题目录下 `scripts/` 里的脚本。这里选择在主题目录下的 `/scripts` 目录中新建 [` scripts/hot-reload.js `](https://github.com/Efterklang/gnix-blog-theme/blob/main/scripts/hot-reload.js)

> 考虑到 GitHub 的代码阅读体验更好，更新更及时，这里不贴代码

## Appendix: How It Work

既然是 vibe，那么实现原理自然是一概不知，能用即可🤓

直接让 AI 给我一个不饶、不躲的解释，~~需要吗？~~

BTW，输出进行了适当调整/删减；这里实际问得是 AI Studio 中的 Gemini，不过它的 Logo 太丑了，暂且换成 OpenAI 的 Logo 吧

<x-chat>
  <chat-message name="GnixAij" avatar="https://assets.vluv.space/avatar.webp" is-me>
    <p>解释一下工作原理，使用的技术</p>
  </chat-message>
  <chat-message name="ChatGPT" avatar="https://cdn.svglogos.dev/logos/openai-icon.svg">
    <div class="chat-heading">核心技术：SSE (Server-Sent Events)</div>
    <p>脚本的核心使用了 <strong>SSE (服务器发送事件)</strong>。这是一种允许服务器向浏览器推送实时通知的技术。</p>
    <div class="chat-heading">实现流程</div>
    <ol>
      <li>
        <strong>环境检测 (<code>isHexoServerCommand</code>)</strong>
        <p>脚本会首先判断当前是否运行在 <code>hexo server</code> 模式下。这样可以确保在执行 <code>hexo generate</code> 静态构建或部署到生产环境时，不会注入这些开发专用的代码。</p>
      </li>
      <li>
        <strong>监听 Hexo 生命周期</strong>
        <ul>
          <li><code>hexo.on('generateAfter')</code>：监听 Hexo 页面重新生成完成。每次 Hexo 完成页面生成后，脚本就会触发刷新。由于 Hexo server 会在文件变化后自动触发重新生成，所以只需要监听这一个事件即可。</li>
        </ul>
      </li>
      <li>
        <strong>防抖与冷却期 (Debounce &amp; Cooldown)</strong>
        <p>为了防止编辑器频繁保存（如自动保存功能）导致浏览器疯狂刷新，脚本加入了 <code>150 ms</code> 的防抖和 <code>500 ms</code> 的冷却间隔，确保刷新动作既及时又稳定。</p>
      </li>
      <li>
        <strong>中间件注入 (<code>server_middleware</code>)</strong>
        <p>脚本在 Hexo 内置服务器中开辟了一个特殊的“暗号”接口：<code>/__hexo_live_reload</code>。浏览器会一直连接这个接口，等待服务器发令。</p>
      </li>
      <li>
        <strong>前端注入 (<code>injector</code>)</strong>
        <p>通过 Hexo 的 <code>injector</code> 功能，脚本会自动在每个页面的 <code>&lt;body&gt;</code> 末尾插入一段极其微小的 JavaScript。这段代码负责监听来自服务器的 <code>reload</code> 信号，并在接收到信号后调用 <code>window.location.reload()</code>。</p>
      </li>
    </ol>
  </chat-message>
  <chat-message name="GnixAij" avatar="https://assets.vluv.space/avatar.webp" is-me>
    <p><code>window.location.reload</code> 是？<code>__hexo_live_reload</code> 是？</p>
  </chat-message>
  <chat-message name="ChatGPT" avatar="https://cdn.svglogos.dev/logos/openai-icon.svg">
    <div class="chat-heading">1. <code>window.location.reload()</code> 是什么？</div>
    <p>这是浏览器原生 JavaScript 提供的一个方法，它的作用非常简单直接：<strong>刷新当前页面</strong>。</p>
    <ul>
      <li><strong>效果：</strong>相当于你在键盘上按下了 <kbd>F5</kbd> 键，或者点击了浏览器地址栏旁边的“刷新”按钮。</li>
      <li><strong>在脚本中的作用：</strong>当浏览器接收到服务器传来的“文件已更新”的消息时，它会执行这行代码。这样你就不用手动去点刷新，页面会自动变到最新的状态。</li>
    </ul>
    <div class="chat-heading">2. <code>/__hexo_live_reload</code> 是什么？</div>
    <p>这是一个<strong>虚拟的通信地址（Endpoint）</strong>。</p>
    <p>在正常的 Hexo 预览中，你访问 <code>http://localhost:4000</code> 会看到你的博客。但这个脚本在后台偷偷开辟了一个隐藏的“专线”：<code>http://localhost:4000/__hexo_live_reload</code>。</p>
    <p>它的工作原理如下：</p>
    <ul>
      <li><strong>它是“管道”： </strong>这个地址并不存在于你的磁盘上，它是通过 Node.js 脚本拦截请求而创建的一个<strong>长连接通道</strong>（基于 SSE 技术）。</li>
      <li><strong>服务器端（Hexo）：</strong>当你在 <code>scripts/live-reload.js</code> 中看到 <code>req.url === LIVE_RELOAD_PATH</code> 时，就是在告诉 Hexo：“如果有浏览器请求这个地址，不要给它网页内容，而是保持连接不断开，随时准备发信号。”</li>
      <li><strong>客户端（浏览器）：</strong>脚本在页面里写了 <code>new EventSource('/__hexo_live_reload')</code>。这行代码的意思是让浏览器一直监听这个地址，就像在等一个电话。</li>
    </ul>
    <div class="chat-heading">总结：两者是如何配合的？</div>
    <ol>
      <li><strong>连接：</strong>浏览器打开你的博客，自动连接到 <code>/__hexo_live_reload</code> 管道。</li>
      <li><strong>等待：</strong>浏览器在管道另一头等待，无事发生。</li>
      <li><strong>触发：</strong>你修改了 <code>.md</code> 文件，Hexo 重新生成了页面。</li>
      <li><strong>发令：</strong>Hexo 服务器往 <code>/__hexo_live_reload</code> 管道里塞入一条消息：<code>{"type": "reload"}</code>。</li>
      <li><strong>动作：</strong>浏览器收到消息后，立即执行 <code>window.location.reload()</code>。</li>
      <li><strong>结果：</strong>页面刷新为最新状态。</li>
    </ol>
  </chat-message>
</x-chat>
