Interesting exercise. It explores the upper bounds of what can be done this way and proves that forms and links don’t need to be an SPA monstrosity.
But comparing this to the linked Astro version [0], Astro wins hands down. It feels faster, URL works, and behaves more predictably.
Turns out a music player is best built using SPA-y tech. And it is possible not to build a monstrosity along the way. These so-called island architectures are right on the money. It is server first, but breaking out of it and building some client side interaction is seamless when required.
Can we stop using expressions such as “SPA monstrosity” when this “unbloated HMTL” website breaks copying links, the back button, the address bar, don’t cache ressources properly, etc etc
I agree. They did a very cool exercise. But let be honest here, most of the users today would not put up with a UX like that. Even for a tech person who is used to some very hostile UX day in and day out, I would still not use that if I have the option.
To me this article and demo shows why we use SPA's, and how much modern frameworks actual do for you. It's hard to not see a whole lot of very basic problems in this demo that any modern framework would solve out of the box.
The "SPA bad" arguments are also largely outdated. Most apps that are build now are at least hybrid, with some SSR getting best of both worlds, urls and navigation are solved, accessibility is improving with better component libraries linters and best practices, automatic bundle splitting, highly optimized caching of static assets, build tools with hot reloading as a standard option, static exports, etc etc.
Getting a good Lighthouse score is not the flex it used to be (referring to the enhance movies demo), my all-JS Next.js app scores 100's without even trying.
The only potential argument would be users not having JS enabled, which is not relevant to most apps and use cases. In most cases you do not want to degrade the experience of most (but more likely all) of your users, and with SSR you can have it all.
Obviously build for your audience and use cases and apply technology that makes sense and I welcome experimentation with alternative approaches, but saying "spa bad" is imo not very productive.
When I started in the industry, progressive enhancement as a concept was the theme du jour. As SPAs have gained prominence, I’ve sadly watched the drive to improve them and sometimes pushback against them morph this concept as it’s taught to newcomers into “No JS” or “low JS” as a side effect. It lacks nuance.
For example, your menus should work without JS but that doesn’t mean they should work as well as or exactly the same way as they do with JS disabled. There are many features and patterns that a11y/screenreader users (which I recognize I’m somewhat lumping in with the “I disable JS for X reason(s) crowd here) appreciate that are still impossible without JS. I’ve surprised people on a couple occasions by showing them how much JS is actually used to create many of the WAI-ARIA menu proof of concepts[0], for example.
I think the “a11y is easy” crowd, well intentioned as it may be, is also partially to blame for this. I’ve read articles with titles like “How to do X with CSS only” that gloss over many UX downsides to the chosen approach.
The sad reality is that a11y users represent another audience with a different set of formed expectations one needs to cater to, and fulfilling those expectations is not free more often than some would care to admit, especially when you consider how complex the differences in approaches between screen readers can be. This isn’t an excuse to not help those users, but I have wondered before if we would be doing better if we were more scathing in our commentary on vendor standards as it relates to this. Who knows? Perhaps I’m naive.
At first, I thought it was a good exercise (and it still is), but going through the result [0] made me more skeptical.
It is... slow? I mean, Internet Explorer slow. Maybe I'm spoiled by the level of responsiveness of application-style web interfaces, but opening an album or returning to the library feels slow. Is it because I'm browsing from Western Europe and the application is hosted in the USA?
I'm used to browsing multi-page apps that don't pretend to be apps, and having a 500ms load time after a click is expected and feels right. But waiting the same time for a click in a page that looks like an app makes me uncomfortable. It's weird - is this the Uncanny Valley again?
Looks like the enhance-styles.css doesn't get cached properly and gets requested on every route. The browser then waits for 500ms for a response from a server, likely due to increased web traffic.
An issue which could have been avoided by using a SPA :D
the main page loads fast but the interactions are slow, like there's some artificial delay
the 500ms estimate above seems about right... it should be much faster. Navigating from one static page to another should be sub-100ms assuming the server is on the same continent
Am I getting old? As far as I can tell, this is what we were doing before JS became the mighty weapon it is today (like pre-2005).
So if you don't like JS, this is a pattern you might want to revive, but I can hardly believe, that we want to go back to that world as a standard for web development.
If you look at things like htmx and the principles that underpin that many would say the past 15 years have been a holding pattern waiting for browser APIs to catch up and provide a decent front end experience.
Personally I love working on in an “older” style way here the server just sends html to the browser and we don’t need to built an app twice (once in the fronted and once in the back end).
The real magic comes in having the older, full stack approach, sprinkling in JS as needed and then having modern CI/CD for deployments.
Not with this quality nor was it tried with modern tech. New times, new ways to do the same thing. I would prefer to code using this Enhance framework used by to develop this app than React.
The problem I see here is that it's not linkable - I can't see my current location in the url bar, which means I can't share it to other people. Maybe you could fix this with some JS?
Zero JS makes sense in some cases but this seems more code golfing than a real product that users would love.
I totally agree bloated SPAs are terrible and in most cases an MPA is the best option. But a bit of JS is certainly fine when it makes for a better experience. Even HN uses a bit of JS.
And what's the point of having all this bandwidth and CPU power available if we're not going to use it? (even one tiny bit)
There's a huge gap between some JS and Spas though. The question for me is always where data is rendered to markup, not whether it uses JS in the browser at all.
Everything comes at a cost, in this case it was deep linking. Even though back and forward buttons work, navigating to an album won't update the URL which won't allow sharing and will be bad for search engines since all navigation points to the same URL.
That's fairly easy to fix (in principle; I don't know what their backend is; it might require JS).
But there's so little value in the navigation, that I'm not surprised it works as an "MPA" (even though it feels janky). And that's all not done by JavaScript. The player, the animation, etc. still requires it. TBH, I don't see the point of this demonstration.
I built a podcast library/player[1] using HTMX and Alpine, using Django as the backend. It has an audio player that would stay open and keep playing while the user navigates around the page. This is quite easy to do with HTMX.
It's pretty simple - it's just for my own needs - but it works quite well. You can go far these days before needing an SPA.
I have to ask, partly because I went around this same circle in my head when evaluating htmx and deciding that it was cool enough but I didn't want every interaction to be a server round trip.
What do you get from combining htmx with alpine that you wouldn't get more simply from just alpine?
The way I saw it was that I'd end up with some state in the client, some on the server, some in json, some in html, my server would have to know how to render some components, the client others.. and for what benefit? Alpine can fairly easily do everything htmx does and it's one less framework to send to the user, one less thing independently watching and acting on the dom, one less dependency to manage, etc.
Alpine is just used for pure client lightweight interactions, such as toggling a menu (if CSS was insufficient) or (in this case) handling the audio player controls. Of course, I could even go more lightweight, and just use vanilla JS in such cases, but as both libraries are pretty small anyway it was easier to manage client DOM interactions with Alpine.
There's no JSON in this site: the only pure non-HTML interaction is a "ping" where the audio player posts the latest play time of an episode every few seconds while running, so that if the user switches to another device, or closes the tab, they get the latest runtime of the last episode they were listening to.
In terms of performance, I found there wasn't much difference between this approach and an SPA. For example, if I navigate from page A to page B in a React app, I still need to fetch JSON or GraphQL data for that new page, perhaps including multiple round trips to the server depending on the API design. With HTMX, I can just fetch a small HTML snippet: for example, if I click a "Subscribe" button, it just returns some new HTML showing "Unsubscribe" and updated URL or whatever. Sure you can do that with plain Alpine, but HTMX makes it easier to reason about state, which I'd need to otherwise manage client side (e.g. keeping a tally of all my subscribed podcasts).
HTMX also provides solutions for things like updating state in multiple places: for example you if you have a shopping cart, and you want to update not only the cart itself but a counter widget in the navbar, you can do "out of band" responses that can insert snippets of content outside the main target. Again, totally doable with Alpine, but more work for me.
And not every interaction has to be a server round-trip, you can certainly push work to the client side if needed, and use Alpine or vanilla JS or even React if you want: in a work project for example, we had most of the site in HTMX, but used React for a dashboard page with lots of complex interactive graphs and whatnot.
As I said, it was a pretty simple app, certainly not as complex as Spotify. But given that the often repeated example of when to use an SPA is "running an audio player while navigating around the site", it makes me wonder why the need to reach for React or other SPA framework for even simpler needs.
But comparing this to the linked Astro version [0], Astro wins hands down. It feels faster, URL works, and behaves more predictably.
Turns out a music player is best built using SPA-y tech. And it is possible not to build a monstrosity along the way. These so-called island architectures are right on the money. It is server first, but breaking out of it and building some client side interaction is seamless when required.
0 - https://astro-records.pages.dev/
The "SPA bad" arguments are also largely outdated. Most apps that are build now are at least hybrid, with some SSR getting best of both worlds, urls and navigation are solved, accessibility is improving with better component libraries linters and best practices, automatic bundle splitting, highly optimized caching of static assets, build tools with hot reloading as a standard option, static exports, etc etc.
Getting a good Lighthouse score is not the flex it used to be (referring to the enhance movies demo), my all-JS Next.js app scores 100's without even trying.
The only potential argument would be users not having JS enabled, which is not relevant to most apps and use cases. In most cases you do not want to degrade the experience of most (but more likely all) of your users, and with SSR you can have it all.
Obviously build for your audience and use cases and apply technology that makes sense and I welcome experimentation with alternative approaches, but saying "spa bad" is imo not very productive.
For example, your menus should work without JS but that doesn’t mean they should work as well as or exactly the same way as they do with JS disabled. There are many features and patterns that a11y/screenreader users (which I recognize I’m somewhat lumping in with the “I disable JS for X reason(s) crowd here) appreciate that are still impossible without JS. I’ve surprised people on a couple occasions by showing them how much JS is actually used to create many of the WAI-ARIA menu proof of concepts[0], for example.
I think the “a11y is easy” crowd, well intentioned as it may be, is also partially to blame for this. I’ve read articles with titles like “How to do X with CSS only” that gloss over many UX downsides to the chosen approach.
The sad reality is that a11y users represent another audience with a different set of formed expectations one needs to cater to, and fulfilling those expectations is not free more often than some would care to admit, especially when you consider how complex the differences in approaches between screen readers can be. This isn’t an excuse to not help those users, but I have wondered before if we would be doing better if we were more scathing in our commentary on vendor standards as it relates to this. Who knows? Perhaps I’m naive.
[0] https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/me...
It is... slow? I mean, Internet Explorer slow. Maybe I'm spoiled by the level of responsiveness of application-style web interfaces, but opening an album or returning to the library feels slow. Is it because I'm browsing from Western Europe and the application is hosted in the USA?
I'm used to browsing multi-page apps that don't pretend to be apps, and having a 500ms load time after a click is expected and feels right. But waiting the same time for a click in a page that looks like an app makes me uncomfortable. It's weird - is this the Uncanny Valley again?
[0] : https://enhance-music.com
An issue which could have been avoided by using a SPA :D
55ms for html, 67ms for css, 15ms for webp image.
I'm in bay area so it might be slower in other places
the 500ms estimate above seems about right... it should be much faster. Navigating from one static page to another should be sub-100ms assuming the server is on the same continent
So if you don't like JS, this is a pattern you might want to revive, but I can hardly believe, that we want to go back to that world as a standard for web development.
Personally I love working on in an “older” style way here the server just sends html to the browser and we don’t need to built an app twice (once in the fronted and once in the back end).
The real magic comes in having the older, full stack approach, sprinkling in JS as needed and then having modern CI/CD for deployments.
Cool idea though.
Deleted Comment
I totally agree bloated SPAs are terrible and in most cases an MPA is the best option. But a bit of JS is certainly fine when it makes for a better experience. Even HN uses a bit of JS.
And what's the point of having all this bandwidth and CPU power available if we're not going to use it? (even one tiny bit)
I tend to agree but it depends.
I made a small SPA a coupe of years ago with Mithril and the whole thing is like 40kB gzipped (JS and CSS).
It’s there when you need it? Using less bandwidth and cpu means using less battery.
But there's so little value in the navigation, that I'm not surprised it works as an "MPA" (even though it feels janky). And that's all not done by JavaScript. The player, the animation, etc. still requires it. TBH, I don't see the point of this demonstration.
It's pretty simple - it's just for my own needs - but it works quite well. You can go far these days before needing an SPA.
[1] https://github.com/danjac/radiofeed-app
What do you get from combining htmx with alpine that you wouldn't get more simply from just alpine?
The way I saw it was that I'd end up with some state in the client, some on the server, some in json, some in html, my server would have to know how to render some components, the client others.. and for what benefit? Alpine can fairly easily do everything htmx does and it's one less framework to send to the user, one less thing independently watching and acting on the dom, one less dependency to manage, etc.
There's no JSON in this site: the only pure non-HTML interaction is a "ping" where the audio player posts the latest play time of an episode every few seconds while running, so that if the user switches to another device, or closes the tab, they get the latest runtime of the last episode they were listening to.
In terms of performance, I found there wasn't much difference between this approach and an SPA. For example, if I navigate from page A to page B in a React app, I still need to fetch JSON or GraphQL data for that new page, perhaps including multiple round trips to the server depending on the API design. With HTMX, I can just fetch a small HTML snippet: for example, if I click a "Subscribe" button, it just returns some new HTML showing "Unsubscribe" and updated URL or whatever. Sure you can do that with plain Alpine, but HTMX makes it easier to reason about state, which I'd need to otherwise manage client side (e.g. keeping a tally of all my subscribed podcasts).
HTMX also provides solutions for things like updating state in multiple places: for example you if you have a shopping cart, and you want to update not only the cart itself but a counter widget in the navbar, you can do "out of band" responses that can insert snippets of content outside the main target. Again, totally doable with Alpine, but more work for me.
And not every interaction has to be a server round-trip, you can certainly push work to the client side if needed, and use Alpine or vanilla JS or even React if you want: in a work project for example, we had most of the site in HTMX, but used React for a dashboard page with lots of complex interactive graphs and whatnot.
As I said, it was a pretty simple app, certainly not as complex as Spotify. But given that the often repeated example of when to use an SPA is "running an audio player while navigating around the site", it makes me wonder why the need to reach for React or other SPA framework for even simpler needs.