ring 2026-09-20

I'm working on a server-rendered website while learning Clojure, Ring, Compojure etc; could somebody help me understand something about handlers & middleware?

I have a function main-layout whose purpose is to wrap its inputs with the site's basic HTML (using https://github.com/escherize/huff):

(defn main-layout [req & content]
 (str
  (huff/html
  [:body
   (when (logged-in? req) [:p "logged in."])
   [:nav>ul
   [:li>a {:href "/"} "home"]
   [:li>a {:href "/log-in"} "log in"]]
   [:main content]])))
Wrapping all my handlers with this works fine, but I had hoped to reduce the tedium a little bit by creating a middleware that would handle this wrapping business for me—however, it seems like Ring doesn't like handlers returning vectors: No implementation of method: :render of protocol: #'compojure.response/Renderable found for class: clojure.lang.PersistentVector. This is the wrapper I've put together:
(defn wrap-layout [handler]
 (fn [req]
  (let [response (handler req)]
   (assoc response :body (main-layout req (:body response))))))
So either this just won't work the way I thought, or I'm doing something really wrong. Is the usual way to share a layout just to wrap all the handlers manually, or is there a better way?
(defn home-page
 "The way that works"
 [req]
 (main-layout
  req
  [:h2 "home page"]))

(defn home-page-concisely
 "The way I want it"
 [_req]
 [:h2 "home-page"])

Is your repo up somewhere public where we can see the whole thing? Hard to tell what to suggest from what you've show here...

Ring handlers must return a Ring response, so you'll need something like:

(defn home-page-concisely
  [_req]
  {:status 200
   :body [:h2 "home-page"]})

☝️ 1

So this isn't Ring per se, but Compojure. Compojure uses the compojure.response/render function to turn a value, like a string, into a full response. Your middleware is applied after Compojure tries to render the return value into a response, so Compojure errors before it gets to your middleware. There are two solutions. The first is to always return a map with the vector as the :body, as @jkrasnay suggests. This might be good practice, as it means you're always explicit about which response code and headers you're using. Alternatively, you can extend the compojure.response/Renderable protocol treat vectors as response bodies:

(extend-protocol compojure.response/Renderable 
  clojure.lang.APersistentVector
  (render [v _]
    {:status  200
     :headers {"Content-Type" "text/html; charset=utf-8"}
     :body    (str "\n" (huff/html v))}))
With this protocol extension, any bare vector will be converted into a string of HTML. This allows you to return bare vectors:
(GET "/" []
  [:html [:head [:title "Hello World"]] [:body "Hello World"]])
While also allowing you to return maps when you need custom headers or response codes.
(GET "/404" []
  {:status 404
   :body [...]})

That's very interesting! I wasn't sure if it was Compojure or Ring doing some magic with those plain strings. I'm going to play around with extending the protocol like you suggest, thank you