A Zodiac extension for hot reload of server-rendered applications using ring-hot-reload.
When server-side code changes, the browser automatically re-fetches the page and morphs the DOM in place using idiomorph, preserving scroll position, focus, and form state.
(ns myapp
(:require [zodiac.core :as z]
[zodiac.ext.hot-reload :as z.hot-reload]))
(defn handler [_request]
[:html
[:body
[:h1 "Hello world"]]])
(defn routes []
["/" {:get #'handler}])
(z/start {:routes routes
:extensions [(z.hot-reload/init)]})
That's it — save a file or eval a form in your editor and the browser updates automatically.
zodiac.ext.hot-reload/init accepts an optional map of options, passed directly
to ring.hot-reload.core/hot-reloader:
:watch-paths — directories to watch (default ["src"]):watch-extensions — file extensions that trigger reload
(default #{".clj" ".cljc" ".edn" ".html" ".css"}):uri-prefix — WebSocket endpoint path (default "/__hot-reload"):inject? — predicate (fn [request response]) controlling script injection
(default: always inject into full HTML pages):debounce-ms — debounce window in ms (default 100):bust-css-cache? — append cache-busting param to stylesheet URLs on reload
(default false)Example with custom options:
(z.hot-reload/init {:watch-paths ["src" "resources/templates"]
:debounce-ms 200})
For a full development experience with Vite asset management and hot reload, use together with zodiac-assets:
(require '[zodiac.ext.assets :as z.assets]
'[zodiac.ext.hot-reload :as z.hot-reload])
(z/start {:routes routes
:extensions [(z.assets/init {:manifest-path "myapp/.vite/manifest.json"
:asset-resource-path "myapp/assets"
:vite {:mode :dev-server}})
(z.hot-reload/init {:watch-extensions #{".clj" ".cljc" ".edn" ".html"}})]})
In this setup:
When using with a Vite dev server, narrow :watch-extensions to server-side
files only (exclude .css) since Vite handles CSS/JS HMR natively.
For the best development experience, add the ring-hot-reload nREPL middleware so that evaluating code in your editor triggers a browser reload automatically — no file save needed.
Add .nrepl.edn to your project:
{:middleware [ring.hot-reload.nrepl/wrap-hot-reload-nrepl]}
Or configure it in CIDER via .dir-locals.el:
((clojure-mode
(eval . (progn
(make-local-variable 'cider-jack-in-nrepl-middlewares)
(add-to-list 'cider-jack-in-nrepl-middlewares "ring.hot-reload.nrepl/wrap-hot-reload-nrepl")))))
See the ring-hot-reload README for more details.
Copyright © 2025 Brett Adams
Distributed under the MIT License. See LICENSE for details.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |