DEV Community

Cover image for Routing: I’m not smart enough for a SPA

Routing: I’m not smart enough for a SPA

Taylor Hunt on April 19, 2022

In part 2, I glossed over a lot when I wrote… I decided this called for an MPA. (aka a traditional web app. Site. Thang. Not-SPA. Whatever.) Ok...
Collapse
 
jon49 profile image
Jon Nyman

With service workers you can get the same benefits of an MPA but have everything be instant on your device by streaming the HTML from the service worker itself. The main downfall of this is that the service worker isn't mature yet in that I can't load modules on-the-fly with dynamic imports. I created my own importer to do this but it turned into so much work maintaining it all that I just ended up using a library similar to HTMX (my own HTMF).

Apps for the general public I think MPAs make a lot of sense. Apps for internal business I think something like HTMX (or HTMF :-)) can make really good sense (a mix between MPA and SPA).

It really depends on the constraints of the project to determine if a full SPA is needed or a mixture or just an MPA.

I haven't used SPAs a lot but one thing that I don't like is the repaint between pages when I'm doing the MPA/SPA with the service worker when I don't stream the HTML from the service worker. It is much cleaner to do the latter. But, like I said before, it is difficult to do when you don't have a framework built that way already as most people are focused on SPAs rather than a service worker app with streamed HTML for each page.

Collapse
 
tigt profile image
Taylor Hunt

I got on-the-fly imports working well in Marko, since its Rollup plugin already has logic for server vs. client bundles. (I lied to @marko/rollup in just the right way so that it also made out a third, tree-shaken bundle that thought it was for the server, then I transpiled it for the SW environment.)

That repaint between pages is interesting if you still have it — Chrome was the last browser to remove that flicker when it shipped Paint Holding in 2019, I thought. When and where are you seeing it?

Collapse
 
jon49 profile image
Jon Nyman

Oh, and the on-the-fly imports (dynamic imports) I was talking about I was more talking about doing it in a service worker. Which isn't allowed in service workers yet - but I have created my own, but it is a bit of a pain to do :-) compared to just having it work out of the box.

Thread Thread
 
tigt profile image
Taylor Hunt

Yeah, I reused the Rollup ecosystem’s code for that, myself. Pretty odd that import still isn’t allowed in Service Workers, but at least importScripts() lets us fake it.

Thread Thread
 
jon49 profile image
Jon Nyman

Yeah, actually, I'm looking for specifically dynamic imports so I only need to load the code is only being used at the moment rather than having to load all the pages code at once. So this:

developer.mozilla.org/en-US/docs/W...

For regular imports they work with the latest Chrome as of earlier this year (if I remember right). But Firefox still doesn't have it.

But this discussion has been great. It's made me think of how I can simplify my offline app more using a truer MPA style coding.

Yes, I'm definitely looking forward to your next post! Thank you for putting all the time and effort into these posts!

Collapse
 
jon49 profile image
Jon Nyman

Yeah, I guess the repaint issue is because I created my own "spa" framework. Basically, it isn't SPA but kind of is. Since I have separate HTML pages cached with a service worker and then I have all the code on the front end (since I have the app as an offline first app). So, each time I go to a different page it has to load the data on the fly after changing the page. So, I have a list of my weight for the year with comments and such and when I go to that page it takes time to get all that data onto the page since it has to load the JS first before adding the dynamic data. If I did it from the service working the user wouldn't notice all that. I guess that is why SPAs do their own routing so it doesn't seem so janky. I don't really work with SPAs much since I'm more of a back end person but I imagine in a SPA they create the page first before moving over to the new page. Also, like you, I don't like the size of JS on the front end and frameworks seem to be pretty beefy and cause issues for the user - like you were mentioning. That is why I've been pushing for doing everything from the service worker. I was thinking of just loading all the code in the service worker and just streaming the HTML from there.

But there are also issues with service workers. Like, when using a service worker ideally you have the SHA unique characters appended to your file name. But that is hard to do without getting involved in the SPA world. So, tooling isn't really great when doing it this way. But maybe I'll pick it up again and try it again. I haven't been doing as much coding on the side as I have in the past as I've been trying to spend more time with the family before they leave the house :-). But I get some free time here and there.

Thread Thread
 
tigt profile image
Taylor Hunt

It’s very interesting that you and I seem to be converging on the same ideals, but from opposite directions. You might like the next post in this series for that especially.

Collapse
 
jon49 profile image
Jon Nyman

Your post inspired me to rewrite my app in an MPA style with minimal JS. Granted it is an offline app so it is all JS but it is written from a service worker. It really simplifies things. Make a post on a form - no problem, just refresh the page! I still get the occasional white flash on the page when going between pages, which is annoying. But the simplicity is amazing.

I'm excited for your next posts!

github.com/jon49/WeightTracker/tre...

Collapse
 
tigt profile image
Taylor Hunt

Oh damn! I am going to be reading the heck out of this code — you may have pulled off what I was trying to demonstrate for the next post faster than I could!

Collapse
 
jon49 profile image
Jon Nyman • Edited

lol, Thanks. Yeah, it isn't the first time that I've attempted this. That's why I was able to do it so fast. I've been fascinated about running an app from a service worker ever since I heard Chris Love say that service workers are the death of SPAs.

I have actually created a module loader to load JS on-the-fly from a service worker so you can have a pretty large app. But it was too much work and I just wanted to finish the app. So, I ended up making it with HTMF and Razor Pages (C#).

github.com/jon49/MealPlanner/blob/...

Chris Love's website: love2dev.com/

Collapse
 
frontsideair profile image
Fatih Altinok

Love the header image.

I'm mostly convinced that client-side routing is almost always bad news. I was already disillusioned with SSG and currently enjoying Remix a lot. So I'm looking forward to the next installment.

Collapse
 
tigt profile image
Taylor Hunt

Yeah, I think at this point I like everything about Remix’s technical approach except for React itself. Remix’s principles and fundamentals make a lot of sense to me — but React’s tradeoffs clash with them sometimes.

Collapse
 
frontsideair profile image
Fatih Altinok

I'm also conflicted about it. Progressive hydration and streaming SSR with Suspense ease some of the pain, but there are still some drawbacks. On the other hand, since changing the DOM is unavoidable in most apps, I like using a single mental model for building and modifying the DOM, and React is a pretty stable choice. I'm still evaluating new choices that I see.

Thread Thread
 
tigt profile image
Taylor Hunt

I agree wholeheartedly. With those priorities, you might as as interested in Marko as I was. It keeps the mental model of a tree of components, but is much more efficient with SSR and doesn’t need a client-side router.

Thread Thread
 
frontsideair profile image
Fatih Altinok

Marko has been on my radar for a long time, but I guess unfamiliarity was a significant barrier for me. There's also SolidJS which has similar claims, for that matter. I guess at some point I'll have to bite the bullet and write a small project in either one to gain some first-hand experience.

Collapse
 
danrot90 profile image
Daniel Rotter • Edited

Totally agree with all the stuff you'ce written here, but there is one thing that bothers me even more from a user perspective: MPAs that use frontend frameworks for extremely basic stuff. I don't want to wait for the JS to load (on every page load) to see the header etc. That feels like the worst of all worlds without any benefits, and seems not to be as uncommon as it should be.

Collapse
 
jon49 profile image
Jon Nyman

Another hard thing about MPAs is that I haven't been able find documentation on how to build robust MPAs. That knowledge is hard to find with searches pigeon holing old web pages. So, e.g., how do you prevent a double click? If I remember right from what I've read you need to do a redirect after a form submission and somehow that stops the double click. There are other patterns like that, but where to find them?

Collapse
 
tigt profile image
Taylor Hunt

Ah, you’ve got two concepts there — which I think reinforces your point that this knowledge isn’t easily accessible.

Redirecting after form submission avoids the “Really resubmit this data?” dialog when hitting Back after a <form method=post>, known as the Post/Redirect/Get technique.

Preventing double-clicked form submissions without JavaScript doesn’t have a single name: each framework tends to have its own (WordPress reuses WP_NONCE, for example). I like calling them “idempotency keys/tokens” after Stripe’s API popularized the term. (Stripe uses an HTTP header, but as you probably suspect it’d be <input type=hidden> for no-JS functionality, such as the django-idempotency-key module.)

Idempotency keys are a good idea even if you have JS briefly disable a submit button after the first click, because it covers network hiccups too. For example, browsers have built-in HTTP retry logic, but if the server’s response was the one that got lost along the way, the browser+network combo could cause duplicate requests.

As for where to find these techniques… I also wish I knew! I had to find them the hard way, almost exactly like you described. Heck, I only learned about idempotency keys last month.

Collapse
 
cmgustin profile image
Chris Gustin

I’m loving these articles, even if some of it is a little over my head on the technical side. I’m blown away at how deeply you know the topics, and the writing is not just clear and communicative, it has style and voice which is rare in technical writing.

Out of curiosity, the references are really thorough. Are you constantly reading and bookmarking things to use day-to-day, or do you search out what you need and research as you write?

Collapse
 
tigt profile image
Taylor Hunt

You guessed it: I have an ongoing bookmarks folder for this. Sometimes I do search for things when I need a stronger case or a more up-to-date link, though.

Collapse
 
marcsw2 profile image
Marc Swikull

Hi Taylor,

This is an excellent post if for no other reason than it's thoroughness--it's a rare gem to see so much thought put into a subject matter with great cross-references. I happen to think you're on the money in many cases, too!

Collapse
 
katsanos_george profile image
George Katsanos

Liked and followed. Excellent deep-dive. I guess this debate started a bit more than a year ago, with the infamous Twitter exchange, then the Jake/Surma video youtube.com/watch?v=ivLhf3hq7eM and the overall abundance of choice in terms of SSR/SSG frameworks (SvelteKit, Elder, Astro).
A general React fatigue - which I'm personally happy to see as it was monopolizing the web/frontend sphere for a while now.

Collapse
 
redbar0n profile image
Magne

SPAs can easily embed in an MPA. (The reverse, though… if it’s even possible, it sounds like it’d inherit the weaknesses of both without any of their strengths.)

MPA embedded in SPA: It sounds like Hotwired.dev Turbo, formerly known as Turbolinks. It stays on the same page (akin to an "SPA"), but on navigation it sends an AJAX request, receives HTML, and replaces the title and entire body of the existing page, and updates the URL with pushState. The good old PJAX approach.

Collapse
 
jazzypants1989 profile image
Jesse Pence

Check out this video for an unholy union of Astro and Turbo to see exactly what you describe:

youtube.com/watch?v=6mv0_jsWhoE

Collapse
 
hypeddev profile image
Oliver Williams

"I asked a maintainer of a popular React router for his take:" which one?

Collapse
 
tigt profile image
Taylor Hunt

He requested not to be named, so I’m respecting that