# Show HN: hmr-serve, hot-loading web app for any folder (MIT)

> Source: <https://github.com/floodtide/hmr-serve>
> Published: 2026-07-17 12:01:45+00:00

HMR-serve is inspired by `npm run dev`

and by [ serve](https://www.npmjs.com/package/serve). It turns any folder into a hot-loading web app, because sometimes it's nice to preview HTML, Markdown, images and other files in the browser.

Point `hmr-serve`

at any folder and review it live in the browser: a VSCode-style
file tree on the left, a content pane on the right and **instant updates as files
change on disk**, powered by Vite's real dev server and
[HMR](https://vitejs.dev/guide/features.html#hot-module-replacement) client. 🔥

**HMR** (Hot Module Replacement) is the mechanism that pushes file changes into the
browser over a WebSocket so the page updates without a full refresh. This project
uses [Vite's HMR](https://vitejs.dev/guide/features.html#hot-module-replacement)
rather than inventing its own.

Tip:Helpful for reviewing AI agent output (markdown/HTML docs) as it iterates.

```
npx hmr-serve ./some-folder
# → opens http://localhost:5183 with a live tree + content pane
```

Or run it like ** serve** without the nav, which serves a directory listing or

`index.html`

if present, plus rendered Markdown, CSV/TSV, all with HMR:

```
npx hmr-serve ./some-folder --no-nav
```

For a permanent setup, install globally and use the short `hmr`

command:

```
npm install -g hmr-serve
hmr ./some-folder
hmr ./some-folder --no-nav
```

CLI:

```
hmr [dir]              Folder to serve (default: current directory)
  --port <port>        Port to listen on (default: 5183)
  --open               Open the browser on start
  --no-nav             Like serve, without the tree UI
```

**Default (nav) mode** shows a VS Code-style file tree and content pane. Supported
types: **Markdown** (rendered), **HTML** (sandboxed iframe), **code** (`.css`

,
`.js`

, `.ts`

, `.json`

, … syntax-highlighted), **text**, **CSV/TSV** (tables),
**images** and **video**. A gear menu toggles which categories show in the tree,
plus an **Other files** option. Choices are remembered per folder (localStorage).

** --no-nav** skips the tree UI: if the folder has

`index.html`

(or `index.htm`

),
that is the root page; otherwise you get a directory listing. Markdown, CSV and
TSV are still rendered to HTML, and edits hot-reload via Vite.**No build step for your content.** Runs in Vite dev-server mode only. The app shell (a tiny Vue UI) is Vite's root; your folder is mounted alongside it via middleware +`server.fs.allow`

. Your files*are*the content — nothing is compiled.**Live search index.** Built at startup with[MiniSearch](https://github.com/lucaong/minisearch)(no native deps) and kept current incrementally off file-watch events — no rebuild step.**Plain HTTP + WebSocket, cross-browser.** The browser only talks to the Node server over HTTP and Vite's HMR socket. No File System Access API, no Chromium-only APIs — works in Safari and Firefox.**Extensible renderer registry.**`.md`

→`markdown-it`

;`.html`

→ sandboxed iframe;`.css`

/`.txt`

→ server-side`highlight.js`

;`.csv`

/`.tsv`

→ HTML tables styled with GitHub markdown CSS. New types register in`src/server/renderers/`

without touching routing.**Lightweight CLI** via:`cac`

`<path>`

,`--port`

,`--open`

,`--no-nav`

.

Live content updates ride Vite's HMR channel: the server pushes a custom
`hmr-serve:update`

event and the content pane re-fetches — so Vite's client
handles transport and reconnection for us.

**Links & URLs.** Raw HTML is served at mirror paths (`/__hmrserve/raw/<file>`

),
so **relative** links and assets inside a framed doc (`href="contact.html"`

,
`<img src="logo.png">`

) resolve against the real folder layout. The selected file
is reflected in a clean, bookmarkable URL under the served folder's name — e.g.
serving `./aurora-notes`

gives `/aurora-notes/docs/intro.md`

. These are real
History-API paths (no `#`

): shareable, reload-safe and back/forward works.
Unknown paths 404 rather than rendering the app, so a stray absolute link can't
nest the shell inside the content pane.

```
src/
  cli/       arg parsing + entry
  server/    Vite plugin, middleware, search index, watcher, renderers
  client/    Vue app shell (tree + content pane)
  shared/    types shared across server and client
fixtures/    versioned sample files used by tests
tests/       unit · component · e2e
```

See [CONTRIBUTING.md](/floodtide/hmr-serve/blob/main/CONTRIBUTING.md) for local setup and test expectations.
