﻿---
title: Adding Hot Reload to Hexo
date: 2026-04-08
excerpt: A lightweight script that makes the Hexo dev server auto-refresh the browser whenever you save a post.
tags:
  - Blog
  - Hexo
  - JavaScript
  - Workflow
updated: 2026-07-08 21:17:39
lang: en
i18n:
  cn: /hexo_hot_reload
  translation: 2
---

`{sh} hexo server` natively watches for file changes and regenerates pages, but unfortunately it never tells the browser to refresh. After every edit, we still have to switch back to the browser and hit <kbd>F5</kbd> or <kbd>⌘ + R</kbd>.

I had used Astro before, and it could do this. Since it was clearly feasible, I asked CodeX to produce a script: user updates a `.md` file -> Hexo regenerates the page -> the browser automatically refreshes the current `http://localhost:4000` page.

I've never studied frontend systematically, but from using tools like Astro and Vite I'd already experienced this "hot reload" feature, so I was fairly sure the idea was doable. I asked CodeX to write a script that implements: **user updates .md -> Hexo regenerates the page -> browser refreshes automatically.**

## Experiences: Wish I Had This Sooner

With the script in place, I can now split my screen: editing the `md` file on the right, and Edge/Chrome/Zen/Safari showing a live preview on the left. The improvement to the experience boils down to:

1. Most directly, no more manual refreshing. Since the blog couldn't hot-reload in the browser before, I usually made do with the editor's built-in preview
2. Previewing in the browser is true, out-of-the-box **WYSIWYG**

### WYSIWYG: Browser or Editors?

On the WYSIWYG point, mainstream editors all ship with Markdown preview these days, but the results are underwhelming. Typora looks nicer, but its editing experience falls a bit short of Obsidian/VSCode/NeoVim/Zed.

Also, in pursuit of better [UI/UX](/tags/UIUX), I often stitch **HTML code** into my Markdown. Most editors' preview engines choke on these custom tags and fail to render them correctly, which of course isn't the editors' fault.

Some cases that fail to render correctly:

- Shiki syntax-highlighting Transformers: a plugin in Obsidian can partially render them, but its syntax follows the Expressive Code standard
- Some markdown-it plugins: popular ones like callout/math/mermaid preview fine in Obsidian/Typora, but the less common yet very practical tab component doesn't. Obsidian's wikilinks have the same problem
- Custom HTML Elements (Web Components): see [[custom_elements|this post]] for how custom HTML Elements are used; the components defined on this site are showcased on [[test_components|this page]]
- CSS styles embedded in Markdown can't be previewed either

## Appendix: Source Code

Hexo automatically loads scripts placed in `scripts/` under the project root or the theme directory. I chose to create [` scripts/hot-reload.js `](https://github.com/Efterklang/gnix-blog-theme/blob/main/scripts/hot-reload.js) in the theme's `/scripts` directory

> Since GitHub offers a better code-reading experience and stays more up to date, I won't paste the code here

## Appendix: How It Work

Since this was vibe-coded, naturally I have no idea how it works; as long as it works 🤓

Just ask the AI for a straight, no-dodging explanation, ~~do we even need one?~~

BTW, the output has been lightly edited and trimmed; the model I actually asked was Gemini in AI Studio, but its logo is too ugly, so I'm swapping in OpenAI's logo for now

<script type="module" src="/js/components/chat.js"></script>
<x-chat>
  <chat-message name="GnixAij" avatar="https://assets.vluv.space/avatar.webp" is-me>
    Explain how this works and what technologies it uses
  </chat-message>
  <chat-message name="ChatGPT" avatar="https://cdn.svglogos.dev/logos/openai-icon.svg">

<div class="chat-heading">Core Technology: SSE (Server-Sent Events)</div>

At its core, the script uses **SSE (Server-Sent Events)**, a technology that lets the server push real-time notifications to the browser.

<div class="chat-heading">How It Works</div>

1. **Environment detection (`isHexoServerCommand`)**

    The script first checks whether it is running under `hexo server` mode. This ensures that these development-only bits are not injected during a static `hexo generate` build or a production deployment.

2. **Listening to the Hexo lifecycle**

    - `hexo.on('generateAfter')`: fires when Hexo finishes regenerating pages. Every time Hexo completes a regeneration, the script triggers a refresh. Since the Hexo server automatically regenerates on file changes, listening to this single event is enough.

3. **Debounce & Cooldown**

    To prevent frequent editor saves (such as auto-save) from making the browser refresh like crazy, the script adds a `150 ms` debounce and a `500 ms` cooldown interval, keeping refreshes both timely and stable.

4. **Middleware injection (`server_middleware`)**

    The script opens a special "secret handshake" endpoint inside Hexo's built-in server: `/__hexo_live_reload`. The browser stays connected to this endpoint, waiting for the server's signal.

5. **Frontend injection (`injector`)**

    Using Hexo's `injector` feature, the script automatically inserts a tiny piece of JavaScript at the end of every page's `<body>`. This code listens for the `reload` signal from the server and calls `window.location.reload()` when it arrives.

    </chat-message>
    <chat-message name="GnixAij" avatar="https://assets.vluv.space/avatar.webp" is-me>

What is `window.location.reload`? What is `__hexo_live_reload`?

  </chat-message>
  <chat-message name="ChatGPT" avatar="https://cdn.svglogos.dev/logos/openai-icon.svg">

<div class="chat-heading">1. What is <code>window.location.reload()</code>?</div>

This is a method provided by the browser's native JavaScript, and what it does is very straightforward: **it refreshes the current page**.

- **Effect:** Equivalent to pressing <kbd>F5</kbd> on your keyboard, or clicking the "refresh" button next to the browser's address bar.
- **Role in the script:** When the browser receives the "file updated" message from the server, it runs this line. You no longer have to click refresh yourself; the page automatically updates to the latest state.

<div class="chat-heading">2. What is <code>/__hexo_live_reload</code>?</div>

This is a **virtual communication address (Endpoint)**.

In a normal Hexo preview, visiting `http://localhost:4000` shows your blog. But this script quietly opens a hidden "private line" in the background: `http://localhost:4000/__hexo_live_reload`.

Here is how it works:

- **It's a "pipe":** This address doesn't exist on your disk. It is a **long-lived connection channel** (based on SSE) created by the Node.js script intercepting requests.

- **Server side (Hexo):** When you see `req.url === LIVE_RELOAD_PATH` in `scripts/live-reload.js`, it is telling Hexo: "If a browser requests this address, don't serve it page content — keep the connection open and stand by to send signals."

- **Client side (browser):** The script puts `new EventSource('/__hexo_live_reload')` into the page. This line tells the browser to keep "listening" on this address, like waiting by the phone.

---

<div class="chat-heading">Summary: How Do the Two Work Together?</div>

1. **Connect:** The browser opens your blog and automatically connects to the `/__hexo_live_reload` pipe.
2. **Wait:** The browser waits at the other end of the pipe; nothing happens.
3. **Trigger:** You edit a `.md` file, and Hexo regenerates the page.
4. **Signal:** The Hexo server pushes a message into the `/__hexo_live_reload` pipe: `{"type": "reload"}`.
5. **Action:** The browser receives the message on its end and immediately calls `window.location.reload()`.
6. **Result:** The page is fresh.

    </chat-message>
</x-chat>
