Nested routes with compojure

344 views
Skip to first unread message

simon lomax

unread,
Jan 2, 2016, 11:35:08 AM1/2/16
to Ring

Index.html

<!doctype html>
<html>
    <head>
      <link rel="stylesheet" href="assets/css/material-design-iconic-font.min.css">
      <link rel="stylesheet" href="assets/css/re-com.css">
    </head>
        <body>
            <script src="js/app.js" type="text/javascript"></script>
       </body>
</html>


Compojure routes

(defn root-handler [_]
  (resp/content-type (resp/resource-response "/index.html" {:root "public"}) "text/html"))


(defroutes app-routes
           (GET "/" [] root-handler)
           (context "/fake" []
             (GET "/" [] (str "<h1>" "Meaningless URL just to test we get a response" "</h1>"))
             (GET "/sub" [] (str "<h1>" "Meaningless sub level URL just to test we get a response" "</h1>"))
             )
           (ANY "*" [] root-handler)   
 )

 (def app
   (->
     app-routes
     (wrap-reload)
     (wrap-authentication auth-backend)
     (prone/wrap-exceptions)
     ; wrap-defaults adds lots of standard middleware such as wrap_params, wrap_cookies etc
     (wrap-defaults site-defaults)))


I have an index.html file and some routes as defined above. When I navigate to localhost:3000/fake or localhost:3000/fake/sub
the "meaningles ..." text is rendered as I would expect.

If I navigate to something like localhost:3000/employees my (ANY "*" [] root-handler) route renders the index.html and then the my client side javascript kicks in and renders correctly.
However If navigate to something like localhost:3000/employees/sub (note a "sub" route of a employees) then I get errors in the Chrome console stating that 
localhost:3000/employees/assets/css/material-design-iconic-font.min.css cannot be found. 

The browser now seems to think that localhost:3000/employees/ is the root and not localhost:3000/ 

How can I fix that?




 

James Reeves

unread,
Jan 2, 2016, 11:49:27 AM1/2/16
to Ring

Use /assets/... in your HTML instead.

Also, that (ANY * [] ...) route is rather strange. I'm not sure why you have it.

- James

--
You received this message because you are subscribed to the Google Groups "Ring" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ring-clojure...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

simon lomax

unread,
Jan 2, 2016, 12:22:04 PM1/2/16
to Ring, ja...@booleanknot.com
Pretty sure I've tried /assets but that gave other problems. The ANY route was supposed to re-serve the index.html again if no other route could be found, then the client should take over and render the route if it is supposed to. I should have mentioned I'm using HTML5 push state and routes w/out #'s. So everything gets to the server first and if the server can't serve a particular route the client then tries. 

I'll try again using your suggestions and report back. Thanks.

simon lomax

unread,
Jan 3, 2016, 12:42:49 PM1/3/16
to Ring, ja...@booleanknot.com
Setting assets to /assets and setting path to compiled js file to js/compiled/app.js

Gives following errors:

Refused to execute script from 'http://localhost:3000/employees/js/compiled/app.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

Uncaught ReferenceError: metime is not defined

Setting assets to /assets and setting path to compiled js file to /js/compiled/app.js (note prefixed with /)

Gives following errors:


Refused to execute script from 'http://localhost:3000/employees/js/compiled/out/goog/base.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

Refused to execute script from 'http://localhost:3000/employees/js/compiled/out/cljs_deps.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

Uncaught ReferenceError: metime is not defined

Anything else I can try?

James Reeves

unread,
Jan 3, 2016, 2:08:56 PM1/3/16
to Ring
It's likely you're encountering that problem because of your (ANY "*" [] ...) route.

Normally a website will return a 404 if a resource hasn't been found, but you've set it up so that if it doesn't find a page it returns the index page. So you've got your asset paths wrong somehow, but instead of getting a 404, the index page is returned instead, and the browser is complaining because it's seeing a HTML resource instead of the expected Javascript.

- James
Message has been deleted
Message has been deleted

simon lomax

unread,
Jan 4, 2016, 4:57:01 AM1/4/16
to Ring, ja...@booleanknot.com
Thanks for replying again James. I appreciate your help. Yes I agree that normally a website would return a 404 if a resource or route is not found. But in my case I'm specifically trying to avoid that. My setup, if you recall is that I have a ClojureScript front end and then essentially a Clojure API at the backend. Because I'm using HTML5 push state with URI's do not include #'s and therefore all routes I type into the browser address bar initially end up at the server. Then based on my Compojure routes, the server then decides if the URI is not "/" or doesn't start with "/api" then it should simply return the index.html (hence the need for for the ANY "*" []... route). At that point the javascript takes over and handles any routes like localhost:3000/employees. All of which works perfectly, no problems at all with any asset paths, no errors in the browser developer tools.

Its only when I try to navigate to a URI that has a second slash in it. i.e. localhost:/3000/employees/99 that the problems arise. The browser then seems to think that the root is localhost:3000/employees as opposed to localhost:3000/.  

Its these "sub" routes that cause the problem. 

Just to clarify the routes now look like this:

(defn root-handler [_]
  (resp/content-type (resp/resource-response "/index.html" {:root "public"}) "text/html"))

(defroutes app-routes
           (GET "/" [] root-handler)
           (GET "/fake" [] (str "<h1>" "Meaningless URL just to test we get a response"  "</h1>"))

           (context "/api" []
             (GET "/auth-token" [] (build-auth-token))
             (ANY "/departments" [] (departments))
             (ANY "/departments/:id" [id] (department id))
             (ANY "/department/:id" [id] (department id))
             (ANY "/employees" [] (employees))
             (ANY "/employee/:id" [id] (employee id))
             (ANY "/holidays" [] (holidays))
             ;(ANY "/holidays/:id" [id] (holiday id))
             (route/not-found "Not Found"))
           ;; All other routes failed. Just serve the app again
           ;; and let the client take over.
           (ANY "*" [] root-handler)
           )

On the index.html file, all the asset paths start with a slash i.e. /assets/... (as you correctly suggested) and the js file tag looks like this:

<script src="js/compiled/app.js" type="text/javascript"></script>
<script>metime.client.main();</script>

The build section of my project.clj file looks like this:

:cljsbuild {
              :builds [{:id           "dev"
                        :source-paths ["src/cljs"]

                        :figwheel     {:on-jsload "metime.client/mount-root"}

                        :compiler     {:main                 metime.client
                                       :asset-path           "js/compiled/out"
                                       :output-to            "resources/public/js/compiled/app.js"
                                       :output-dir           "resources/public/js/compiled/out"
                                       :source-map           "resources/public/js/compiled/app.js.map"
                                       :source-map-timestamp true}}
                       {:id           "min"
                        :source-paths ["src/cljs"]
                        :compiler     {:output-to     "resources/public/js/compiled/app.js"
                                       :main          metime.client
                                       :optimizations :advanced
                                       :pretty-print  false}}]}


If I navigate to any top level route like localhost:3000/employees everything works fine. However If I navigate to any sub level route like localhosty:3000/employess/19
I see the following error in the Chrome dev tools: 'http://localhost:3000/employees/js/compiled/app.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

James Reeves

unread,
Jan 4, 2016, 9:25:12 AM1/4/16
to Ring
You still need to change the other relative paths. So your script tags should look like:

    <script src="/js/compiled/app.js" type="text/javascript"></script>

And your :asset-path option should be:

    :asset-path "/js/compiled/out"

Anything with a relative path that's served to the browser won't work if the URL differs.

I also don't think that having a catch-all route at the end is a good idea. Ideally you should narrow it down so that it returns the index page if and only if the route is known to be valid.

- James

--

simon lomax

unread,
Jan 4, 2016, 9:38:04 AM1/4/16
to Ring, ja...@booleanknot.com
Hi James,

Yes that seems to fix things now. I was actually just having the same conversation with @pupeno over on a slack channel and he came up withe the same solution. No errors in the browser now and its getting back to my client side code which is perfect. 

Thanks again for all your help.

Marius Rabenarivo

unread,
Mar 29, 2020, 6:58:28 PM3/29/20
to Ring
Hi Simon,

I encounter the same problem in a project where I'm using Reagent/Secretary in the front-end and Ring/Compojure as a back-end.

It would be cool if you can share with me how you managed to deal with this issue :)

Kind regards,

Marius
Reply all
Reply to author
Forward
0 new messages