I’m currently rewriting apps that were build using the approach mentioned in the article into SPA’s both at work and at home.
He makes an interesting point, while I agree with some of the things that he points out I think it’s not the complete story.
Downsides to that approach:
- Maintaining template/logic on the server & client has it’s challenges. A lot of the time they get out of sync.
- What’s the offline story ?
- What about websockets story ?
- Do you have an api that you’re offering to external services, does you website use that api ?
- Can you easily reuse data that you just fetched ?
- What about all those page refreshes when loading more items in listing or changing the page ?
The author makes a point that the experience might be better for non-spas. But he’s referring to “half-baked” implementations. I think the SPA user experience is a lot better if you get it right.
The reason I think most SPA’s don’t get it right (also guilty of this on some older projects) is because it’s a lot harder. And there are a few things that people don’t pay attention when there writing them.
A few opinionated thoughts on this:
Use a framework
SPA’s mean that you have a lot of things going on in the client side. You can go about using a minimalist library and doing your own thing where you know how everything works, not that much of a learning curve etc… Personally I really like the idea of standing on the shoulder of giants , building you’re own is just building you’re own “framework”, most of the time this means undocumented, improvised/not well thought out, unless you work on it nothing improves. Doubt a lot of people can do anything remotely close to fulcro, re-frame, keechma, citrus. There documented, well tested, have a lot of libraries like debugging tools etc… just don’t see any plus in the long run for doing you’re own thing instead of using one of those and contributing to them.
Normalize you’re app-state
In SPA’s you have a lot of state, no more refresh and start off with a clean slate. Fulcro has state normalization built in at it’s core, in re-frame it also seems to be the recommended approach.
Use a graph-api
Since most of you’re logic is on the front-end, the backend api should allow the frontend to easily request exactly the nested data structures it needs (restfull api just adds a lot of complexity & network overhead). You can use data-oriented api like om-next query syntax or GraphQl. I am leaning twards graphql since it’s an industry standard and something that I can also expose to third-party clients, the om-next query is pretty clojure specific. The best part is that pathom also has support for graphql so you can have a graphql server and in the client pathom will handle the om-next query sintax <-> graphql conversions.
Now you have a nice graph-api backend, but how does you’re client take full advantage of it ? You can have smart loaders and keep the components in sync with them, but this can be streamlined. Here using something like fulcro in clojurescript or apollo-client & relay in javascript starts to make a lot more sense. What these have in common is that the’re designed to work with a graph-api & have mechanisms built in for normalizing the data app-state. The big idea there is component “co-located queries” & auto-normalization. Now if you add/remove something from the component query the changes will be reflected in the backend api call.
Implement Server side rendering
Rum and fulcro do ssr on the JVM pretty well. With a bit of work seems like reagent could also work nicely.
If you can, nodejs works nicely with clojurescript, I really like the nodejs & npm experience with shadow-cljs a few other examples apps here.
Html links
Use html5 routing and do “link hijacking”. Ex: <a href="/about" onclick="your-fn">about</a>
where you-fn does e.preventDefault()
and triggers the route change. This way if you disable javascript and have server side rendering it should work as a normal website.
Pre-load route content
I see this a lot… you change to a different screen and see a bunch a loading states. Why not just change the route, add a loading marker and stay on the same page until the content for the page loads. Then just trigger the screen change. It’s not really that hard if you start with this idea from the beginning a nice read js and redux. The user experience is a lot nicer and it also simplifies the UI.
Start with code-splitting
A big of a headache but you need to keep the bundle size as small as possible. I’ve found it really hard to add code-splitting mid-way into a project. Think it’s really worth it to build your app with code-splitting in mind.