Clojure(script) and Tomcat - URLs relative to Context?


TL;DR shadow.cljs app.js served by Ring in Cloujre app deployed in Tomcat via lein uberwar, how to generate context-relative links (so relative URLs in HTML/JS will be relative to instead of

I am currently working on mixed lein + shadow-cljs app (lein for backend, shadow-cjls for frontend). During development, I start backend with lein and ring and start frontend with shadow-cljs pointing at backend URL (, let’s say).

On „production” situation looks a bit different. I don’t need and don’t want separate backend and frontend so I first do shadow-cljs release app, that creates app.js file inresources/public/js and my Ring server hosts:

(defroutes app
  (ANY "/api/config/commit" [] config-commit)
  (GET "/" [] (ring-response/redirect "index.html"))
  (route/resources "/")
  (route/not-found "Page not found"))

After that, lein uberwar produces nice WAR for backend with integrated frontend, ready for Tomcat deployment. Awesome!

But I’ve quickly ran into issues. In general, the problem is that all my URLs in my fronted are relative, like:

[:> bs/NavItem {:event-key 1 :href "/status"} [bold-if-active :status "Status"]]

Unfortunately, after deployment in Tomcat my app becomes available under URL like: - and my Status URL takes me to the which is obviously invalid.

Here are some clues how to do this in JSP: - but it all seems very JSP-specific and I have no clue how to apply this to my ClojureScript problem.

Am I doing something extremely unsupported (this lein + shadow mix)? Or am I missing something? It’s obvious that it should not work the way I’ve done it, but how to do it then?


Why do you have a weird context path like that in the first place?

The general strategy for things like this is having a helper fn used to generate all paths in a system.

So you’d change your code to:

[:> bs/NavItem {:event-key 1 :href (env/context-path "/status"} [bold-if-active :status "Status"]]

The context-path fn can than use one of the several options for injecting config data into your application. :closure-defines are one option but other ways are just as valid.


(goog-define CONTEXT "")

(defn context-path [s]
   (str CONTEXT s))

Then in your build options for release builds you can set:

{:closure-defines { "/whatever"}}

Note that none of this has anything to do with shadow-cljs at all. It applies to all JS basically. You need to configure the path to use somehow and make sure your app uses that config.

That’s standard way of handling WARs in Tomcat and it’s default context path for Tomcat apps.

I’m not sure if it answers your question but we use two different mechanisms in our app to make sure that relative links are correct (we have a standalone web app version without any context as well as “WAR” version deployed to Tomcat with a non-empty context path):

Assuming I know the context URL, how should I define routes then? Because with Secretary route like:

(secretary/defroute status "/status" []
  (rf/dispatch [:change-page :status]))


does not work. Should secretary routes also be context-dependent?

The HTML <base> tag changes how relative paths work, so js/app.js and <a href="status"> would be relative to /myapp-0.1.0-SNAPSHOT-standalone/ even if you were on /myapp-0.1.0-SNAPSHOT-standalone/config/users. I don’t know whether it works with (CL)JS routing libraries, though.

I am trying to follow your advice. I’ve managed to add <base> tag with to hosted index.html but I am having problems accessing this base URL from ClojureScript.

In my index.html I’ve got:

    <head><base href="/myapp-0.1.0-SNAPSHOT-standalone">

although, in chrome inspector console, I am getting:

> window.baseUrl
< undefined

what am I doing wrong?

I forgot to mention that we have following piece of code in our master html template:

// Expose site base URL to Javascript.
window.baseUrl = '{{view-context}}/';

Again, view-context is filled on the backend based on the value of the :context key.

Oh! That changes a lot! :slight_smile: Thanks!

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.