Clojure Ring Hot Reloading HTML in the Browser

In some cases when you're developing a full-stack app you want to be able to refresh your website when static assets change automatically. For example HTML templates or CSS files.

There's a nice little utility tool ring-refresh to achieve just that.

Here's an example app that uses Selmer templates to render HTML. During development we want the browser to update to have a faster feedback loop in the browser.

Here's a small example of how this works!


Add the dependencies.

{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        ring/ring-core {:mvn/version "1.6.3"}
        ring/ring-jetty-adapter {:mvn/version "1.6.3"}
        selmer/selmer {:mvn/version "1.12.59"}
        metosin/reitit {:mvn/version "0.7.0-alpha5"}
        ring-refresh/ring-refresh {:mvn/version "0.1.3"}}}

Create a template file resources/templates/layout.html. And remember to add the <head> tag because the ring-refresh will depend on that having in place already. If that's missing the reload will not work because the middleware uses a regex to recognize where to inject the hot reload script. This took me a moment to realize so just a heads up.

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <div>hello {{name}}</div>
        <div>
            <ul>
                {% for item in items %}
                <li><strong>{{item|capitalize}}</strong></li>
                {% endfor %}
            </ul>
        </div>
    </body>
</html>

And here's the app. Notice the selmer.parser/ache-off! call if you are using extended templates.

When rendering files Selmer will cache the compiled template. A recompile will be triggered if the last modified timestamp of the file changes. Note that changes in files referenced by the template will not trigger a recompile. This means that if your template extends or includes other templates you must touch the file that's being rendered for changes to take effect.

Alternatively you can turn caching on and off using (selmer.parser/cache-on!) and (selmer.parser/cache-off!) respectively.

https://github.com/yogthos/Selmer#important

(ns acme.app
  (:require [selmer.parser :as selmer]
            [ring.middleware.refresh :as refresh]
            [ring.middleware.params]
            [ring.adapter.jetty :as jetty])
  (:gen-class))

(selmer.parser/cache-off!)

(defn app [_request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (selmer/render-file "templates/layout.html"
                             {:name "World"
                              :items ["one" "two" "three" "four"]})})

(defonce server (atom nil))

(defn start! []
  (reset! server
          (jetty/run-jetty (refresh/wrap-refresh #'app)
                           {:port 8080 :join? false})))

(comment
  (.stop @server)
  (start!))

Now when you update your templates the browser should refresh the page automatically and save you a bunch of time and nerves. I hope you found this useful!