This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-11-22
Channels
- # aleph (5)
- # announcements (9)
- # babashka (9)
- # beginners (127)
- # cherry (1)
- # cider (48)
- # clj-kondo (5)
- # cljdoc (1)
- # clojure (70)
- # clojure-berlin (1)
- # clojure-europe (57)
- # clojure-france (2)
- # clojure-germany (1)
- # clojure-nl (2)
- # clojure-norway (4)
- # clojure-uk (1)
- # clojurescript (2)
- # css (1)
- # cursive (6)
- # emacs (6)
- # gratitude (1)
- # honeysql (5)
- # introduce-yourself (5)
- # jobs-discuss (7)
- # joyride (1)
- # kaocha (3)
- # lsp (1)
- # malli (9)
- # nbb (2)
- # off-topic (91)
- # pathom (7)
- # pedestal (14)
- # re-frame (4)
- # reitit (67)
- # shadow-cljs (46)
- # spacemacs (3)
- # squint (3)
- # tools-build (14)
- # tools-deps (1)
- # vim (3)
How should I separate reitit
routes when develop SPA with frontend/backend separation?
The problem is:
IF I put all /api
requests to backend handler
AND put every other requests to frontend html-page (with compiled js via shadow-cljs
)
AND I have some routes on frontend (via reitit.frontend
)
THEN (from @vale)
> this does have the negative effect that your server returns 200 ok, even for routes that end up with a "page missing" error.
I found this docs https://github.com/metosin/reitit/blob/master/doc/advanced/shared_routes.md
But I dont understand it.
So first part "Using reader conditionals" is about to use 1 shared .cljc
file for routes and separate them via clojure reader conditionals #?
But what is about second part "Using custom expander"?
In docs example, first we create shared .cljc
routes, then use it as is in frontend routes, but then dont use it at all in backend routes (I dont see any routes
using there). Why? In this example we just re-write all routes in backend part
the :expand option is in reitit.core meaning you can use it the same from frontend and backend.
i think what you noticed is just a mistake in the docs and you'd use the routes
defined in cljc on the server as well
this method also has the advantage that you can generate urls using an uniform route naming scheme both on backend and frontend which is pretty awesome
> i think what you noticed is just a mistake in the docs yeah probably, will try to use it > this method also has the advantage what do you mean?
if you have a common routes.cljc with routes
like in the example, you can always be sure that reitit's match-by-name will always return the same route for the same name. no worrying about having to maintain the same names separately on the frontend and on the backend
aaaah I see. And on backend :expand
part I just use that keyword names for api handlers?
but how is about custom options on frontend? Like :view
?
as i said before i haven't used this method myself so please experiment haha
i get the impression that your :expand
function takes the current "value" for the route -- in that example, ::kikka
and returns the appropriate options for that route.
in a backend ring router that'd be like in the example. on the frontend it'd return a map with :view
or :controllers
according to your needs
Make sense!
Now I have to double all frontend routes in backend :expand
part and anyway it doesnt work (links are not working)
@U055NJ5CC please help me
you need to provide more information than "anyway it doesnt work" when asking for help
It works now after hours of debugging 😄 https://github.com/temanmd/clojure_learning_foobar
but I dont know is it proper way or not
For example, I did not use ::keyword
as routes name keyword, I used a common :keyword
(because if I use ::keyword
I need to write full namespace chain like :foobar.shared.routes/index
instead)
And main issue/thought - I still need to put all frontend routes at my backend routes config (see https://github.com/temanmd/clojure_learning_foobar/blob/main/src/foobar/backend/routes.clj)
So I dont see any advantage to use reitit shared routes
still
Reitit frontend also supports fragment routing, which is the default even. With fragment routing the backend doesn't see the frontend routes and only needs to provide the index route.
@U061V0GG2 do you mean this? https://github.com/metosin/reitit/blob/master/doc/basics/route_data.md#route-data-fragments Do you use it in production projects?
No, I mean the frontend urls look like /#/frontend/route
The backend doesn't see the path after #
so from the view of the backend, user is always requesting just the index file
but it not a good solution from user POV and from search engines POV
I don't see why a user would care what the url looks like. If you are building a site that is indexed, sure, that is a valid reason.
It just looks more clean and humanized 🙂
I think at least previously for example Node.js + React projects just returned the index.html from not-found handler. Frontend will then check if the current route is found on the frontend route tree, and can display not-found error if the route isn't found there either.
Or you can indeed share the frontend route tree to the backend as cljc, and then check if the path exist there before serving index.html
Yes, look I described my solution here https://clojurians.slack.com/archives/C03S1L9DN/p1669198181260149
So I have shared cljc
routes
I think you could either place the frontend tree under backend route tree and replace/add index.html handler for each frontend route, or implement not-found handler which manually checks the frontend tree with match-by-path and returns index.html if a route was matched, or the 404 otherwise.
but dont understand why I have it 😄 if I need to describe it again at backend
you can use a case
instead of a literal map
(case route-name
::api/whatever api/whatever-handler
::api/something api/something-handler
;; else
return-index-html)
@vale but in this case I still cant return HTTP 404
because it will be at index-html
already
You shouldn't need duplicate frontend route trees
https://github.com/temanmd/clojure_learning_foobar/blob/main/src/foobar/frontend/app.cljs#L78 I'd move this tree to the cljc file
The view component references aren't available, but you don't need them in backend, so you could just wrap them in #?(:cljs home-page)
@vale I do it already see https://github.com/temanmd/clojure_learning_foobar/blob/main/src/foobar/frontend/app.cljs#L102
So I use :expand
on both sides
ah sorry I have to remove it)
Last question is only about to require rewriting frontend routes here https://github.com/temanmd/clojure_learning_foobar/blob/main/src/foobar/backend/routes.clj#L32
hm, I will try to use ""
as a general starting point for all frontend routes
If your frontend routes include parameter validation etc, those might be unncessary on the backend. For backend to serve the index.html it is probably enough that the path matches, and the frontend can handle the parameter validation.
(defn my-expand
[data opts]
(if (keyword? data)
(case data
:api api-handler
;; else
frontend-handler)
(ring-core/expand data opts)))
{:expand my-expand}
Now I only pass handler on backend for all frontend routes. And other options/validations are on frontend part already (via :expand)
@vale will try now
I might write the parameter declarations to the shared file still, to keep most of the frontend route tree in one place, and then just toggle the validation off when using those routes on the backend. But either way works.
yeah the expand takes all the route data so you can look at the (:name data) if you decide to use maps
but this solution ruins "HTTP 404" return
no, moment
Don't know how to create ISeq from: foobar.backend.routes$frontend_handler
Also had a error:
Don't know how to create ISeq from: foobar.backend.routes$my_expand$fn__2780
if I use expand func like this:
(defn my-expand [data opts]
(fn [data opts]
(if (keyword? data)
(case data
:api api-handler
frontend-handler)
(ring-core/expand data opts))))
I fixed it
It WORKS!!!!!!!! 🥳
@vale I change your code a little: https://github.com/temanmd/clojure_learning_foobar/blob/main/src/foobar/backend/routes.clj#L17
because it should return {:handler handler-func}
format, not just a handler
Thank you man!
I'm a bit confused why you'd want to do all that, this is what my routes look like in one of my projects:
;; frontend-routes cljc file
(defn routes []
[[""
#?(:clj {:get layout/public-page}) ;; <-- line to not having missing page on any routes
["/"
{:name :home
:view :home
:controllers [{:start (fn [_]
#?(:cljs
(rf/dispatch [:page/init-home])))}]}]
["/requests"
[""
{:name :requests
:view :requests}]
rest of routes ...
;; handler.clj
(mount/defstate app-routes
:start
(ring/ring-handler
(ring/router
[(frontend/routes)
(api/routes)])
(ring/routes
(ring/create-resource-handler
{:path "/"})
(ring/create-default-handler
{:not-found
(constantly (error-page {:status 404, :title "404 - Page not found"}))
:method-not-allowed
(constantly (error-page {:status 405, :title "405 - Not allowed"}))
:not-acceptable
(constantly (error-page {:status 406, :title "406 - Not acceptable"}))}))))
Your example works good too
Look at my example repo. I dont use #?(:cljs)
, I just mark routes names in my cljc
file and then use :expand
to separate front/back end routes
I mean in your example you mix frontend routes functionality (view, controller, dispatch) inside shared cljc
file
My solutions is more clean because I describe frontend part only in frontend files cljs
and leave only plain vector in cljc