SPAs are a pattern that's been applied too broadly IMO, but it's going a bit far to call them a mistake. The aims of an SPA are pretty noble - the idea of essentially removing the network round trip when a user clicks on something is not a bad one. It means things are faster, they work if your connection is flaky, they can do things like offline support. Those are good features.
They might not be actual requirements for a blog or a brochure site but it's not totally unreasonable for someone to want their website to work that way. The question is not whether or not an SPA is a good or a bad thing, but whether or not the cost (more code, more complexity, potentially a series of background requests after the initial page load) is worth paying in order to get the benefits.
When either an SPA or a multi-page site is done well most users can't tell which sort of site they're looking at. That should be the goal. Make stuff where the user forgets about the tech. Make a website that just works, that's fast enough for users not to think 'this is a bit slow', and that's coded in a way you can maintain and work on for a long time. If you get those things right then no one can reasonably criticise what you've made no matter how you've made it.
> the idea of essentially removing the network round trip when a user clicks on something is not a bad one.
Practical SPA's have many more network roundtrips than the equivalent server-rendered web interface. Every AJAX request is an extra roundtrip, unless it can be handled in parallel with others in which case you're still dependent on the slowest request to complete. With SSR, you can take care of everything with a single GET and a single POST no matter what. Images load in parallel of course, but those are non-critical.
SSR isn't a one-size-fits-all solution. Blogs? Sure. Corporate marketing site? Absolutely. Wikipedia? What else.
A webapp where state can exist in various components is a perfect fit for SPA-s. Clicking a button in a widget and submitting the page with every other state, updating them and refreshing the page makes no sense.
SPA-s also help with separating frontend and backend development, they are different beasts. I know many backend devs who wouldn't touch frontend with a 10m pole and not because of JavaScript, but because they don't like design and UX in general.
The distinct advantage of an SPA is that, done correctly, cached data lets you render pages instantly.
Who CARES if the SPA had to make 3xRTT in the background, if it can serve the next page up instantly because that data is already present and cached, it's a huge win.
The server rendered app will ALWAYS have to wait at least 1xRTT for every new render. The SPA does not.
Still doesn't make an SPA the right fit for everything, but on bad networks, we get incredibly improvements in performance by rendering from cache, and occasionally handling cache updates in the background. User's fucking love it compared to waiting 2-5 seconds for every page load, even if they were JUST on the page a second ago.
Unless their cache is cold and then they navigate back before your first render completes. Often we are not the sole source of a piece of information and first load time counts.
> Unless their cache is cold and then they navigate back before your first render completes.
This doesn't matter. It's an SPA - my js context hasn't been wiped because I'm not letting the back button eat the whole thing and start over. I just complete the request and store in cache either way, and next time they hit it, it will be there.
> Often we are not the sole source of a piece of information and first load time counts.
Sure - caching is hard (full stop - it's really hard). But I think if you actually consider most applications, there's not really much difference between an SPA rendering from cache and a server-side page that a user hasn't refreshed in a bit.
Basically - it's not going to solve all your problems, but in my experience developers really underestimate how much usage is "read only" and a cache works fine.
SPAS push giant gobs of logic and CSS ahead of the Time To Interaction and cross their fingers that nobody will notice because it’s not supposed to happen the next time due to magical cache thinking.
People have been ignoring actual research on caches for the entire time SPAs have been around. First time visitors have this as their first time visit. Occasional visitors show up after the last deploy, or like and enthusiastic web users get evicted between loads. And then don’t convert because your site is “always slow”.
P95 statistics don’t tell you if 5% of your users are having a bad experience. Or if new users are massively over represented in the P95 time. You are flying blind and shouting for other people to follow you.
To be fair, there are SPA frameworks that try to minimize the amount of logic that's shipped to the client, Svelte is an example, which should be as lean and efficient as any hand-coded JS. It's more of a side effect of what frameworks you use than SPA per se.
It's still a specialty sub-discipline though. Getting the base size down is a wonderful goal for a framework. But for the behavior the company is adding, there are both diminishing returns and a lot of surface area for bugs.
Look - I understand that I'm making a tradeoff here, and I'm very clear that not all sites are the right fit for an SPA.
But yes - I explicitly handle offline-only use cases, and god-damned terrible connections (think <1kb/s). In these situations, users are going to wait a long time for the first load either way.
They call in excited as all get out when they realize they only have to wait once with my application, and not once every page load. It's the difference between having a chance to grab a coffee at the start of the day, vs pulling your fucking hair out all day long.
Your application seems to be a magical unicorn (not being sarcastic!). Most SPA websites I use on a regular basis end up having to reload all of their page components every visit, and frequently on every 'back'.
I'll be really honest here - it helps that it was a direct requirement from day one, and we have financial incentives aligned with handling the offline-only case.
If you handle offline up front - you mostly get bad connection speed wins by default.
It also helps that our app really isn't intended for casual use - ex: we have very few users who just load a page or two and then leave. Mostly they'll be interacting with the page for a duration of between 30 minutes and 2 hours (sometimes as often as once a second).
So yes - very much not a "blog" or "news" or "social media" site, and I think the value proposition for most companies might be harder to sell.
But I personally find it very, very nifty that we can do this at all in a browser. 10 years ago it would have required an installed application, or a port to each mobile platform. Which is sadly still a hard sell in some of the particularly poor school districts we worked with (mostly computers from the early 2000s, if you're lucky a really cheap tablet - like a kindle - floating around)
> The server rendered app will ALWAYS have to wait at least 1xRTT for every new render. The SPA does not.
Not necessarily. I think you're conflating caching/fetching and rendering here. You can cache rendered views in the same way you'd cache the data for those views.
Turbo does this, for example. When you navigate to a cached page it will display the page from the cache first, then make a request to update the cache in the background. So it's similar to what you describe, but with server-rendered content.
Sort of. There's been no shortage of vulnerabilities where user data has been exposed when an authenticated users request was cached and re-used for subsequent users. It's been my experience that most SSR apps disable caching for authenticated users, or rely on more explicit control of components for partial caching where possible.
Preloading is a browser-native feature, you don't need a SPA for that. And just as often you can't know in advance what data will be requested by the user.
Server push gets you closer with a traditional app, but it doesn't really solve the problem (or rather - yes, you get to reinvent it yourself, but you can do it better, with the specific context of your app)
Basically - My argument is that developers tend to treat SPAs the same as "the website" but with a bigger js framework. And that's the wrong mindset.
Instead, you should think of the first load as an install of a native application. You want to push absolutely everything, not just the next couple of resources you might need (like imgs/css). Ideally you push the current page first, and do the rest in the background while the user can interact with the current page, since you're almost never CPU bound on terrible connections anyways.
You'll get one additional load at login (because now I'm pushing all relevant user data), but after that... basically everything is rendered from cache (including optimistic cache updates based on what the user might be adding/updating).
After that... unless you log out or have cache evicted, you're going to see immediate response times, even while offline.
---
This isn't easy, and most teams don't bother, so if the comment is "If you're treating an SPA like a traditional page - don't bother" then I tend to agree.
but an SPA genuinely opens up options for bad connections that otherwise just don't exist (or are so bad people won't use them)
Storage is cheap but bandwidth is not guaranteed. Getting content from your server to my device is not necessarily fast. Making wrong assumptions about my device's environment makes for a poor experience for me.
if you cache well - you will absolutely use less bandwidth with an SPA.
Sure - that first load is going to be bigger, but literally every subsequent request will use less data.
I try to reward consistent and current customers - and I'm not selling a news article or a social media site...
Now - if you do things like attempt to refresh all of the cache often, then sure, you have some problems to deal with.
> Making wrong assumptions about my device's environment makes for a poor experience for me.
Tough shit? I go way out of my way to make sure that loads on bad lines (or no lines) don't interfere with my users. You could just as easily say "I'm on gigabit why is this app using my disk space? Making assumptions makes for a poor experience. WAAAH".
Have you tried to use a heavily SPAed site from a slow, distant (high latency), or metered connection? A SPA that works and feels great from a big city quickly becomes unbearable when internet access isn't as ideal. There are ways to handle this nicely, but maybe 5% of devs actually think about and test that, and no PM will allocate sprint time for it.
Of course, and it's usually not as big of a problem because everything you need comes in the page render. With SPAs it's not uncommon to have to click 5 or buttons/menus and then wait for data to load, then click again, and more data.
That said there are plenty of SSR sites that are terrible too. It's not the technology that's at fault, but SPAs and common dev patterns in the community definitely enable and encourage making lots of small requests.
Worse yet, with a traditional web app, you are in control of loading the page - or stopping the load, for that matter. With an SPA, it does all those requests in the background. Many apps fail silently (as in, don't refresh) if request to the backend fails.
Sure, if that's your goal. It won't happen by accident, and the "default" method of SPAs make it really easy to fire off lots of different requests, often on demand.
It doesn't happen by accident in the same way that preventing an SQL SELECT N+1 problem doesn't happen by accident. Poorly written software is poorly written.
I know this is/was not your intention, but to be honest, I find that attitude insulting and frankly discriminatory.
The internet has become essential to modern life, and many people have no choice but to use it. Most of my monthly bills don't have any alternative to paying online. (Some will take a payment by phone but they'll charge a $20 "phone fee" or similar).
There are certainly sites/apps that just can't work without requiring a big and short pipe, but it's extremely defeatist to suggest that it's hopeless and we shouldn't even try.
You're certainly not alone with your opinion. I suspect that attitude is a big part of why this is such a problem nowadays.
Yes - that's literally exactly what I talked about.
I can tell you - My app renders immediately in these cases, precisely because I lean heavily on a cache.
I preload basically all the data a user might need at startup (prioritizing the current page) and then optimistically render from cache.
I'm very familiar with these kind of situations (I have developed software designed explicitly to handle offline-only cases, and I'm very familiar with how bad a connection might be in rural Appalachian schools.)
Awesome, thank you for doing that! I wish more people did. I've had several people tell me that "in a web app you only want to load the data you need, at the time you need it" and I die inside a little.
> I preload basically all the data a user might need at startup
This can be a great strategy when you're dealing with bounded data that's not sensitive, but it's important to recognize that this approach is often inappropriate. A web app may be allowing users to wander around terabytes of data, or it may need to make highly consistent authorization determinations before allowing a user to see specific data, or you may need to keep a highly detailed audit log of who requested what when (e.g., if the app targets healthcare or government users), which aggressive preloading would render useless.
I would counter that data freshness and authZ concerns make SPAs a poor fit for most things. What you're proposing in this thread is that good SPAs essentially need to hold all data client side and not have to worry about out of band cache invalidation, which seems like a pretty rare set of circumstances.
This works great as long as it is not a first visitor though. For new visitors with a spotty connection this will be a nightmare. Also I assume any new update you release which might need to refresh the cache would also be a problem in this situation. I don't think it's that easy. Not saying that you're not doing it right, just that it is not a trivial thing and almost everyone else won't do this right.
It's not that SPAs cannot handle that but it's rarely a requirement.
I've once launched a product on (early days) Google App Engine and it was unusable as purely server-side rendered website because sometimes it would take ages to load or worse an error would come up eventually. With AJAX it could be solved and failing requests could be repeated. It's a pity that this is rarely done but it is of course possible.
> Who CARES if the SPA had to make 3xRTT in the background, if it can serve the next page up instantly because that data is already present and cached, it's a huge win.
The data is never in the cache. It is for a developer because they forget to clear their caches when testing. For most users the data will not be cached unless they're sitting on the SPA all day.
So when the user doesn't have data cached that 3xRTT on their spotty 4G signal the SPA pattern is of no help. Not everyone is sitting still on great WiFi. Someone's cellular reception can go from great to unusable just walking down the street.
Between localstorage and indexdb - unless you're literally out of disk and having storage being evicted, then if you've loaded my app once (and I mean once, not once this browser session) then I have data in cache.
Basically - don't blame SPAs, blame developers for assuming that the same "request the data every page view" paradigm is still in play.
This feels a lot like you're making a “No True Scotsman” argument. The behaviour you're saying doesn't / shouldn't happen has been the defining characteristic of SPAs since the beginning and it's still immediately visible on most of them as soon as you have a less than perfect high-speed internet connection. If statistically nobody except the Wordle developer can make something which handles crappy WiFi the problem requires more than just saying someone is a bad developer.
> The distinct advantage of an SPA is that, done correctly, cached data lets you render pages instantly.
Yeah, but full page caching is a thing, and in my experience, teams writing traditional server-rendered pages are much more aggressive in their use of response caching than are teams writing JSON APIs. Honestly, a Rails/Django/Laravel app with smart caching headers and a Varnish instance in front feels more reliably instantaneous than the bespoke caching solutions each SPA seems to invent for itself.
How does that work if a user is logged in and private data is visible on the page?
I’m thinking of building something that only renders public data on the server, and then later gets that which pertains to individuals through an API. But I don’t want to spend more time on a lot of complicated plumbing than on core functionality in my application.
A SPA seems like the path of least resistance here, since all you have to do is put the appropriate cache control headers on your API endpoints, and let the browser abstract it away for you. I know programmers like to build bespoke caching solutions, been there, done that, but you don’t actually need to.
My biggest gripe with SPAs is the overhead of building and maintaining an API for everything, but GraphQL takes away a lot of that pain.
GraphQL is a protocol, as such it cannot take away the pain of creating a backend for your frontend.
There are tools which make the creation of the backend painless and utilize GraphQL for sure (i.e hasura), but claiming that GraphQL solved that issue is nonsensical, because writing the GraphQL API is generally way more annoying then the equivalent rest API is
Yes, I obviously mean leverage the existing tools, not roll my own implementation of the protocol, that would just be stupid and counter-productive.
Equivalent tools for REST and non-REST HTTP do exist, but none that I've seen has been as cohesive as for instance Apollo. I used it on a small side project and was amazed at how much time and effort it saved me. The alternatives are a patchwork of different tools with slightly different goals for things like client code generation, documentation generation, playgrounds, HATEOAS, etc. that have to each be evaluated and shoehorned in with the others.
I've seen a lot of projects that use a full response cache to capture a view with holes where user-specific information would go and then fill in those holes with some client side JS. I wouldn't call those SPAs, though.
But I'd say it's more common to just use "private" cache headers in this case for any page that differs for logged in vs unauthenticated users. The user's browser will still serve as a full page cache, but shared caches will ignore those pages.
Big assumption. This is what the whole discussion is about. Doing "correctly" an SPA is incredibly expensive.
I can also assure you that when an MVC application is done correctly you can have an equally good user experience.
> The server rendered app will ALWAYS have to wait at least 1xRTT for every new render. The SPA does not.
This is an outdated idea of how server rendered apps work. See Unpoly, HTMX, LiveWire, Hotwire, etc.
I'm personally using Livewire. I make server request in only 2 situations. 1) When going to different pages (I'd need that anyway with an SPA given I need "SSR") and 2) When I'd need to write or read data from the server, which with an SPA would mean I need an API call anyway. Every other interaction is done with Alpine, 100% client side.
People generally have a very bad estimate of just how engaged users are with their sites.
These people have lives and other shit to do besides spend all day in my web app. There’s a lot more accidental and exploratory clicks than you think and all of the background requests run even though the user is only on that page for half a second.
If you can do it in a single GET/POST in an SSR, you can do it in a single XHR call in an SPA. There's no reason it has to be split up in separate XHR calls.
> Practical SPA's have many more network roundtrips than the equivalent server-rendered web interface. Every AJAX request is an extra roundtrip, unless it can be handled in parallel with others in which case you're still dependent on the slowest request to complete.
This is largely solved with innovations like GraphQL (which you don't need a SPA to use). Pages that require multiple API calls can show their UIs progressively with appropriate loading indicators. For SPAs that have ~long sessions, it's arguably a good thing to have multiple API calls, because each can be cached individually: the fastest API call is the one you've already cached the response for. This is stuff we were doing at Mozilla in 2012, it's nothing new.
There's also nothing stopping you from making purpose-built endpoints that return all the information you need, too. Your proposed solution (SSR) is literally just that, but it returns HTML instead of structured data.
What they're meaning is that the total time is longer for the SPA if you don't go all out (and nobody does).
SPA:
1. get html from server (1 roundtrip)
2. get resources (js/jpg/movies/gifs/favicon/...) from server (1 roundtrip)
3. get ajax calls from server (1 roundtrip)
4. process answer from server + update page (not a roundtrip, but not zero)
Vs traditional:
1. get html from server (1 roundtrip)
2. get resources from server (1 roundtrip)
Also if there are sequential ajax calls required to build the page (like a list -> detail view), it goes up a lot without speed oriented (very bad code style) API design. For instance you need a separate "GetListOfUsersAndDetailsOfFirstUser()" (or more general "getEverythingForPageX" calls). You can't do "GetListOfUsers()" and "GetUserDetail()" separately.
So to match traditional webpage total load time you cannot do any ajax calls in your SPA until after the first user action. And even then, you only match traditional website performance, you don't exceed it.
Time until the first thing is on screen however, is faster in SPA's. So it's easy to present "a faster website" in management/client meetings, despite the SPA version actually being slower.
You can make SPAs faster than traditional websites ... but, for example, you cannot use javascript build tools. Since you need to do the first calls server-side and have javascript process them, and only send the result of the preprocessing, and the resulting dom to the client, after optimization and compression. After that you need to then do image inlining, style inlining, etc. I know react can do it, but does anyone other than facebook actually do that?
You eliminate #3 by having data preloaders inline it into the html response and move it into #1. But without SSR your rendering still starts after #2 instead of #1.
I think your point is still correct. SPA by default are a huge regression and you have to build and maintain sophisticated web infra to reclaim it.
Yes, but most of the time (getting the HTML and all the Javascript etc) is only at startup time. Let it take a second or two. From then on the user is in the application, and everything is much faster than it used to be with getting entire new pages from a backend every other click.
I wrote a post very similar to this comment that you might be interested in reading. I broke down what takes time to load, how fast the browser determines something has loaded, and how fast the user can perceive the page loading. I ended up having to take a high speed video and measure frame by frame the time it took to load for user perception.
The more something behaves like an actual application and the higher the number of user interactions per visit, the more sense the SPA approach makes IMO. This is because the initial load overhead of an SPA gets amortized over subsequent interactions.
Blog or corporate/news website on one end and an interactive game or video editor in the other, with a site like FB somewhere in the middle.
It’s not a hard rule and I can already think of counter-examples but this line of thinking is useful when architecting a new project.
> For instance you need a separate "GetListOfUsersAndDetailsOfFirstUser()" (or more general "getEverythingForPageX" calls). You can't do "GetListOfUsers()" and "GetUserDetail()" separately.
This is literally a core use case of GraphQL. But you probably also want to fan out to preload all of the other user data in one API call: that's the whole point of doing it on the client. If you are trimming the handful of kilobytes on the first request, you're paying the price for that on each subsequent load.
> I know react can do it, but does anyone other than facebook actually do that?
Yes. Every job I've worked at professionally since 2016 (when this became mainstream) has done this. I do it in my side hustle. It's really not that hard.
> 2. get resources (js/jpg/movies/gifs/favicon/...) from server (1 roundtrip)
> Yes. Every job I've worked at professionally since 2016 (when this became mainstream) has done this. I do it in my side hustle. It's really not that hard.
Can you link to a tutorial for this? I'd be most interested.
I think it's exciting to see frameworks like https://remix.run/ trying to temper the disadvantages of SPAs by relying on web standards and server-side rendering. It's pretty cool that this is a JavaScript framework that can work without any (client-side) JavaScript.
Honest question: why is remix the primary example for this now? Next.js has been doing this for years and is really amazing. It seems like a ton of people had no idea this was even a problem they had until remix came along, and now remix is the savior.
Am I missing something with remix? Is there anything really novel that it does/introduces?
I don't know. I'm not very familiar with Next.js (I'd heard about it but never really looked into it much). Your response inspired me to investigate, and I found an article by a Remix founder about the differences [1]. It's obviously biased, but maybe still interesting. It looks like there was an HN submission and a bit of discussion, too.
There's a bunch of other comparisons out there, too.
>It means things are faster, they work if your connection is flaky, they can do things like offline support. Those are good features.
Yet the reality seems to be the opposite. If my connection is flaky (which it often is) then SPA's seem to fail more often and it isn't obvious what is going wrong. They don't seem to be faster either, long pauses between pages is common.
Right. The typical SPA doesn't recover at all from network failures, and you end up poking random buttons hoping that it will reach some sort of consistent state. Then you find that doesn't help and you need to refresh anyway, which is a reboot for the whole 'app'. So much for saving page loads.
> When either an SPA or a multi-page site is done well most users can't tell which sort of site they're looking at.
And therein lies the problem. In the _vast_ majority of SPA sites I've been to, they are not "well done" by this definition. It is commonplace for things to break, like the back button as the quintessential example, because the developer(s) didn't spend the time to make sure things work correctly. With a non-SPA site, you generally have to go out of your way _to_ break those same things.
I like SPAs for some things (gmail being a good example), but they should not be the default implementation; there should be a _very_ strong argument before the SPA architecture is used.
The GP was not saying "bad software is bad"; they pointed out a lot of footguns that are introduced by going the SPA route. "More code -> more (opportunities for) bugs" is the truism at play here.
If one style of writing applications is far more prone to having “bad software”, consider that this is a valuable signal about the difficulty of using that style effectively. SPAs require you to take on ownership of more hard to debug challenges and that's an important factor to consider when selecting tools.
Not saying it can't be done, but I haven't seen many SPAs that handles well flaky connections. Most stall with no indication to the end user, endless spinners or just broken in some random way.
This ship has sailed. SPAs shortcomings are widely known and addressed by frameworks.
Whatever MPA alternative you bring will need to address other shortcomings. There will always be something quirky due to building applications in a technology designed for hypertext documents.
Many of those shortcomings were solved 15-20 years ago. Far too many SPAs are little more than old school request and response, GET and POSTs. I've literally had to drop into the dev tools console just to push through a page with 7 sets of radio buttons because Angular broke in some spectacular way, on a page that is and should be boring (prescription refill page).
I don't know why routing is the sole focus of the article. In all my SPAs it has been a set up once and forget about it deal. No one is constantly reinventing the wheel there, the churn is in other areas like reactivity and state management.
I think one of the unsolved problems of client-side interactivity on websites is how difficult it is to add it just a little bit of extra client-side functionality to a traditional server rendered website. For example, recently I had to deal with photo uploads on a Rails app, which works fine out of the box at first, until you want to show progress bars and uploaded previews etc. Then you add a couple of client-side Stimulus controllers, maybe a Turbo frame here and there and it works, but then you get to browser navigation and you're screwed, because none of this works if somebody were to submit the form, then navigate back. Now you have to implement lifecycle handling to account for navigation and once you're done, you've basically implemented a SPA, except it's broken into a mix of tightly-coupled Javascript, Ruby and ERB templates.
Progressive enhancement was largely solved before SPAs became a thing. Its quite easy to enhance the client side incrementally, but SPAs started out as useful for full on Web apps and now everybody uses them by default for some reason.
Exactly. I've been in the position various times where I'm embedding a JS app on a page to help the user do something highly interactive, usually creating/editing content. And then as it's expanding to integrate with other things on the site, I start to wish more of the site was in the JS-app side of things.
Sometimes I realize a SPA would have simply served the user better, and it doesn't necessarily take much to be in that predicament.
Too many people hate on SPAs by, presumably, just imagining static read-only content like blogs and news. Though I'm also a bit tired of "SPAs suck amirite, HN?"
Put the user first, consider the trade-offs that work towards that goal, and see what shakes out.
I think the fundamental issue with SPAs is that it's building on multiple levels of technology that fundamentally weren't designed to support being a single page application.
The browser <-> multiple pages paradigm is pretty much how the web evolved, so SPA's just end up being one giant hack to get everything working.
UWP/WPF/any other desktop app framework demonstrates how easy developing a 'single page application' can be without all the cruft you have to add to make a SPA work because it's actually a sort-of-massive-workaround.
In my opinion, UWP/WPF/aodaf makes it easy because it just doesn't implement the things a user is used to in a browser (bookmarkability, back button, ...).
If you ignore those things in your SPA, much of the "cruft" is negligible.
This is pretty in line with grandparent’s comment in that the browser was simply not made for this - aka SPAs are sort of a hack on the traditional page-based approach.
Many desktop apps have back buttons, and of course mobile apps always have them. They're also more reliable; there is no desktop/mobile equivalent of the "warning: need to re-POST this form" problem.
As for bookmarks, that's semi-right, but the web platform conflates the back button with bookmark-ability in ways that are quite confusing. If you want the back button to get the user out of your settings screen, then you must push a new URL onto the history stack using JavaScript, but then if the user bookmarks your site they'll bookmark the settings screen specifically even though that's very unlikely to be what they wanted. Non-web apps that have enough content to need something like bookmarks often implement a similar mechanism that's more predictable, albeit less general.
The OPs article is really a common lament amongst web developers - they're (ab)using a tool that was never meant for what they're using it for. It's a real and very serious problem in our industry.
The whole SPA vs multi-page dichotomy really emerges because the web doesn't have sufficiently rich, modular or low level APIs. If you look at non-web frameworks like iOS or Java then you get a huge number of APIs (usually classes) tackling the low levels of a problem, then more classes building on the lower levels to give you higher level solutions. Those classes can usually be customized in many different ways, so the amount of control a developer has is enormous. If you start with a high level helper class and find you can't customize it to meet your needs, you can reimplement it using the lower level building blocks.
The web isn't like this. Web APIs are an absolute pigs ear of very high level function calls and classes designed in more or less random ways to quickly hack together a solution to whatever need a browser maker had at the time. HTML5 supports peer-to-peer video streaming and augmented reality but not a virtualized table view. In many cases it's obvious that little or no thought went into the APIs, e.g. XMLHTTPRequest has nothing to do with XML, you can open a URL for a custom URL handler but not detect if that URL will work ahead of time, nor even if opening that URL worked once you try it, and so on. Instead of starting with low level APIs and composing them into high level APIs, browsers start with high level APIs and then introduce entirely orthogonal low level APIs decades later, or never.
These sorts of problems just don't occur when writing desktop apps with professionally designed frameworks - even in the rare cases that you hit some limit of the framework, you can (these days) just patch it directly and ship a fixed version with your app. If you hit a problem in the browser you're just SOL and have to hack around it.
Our industry needs to reinvigorate desktop apps. Attempting to convert browsers into Qt/Java/.NET competitors has:
a. Made HTML5 unimplementable, even by Microsoft. It's not so much a spec anymore as a guide to whatever Chrome happens to do on the day you read it (hopefully).
b. Created tons of security holes.
c. Yielded an atrocious and steadily decaying developer experience.
You could just use `history.back()` instead of pushing a new url... beyond this, you may need to listed to history changes so that your UI response... most spa frameworks have a router that supports this without much issue.
At least since HTML5 and CSS3 and ES6 (so many years already) these technologies are made for SPAs. There aren't many better UI frameworks, and WPF isn't that by a long shot.
> Even Microsoft itself now invests into React Native.
Microsoft invests in React Native as they want to bring applications built in React Native for iOS and Android to Windows.
The fact they are investing in React Native doesn't necessarily mean much more than the fact that they want developers to compile their React Native apps to Windows as well as the mobile platforms.
I mean, grid layout has existed in Tk (the one with the Motif look) since the earliest version I can quickly find documentation for, 8.2 from August 1999; Wikipedia tells me that 'table', apparently an early version of 'grid', was introduced in July 1993, that is to say around the time Windows NT (and with it Win32) was officially released. (And of course plain Win32 never got it, seemingly because Microsoft decided they weren’t in the business of selling toolkits despite USER being one.)
The hard idea from the programmer’s point of view is automatic layout, not grids; my impression was that Tk originated it (it certainly brought it to light), but now that I’m looking I’m not sure it wasn’t already in Motif to some extent, so the roots of the idea might go down into the primordial soup of Project Athena and similar places.
I wouldn't really say that - CSS3 was made in 1999 so was hardly made for SPAs.
Additionally HTML5 wasn't really 'made for' SPAs, it added features to HTML which could help support SPA's but it's main design decision was to be 100% backwards compatible with the older HTML spec. HTML5 is made for SPAs the same way that a stretched limo is made for commuting.
SPAs are old though. Their popularity is modern, but I was making SPAs in 2005 - and it most definitely was a concern of HTML5 and CSS3; it was a concern even before, during the DHTML days. You're right it wasn't the primary concern - that's backwards compatibility, after the XHTML blunder - but that doesn't mean HTML5/CSS3 wasn't made with SPAs in mind.
Well, the browser has certainly evolved past the point of SPAs being nothing but a hack. The browser has evolved into a heavily generalized application environment, as much as we may want to bemoan that. A good web client can surely demonstrate this.
It's certainly true that you don't get to lean on built-in features like history support, but that's why you can now drive history with Javascript. And all sorts of other things. And if you're smart, you're using a solution that handles these things for you.
Rich client development is always hard—on any platform—, and you always make concessions for the platform you're on. I certainly have to when I'm building iOS apps. But I see no reason for this to dissuade you if you can push a better UX to the user.
As tried-and-true as server-rendered architecture might be, there are all sorts of things it will never be able to do no matter how much of a hack you think web client development might be. Software is a hack. And at the end of the day, your users may remain unconvinced when you preach about what the browser was "meant to do" when they begin coveting a richer experience.
That's something that's often left by the wayside when we discuss these things. We talk about technical superiority and become prescriptive about what we think technology is for. While fun thought exercises as craftspeople, we too seldom talk about delivering value. If leaning into the historical nature of the browser supporting HTML pages over the wire helps you build a better experience, that's great. But that's not the only option you have today.
> Rich client development is always hard—on any platform—, and you always make concessions for the platform you're on. I certainly have to when I'm building iOS apps. But I see no reason for this to dissuade you if you can push a better UX to the user.
I think the difference is how much you as a developer have to "fight" the platform. Having to implement history management yourself very much qualifies as "fighting" to some extent in my eyes… continuing with native platforms as an example, that sort of thing just isn't necessary in most cases – like with iOS, 99% of your "history" management comes for free with some combination of UINavigationController and view controller presentation, assuming the use of UIKit.
As others have said, history management in a SPA tends to be done for you. But if you were to do it yourself, you would centralize your href-click handling in a single spot where you go history.pushState(path) and never bother with it again, it simply hooks into all <a>.
While bad SPAs do this badly just like bad iOS clients do things badly (like incessant spinners, zero caching, and unselectable text), it's a small concesión to make in the scheme of client-side development.
On the other hand, have you ever wrestled with CoreData on iOS? It's like using the worst ORM with the worst Active Record abstraction, easy to get wrong, yet that's the tool you're given. And you're choosing between that built-in solution or going off the rails with another solution with its own trade-offs, and both paths feel like you're wrestling with the platform.
It just comes with the space of client-development, you just tend to get used to wrestling with the platform you have the most experience with, and it's easy to forget that when you judge the concessions that must be made on other platforms.
The other thing people who hate SPAs need to think about is that rendering blogs/news/static sites isn't a problem that developers really work on anymore. Businesses will just pick from the huge pile of existing CMSs or hosted solutions because it's easier and cheaper. So if you're wondering why all these developers are building SPAs, it's because we're building custom applications, not blogs.
Setting limits for future features to keep the code base clean and manageable is something developers could be more vocal about.
Acquiescing to every demand product designers and management throw into the mix is what turns beautiful, easily-maintained codebases into nightmares.
What people are talking about here is writing web apps as a series of small, tightly-coupled spas who manage state within very specific parameters. And that's a great way to build software. Until someone comes in and asks you to draw in the state from one section into two other sections. You have the choice to say a) no and explain why, to b) create a clever "glue" that will be hard to maintain and difficult to explain, or to c) take the time to refactor the code into something more general and complex to allow for the feature on a more abstract level.
Guess which one almost always gets chosen.
The upshot is this: The solution to front-end complexity may not be a technical one, it may not require a new framework or library. It may be a shift in what we expect out of the web. We could always temper our expectations in order to keep our code clean.
I'm aware, but the discussion about the failures of spas always center around their failures in maintainable code. So if one's issues with SPAs are codebases becoming unmanageable, the solution won't be technical, it's going to be managerial.
A start would be devs communicating to product & design what the technical limits are to the current approach so that it informs their decisions.
> Put the user first, consider the trade-offs that work towards that goal, and see what shakes out.
In my experience, every single team I've been part of that was building an SPA was because they put the developer experience and desires first, even if in the mid/long term the dev experience ends up being worse as the project grows.
This is my biggest gripe with the direction of Rails.
I completely understand the historic reasons for wanting to keep JS to a minimum and stay within ERB/Ruby, but the JS component-driven UI pattern feels like the correct front end architecture for most commercial projects these days, especially now with better JS tools like Svelte, which feels like rails for the front end to me.
I started playing around with InertiaJS recently and I absolutely fell in love with it. I was surprised to see how niche it still is because it's pretty much what I've dreamed what writing JS with Rails could be for the last 5 years.
It does and it has lots of work-alikes on other platforms (though some are not nearly as good). For almost any app that is inherently useless when not connected to the server, its a pretty good fit.
The Basecamp guys created Hey which is a mail app not implemented as a SPA. It's probably the fastest mail app I've ever seen while delivering around 40kb of javascript.
If Hey is a toy app you then you must be working on some truly alien projects from the future or something.
While it might be fast, it feels you’re like using Rails app with turbo links. Page loads. Feels barely interactive.
Compare native Mail app and Hey. If anything should be a SPA, it’s an email app.
Also who cares how much kb of JavaScript your mail app uses? It’s completely wrong metric to optimize for an app someone uses every day, multiple times a day and likely has the app open all the time.
(Also haven’t heard people using Hey much since the launch)
SPAs dominate? Off the top of my head I can't think of a single SPA that I actually use regularly. I worked in a niche a few years back where they seemed common (configuration interfaces for embedded systems) but looking at my own habits and the sites that dominate in terms of web traffic, that's thankfully not something that has caught on generally.
I can sort of see the point if the "A" part of "SPA" actually applies to your product, but for the dominant players that doesn't seem to be the case.
I feel like most people that hate SPAs never have to deal with this type of thing, or even only have to work on the backend. They just don't get it. Of course, if you're using a SPA for a static website, you're also doing it wrong but that doesn't mean SPA itself is a bad thing.
I'm living proof that it is possible to simultaneously hate SPAs and understand why they are popular. I've built and worked on a number of them, I don't have a better alternative to recommend, and yet I still hate them because I think they are a clunky solution to the problem. We need something better, but I'm not smart enough to come up with what that should be.
I don't think I'm any more smart when it comes to this particular issue, but my thought is that the answer probably has something to do with more primitives that are suited for web applications being provided by web browsers, so it's both easier to build a web app without some huge gangly framework and so frameworks have more to build on top of and less manual footwork introducing room for error.
I agree. These people hating on SPAs are mostly not frontend developers. They're backend devs that think returning an HTML page is all you need to do. Modern websites are very complex.
If the only purpose of your site is to load some text or an image, go use a framework with SSG like NextJS. Then you get fast websites without having to pretend that writing stateful updates (e.g. jQuery) to the DOM is fun.
I've also been building these things for a number of decades now. That said, I thoroughly enjoy using the various, modern APIs and standards that have come together to enable single page apps. I'm all for first principles, but which stack are you talking about? The "backend" of the web has been about abstraction for a long time now. Building "these kind of features" has become easier and cheaper on any number of fronts.
Yeah can someone fix this please. I'm experiencing the same thing with a rails app I'm currently building.
In all seriousness it feels like there must be an elegant way to do more responsive modern functions while keeping the core server rendering concept. It's 2022 after all. But no I don't know a good solution either.
"I think one of the unsolved problems of client-side interactivity on websites is how difficult it is to add it just a little bit of extra client-side functionality to a traditional server rendered website. " I've started using django-unicorn lately and I'm hopeful that it may solve that problem for many use-cases.
> Then you add a couple of client-side Stimulus controllers, maybe a Turbo frame here and there and it works fine, but then you get to browser navigation and you're screwed, because none of this works out of the box if somebody were to submit the form, then navigate back. Now you have to implement lifecycle handling to account for navigation and once you're done, you've basically implemented a SPA, except it's broken into a mix of tightly-coupled Javascript, Ruby and ERB templates.
Where in this process did you need to start adding navigation via JS? Did that functionality really require JS, or was it implemented that way just because other parts are JS, and the trend was continued? Could you have used Stimulus controllers to manage functionality on the page itself, but when it came time to navigate to a new page, just done a basic browser redirect?
Saying that SPAs is a mistake doesn't mean that a strong dependence on JS or frameworks in a mistake. It just means that trying to make an entire site fit into a single page load introduces more costs than savings. You can still have pages controlled via React or Vue or Stimulus, but forgoing the router functionality.
In my own apps, I use Vue and VueX to control page behaviors, but each URL is its own Rails page. For example, I have a search page that uses Vue and VueX to asynchronously load, filter, and display search results. Clicking on a result is a basic browser redirect, taking you to the result's page, doing a full Rails page load, and again using Vue and VueX to manage any dynamic state (any static content is rendered via ERB files).
This has created a very clear and simple structure that allows each page to have any dynamic functionality it needs, but no complications from having to maintain navigation or browser history via JS. The browser already does that natively, and I get to spend my time working only on actual features--not recreating the browser's built-in functionality, or debugging my own take on it.
> Where in this process did you need to start adding navigation via JS? Did that functionality really require JS, or was it implemented that way just because other parts are JS, and the trend was continued? Could you have used Stimulus controllers to manage functionality on the page itself, but when it came time to navigate to a new page, just done a basic browser redirect?
In my specific example, the navigation itself didn't happen through JS, but you need to hook into navigation APIs to handle backing into a partially-filled form after submitting to rebuild the UI to reflect the state before the user hit submit. To make things even more fun, Turbo/Stimulus (sometimes?) breaks bfcache in Safari & Firefox so they behave different from Chrome.
Personally, I detest the vast majority of SPAs, especially the ones from Google, Facebook and Linkedin. Not even sure what they do to make them so horribly slow to use.
> In my specific example, the navigation itself didn't happen through JS, but you need to hook into navigation APIs to handle backing into a partially-filled form after submitting to rebuild the UI to reflect the state before the user hit submit.
I'm not sure I understand. The "conventional" solution to this is not to do it -- the reason browsers don't re-fill submitted forms is to prevent duplication. Give users the ability to post-backclick-repost and your DB will rapidly fill up with duplicate rows.
If you do need to back-navigate to a page that has been submitted (say, to edit the submission w/o requiring an edit-specific URL), you have the server re-render the form with the filled values.
If that isn't sufficient, you can always push your form state on the history stack using the history API, so even though this is a bad idea (IMO), you can still do it pretty easily without needing to resort to re-writing nav.
I hate to say it, but I feel like a lot of this stuff had server-side solutions that worked fine circa 2010, but have been almost completely forgotten.
But browsers absolutely do keep the form filled unless your HTML or JS says otherwise. Pressing back after submitting on a vanilla HTML form keeps all values in place. As it should.
Yes, even after POST. Why not try it, right here, on Hacker News. The comment form uses POST. I post this comment and go back and my text is still in the input box.
In fact, browsers even have re-submit built-in: Just press F5 on a page that was retrieved using POST. That’s why so many sites use a redirect after POST.
Redirect after POST was a pattern created to bypass the warnings that (most) browsers emit on back-navigating after form submit, while still preventing double-submit (by redirecting to the GET):
It sounds like this warn-on-double-submit behavior is convention and not required, so maybe it has become more common for browsers to stop doing it.
> Why not try it, right here, on Hacker News.
OK, I did. HN does a 302 redirect on comment POST to the comment GET. When you back nav, you are seeing the response to a GET request to /reply?id=X
(In other words, the form is re-rendered.)
Edit: oddly, however, submitting on form rendered by GET /reply?id=X ends up creating a new comment, so that's just weird server logic. It might explain why you occasionally see duplicate comments on HN.
Edit 2: on backnav to a comment edit, the redirect is to GET /edit?id=X, which is a re-render of the edit form, the process I described in my OP. Not sure what's going on with the back-after-post...maybe a bug?
> When you back nav, you are seeing the response to a GET request to /reply?id=BLAH
With the text you previously entered, which the browser restored. If it did not for you, you have non-standard settings like disabled caching. I can assure you that it behaves the same on Firefox for Linux, Windows, macOS and Safari on iOS: go back, form content is restored.
In fact, the entire internet is full of people trying to get rid of this behavior.
Thanks for sharing! That helps me understand things better.
> you need to hook into navigation APIs to handle backing into a partially-filled form after submitting to rebuild the UI to reflect the state before the user hit submit
Was this a hard requirement for the feature or just a nice-to-have? I understand why you'd want that behavior ideally, but it also seems the sort of thing that adds much additional complexity for a problem that isn't (at least in my eyes) severe. Having people re-fill things isn't too much of an ask, I think, and if it's something crucial, it might be better served with a preview page that allows additional changes. Or a "Make changes" link that redirects to a page that can load from the submitted state. There are easier ways to handle it than manually controlling navigation.
But yeah. If the behavior is a hard requirement from the business/product side of things, then using an SPA is no longer an architectural decision made for technological purposes, but is a feature requirement from beyond. And that's a whole different matter.
A little bit of both probably. Supporting it when navigating back is probably a nice-to-have, but persisting file upload previews etc through a form submit that might fail due to some unrelated ActiveRecord validation (maybe a blank field) is a must.
It was probably never too beautiful of a technique, but JSF does solve this problem splendidly. You basically have a view-flow besides the usual ones and it can act in many of the ways SPAs can.
It's an implementation of Elm (imagine React if you're a JS dev), but all logic is executed on the server. State is passed back to the server whenever you choose to listen to an event. The view is then re-rendered and virtual dom diffed on the client. Non-interactive pages are just views. If you want them to be interactive, you add a Message an update function.
I used it on a client project and it was pretty delightful.
It probably isn't documented well enough yet to make total sense, but I think it's a step in the right direction.
At the risk of a pile on, this is what jQuery was brilliant for. And frankly the native browser APIs have caught up enough that scenarios like what you want to achieve are simple to implement with just a script tag and a sprinkling of JS. I don't know what "Stimulus controllers" or "Turbo frames" are but they don't sound necessary.
Yes. Nobody should have to make a SPA to accomplish what JQuery did with sprinklings of JavaScript. JQuery was maligned because many people used it to make all XHR requests all over the place resulting in non-deterministic async behavior. JQuery was made to make adding small bits of JS easy and work in almost all browsers.
Bringing in React and turning that non-deterministic events firing all over the place greatly improved that situation, but this has become if someone needs JS, bring in SPA framework. This is in spite of the browser world getting much better. So just as JQuery was used to make SPAs to bad effect, SPA frameworks have been used to do minimal JS actions in a bad way.
The problem is the mixing and matching of state management. In a traditional web app all of the state is in the back end, in a SPA all of the state is in the front end. When we share state in between the two it's often messy.
To me the answer feels like it should be "traditional web app for most things, components-as-first-intended for some things". The simplest React example is just one component that abstracts presentation and logic. The only state is its own. It does not handle an entire web app as a SPA, no Redux, prop drilling, it's just the idea of a reusable component as an HTML tag. Same with VueJS and all. If we restrict ourselves to that we're in the good path.
If there was already an HTML tag for your own specific problem, wouldn't you just use it in your traditional server-rendered app? A `<photo-upload url="/photos">` tag that does exactly what you want. Or a `<wizard pages=5 logic="com.domain">` tag. We should create just those components, either in React/VueJS/Whatever or in vanilla JS Web Components, and live with the rest as we used to.
We're basically saying that some parts of our apps are too difficult to mix and match state management, and we should offload all of the state to one of the two sides. In some rare apps indeed all of the state should be on the front end, but the use cases for that are just not as big as they're made out to be.
Exactly, the best approach seems to be where the app is composed of traditional pages with server navigation between them, but each page is implemented as an SPA.
This approach eliminates the need for a client-side router, keeps any centralized page state small, and improves the SEO and bookmarkability of the app.
I have implemented this architecture in several projects, and it’s effective
I'd love to watch people attempt to implement some of the stuff I pulled off with that back in the day now. Occasionally, very complex data driven page flows are required and you could nail that entire problem domain trivially with them.
There is a very large and well known company with a front-facing product used by millions of people which took 5 years pissing around with three different front end technologies to reimplement what we did in 3 months with that and it was slower and heavier and basically reimplemented web forms in python in the end.
It broke the the www intentionally because it sucked. And it still does.
Speaking as someone who is still maintaining a web forms app, that sounds like a people problem, not a tech problem. You can implement literally any workflow if you understand continuations, and it will be simpler and more lightweight than web forms.
If you don't understand continuations, then you'll struggle and invent all sorts of poor state management patterns. Web forms simplifies some of this because the continuation is captured in viewstate, but it captures too much state (there are other warts too of course).
What’s wrong with razor pages? They’re fantastically simple and productive.
And WebForms wasn't a misunderstanding. It was a deliberate and brilliant design that brought WinForm developers and their experience to the web and allowed them to build complex web apps two decades ago that still work to this day.
It's contextual. For me, WebForms fell down because it let the average web developer impose too many "costs" on internet-facing projects. A WinForms developer is a very specific kind of developer with a high focus on development of internal, or line-of-business (LOB) apps. WebForms also excelled at this, but brought more reach as people moved away from a preference for desktop apps.
The height of WebForms coincided with an embrace of web standards and accessibility which flows into the Web 2.0 era. You had to jump through a lot of hoops to achieve what was needed WebForms to get it to behave in a web-friendly way. The underlying .NET framework and base of ASP.NET (HttpHandler and HttpModule) was outstanding though.
(I still build/maintain WinForms, ASP.NET, and WPF apps.)
I just remember massive state being injected into forms, and it wasn't pretty, was massive at times and just unpleasant regarding WebForms. It was fine for internal apps with a network connection, the experience was horrible if you were a dialup user. Especially in components that a change triggered effectively a full server round trip to update the whole page.
It got better by ASP.Net 3, but MVC/Razor was much, much better imo.
Yeah, and as soon as soon as they tried doing that, they found out it was all superficial and the fact that there was HTTP calls inbetween meant everything started falling over and they saw weird behaviour. And instead of learning how to work with state management in general, they learnt how to work with WebForm's state management instead.
I'm working on one of those webforms projects right now, a legacy project that they need some fairly trivial tweaks to and it's an absolute nightmare of bad code. And I've done this before, it's become an accidental skill that I can still fix these awful messes.
One of the (many) reasons why webforms and the new Razor Pages are bad is because the code gets split up according to the UI instead of by function. So it gets scattered all over the place and is incredibly hard to do maintenance work on it.
The page-centric code layout that Webforms/Razor Pages/all the old PHP/Perl/etc. encourage is also extremely conducive in encouraging copy-and-pasta code for programmers trying to get stuff done asap.
So not only is it a nightmare to pick apart the code, you can often fine 2 or 3 copies of the same code that you only discover when one page is working as expected, but the same functionality is used somewhere else but someone just copied the page/control instead of actually splitting the page up into controls and re-using functionality.
I've seen this happen over and over and over. A single developer can avoid these pitfalls, a team cannot.
WebForms was a complete disaster, made solely to allow Microsoft salesmen to quickly slap together a demo.
The problems started when you wanted to put together a REAL application. Because the entire paradigm of a web application programmed like a desktop application was flawed it resulted into an endless stream of headaches and workarounds, with bloated, complex and slow applications as a result.
In that sense ASP.NET MVC was a breath of fresh air.
Yeah, as much pain as it has caused me in the past I think the crucial realization Microsoft made with Webforms and maybe Blazor is that the stateless model of web is fairly crap if you are doing anything beyond displaying static text.
I don't understand why they choose an example -- a simple user dashboard -- that could be easily implemented as a traditional MPA. It seems like Remix is mainly for the "I learned to program with SPAs" audience.
Remix's _messaging_ may be aimed at the "SPA generation", but its capabilities are light years beyond a Rails w/ Turbolinks or whatever your concept of a traditional MPA might use. I'd encourage you to look a little closer.
My kneejerk rxn was "clearly you don't get it" -- but your critique and (mis)perception speak to their marketing / messaging, which, well, yeah, this is the world we live in. SPA has become the default, and Remix is pushing back on that, hard, and I'm stoked.
I've been doing webdev for a living since 1998, and Remix (like https://every-layout.dev 's "axiomatic css" - but I digress) is doing something profoundly powerful by leveraging the amazing power of the web platform on its terms, using the native APIs, and doing much, much more by being simpler and doing less. It's so refreshing.
I disagree with that. There's no real reason for each page to be a separate SPA, most likely only a small part of that page will be interactive, and most of it will be cacheable. Extracting only the interactive parts of a page into components is what I'm talking about. In some cases that'll be all of the page, but there are very few apps that meet that criteria.
>app is composed of traditional pages with server navigation between them, but each page is implemented as an SPA.
I agree with this because you affirm my bias.
Also because I've also had success implementing this structure. This makes sure that state of each page doesn't leak everywhere, which simplifies the state management.
It also loads faster than a big SPA because you don't have to worry about bundle splitting!
I've had this thought experiment before, but where I get stuck is - what happens when you want to share state between the pages? URL params, storing to indexedDB, sticking them in global variables - all viable solutions but all with their own issues.
I'd be very interested to hear how you solve this, or if it's less of an issue than people might think.
Agree, we just add React components here and there to server-side rendered HTML and it works great for us. The issue is most companies want separate teams for front-end and back-end and each team wants clear separation of boundaries and responsibilities between them.
The old mythical man month strikes back. Best devops is no ops. Best team is no team.
Of course eventually you need more people and I don't love anyone touch my code, but I'd rather have someone incompetent do entire stack that at least I can review than spend weeks upon weeks communicating basic assumptions.
Yes this is exactly right. What I’ve done in the past is use Django to return HTML with JSX mixed in, and have a super lightweight SPA frontend that just hydrates the react components on each load. You can also use form state to communicate back and forth with the server, where sending a response doesn’t refresh the entire page, just a react render diff.
With this you get the best of both worlds where your backend can do the heavy lifting where everything it needs to decide on the view is all in one place, and your frontend just comprises of really really generic JS components.
I have a library I’ve been playing around with for 2 years now, I should package it up
HTMX goes about it in the wrong way, in my opinion. HTML code should not carry state and logic. The moment you need something a bit more complicated than the common examples you’re in HTML-attribute soup trying to use a real programming language.
It’s better to just write that as a component in React or Svelte or whatever. It’s more testable, easier to understand, can carry state and logic just fine. It does mean you have to communicate in JSON for a small part of your app, but that is reserved to a few well known endpoints and components like a complex form.
The penalty is loading React or some other lib just for that, but if used in this way the extra dependency isn’t really a big deal as it’s not your whole app. Just use React as a library for components the browser doesn’t already provide.
And by components here I should really clarify as “affordances”. A “primary” button isn’t an affordance, the browser already gives you a button element and CSS classes (or HTML attribute) for that. It doesn’t give you a “multi page wizard with immediate validation” affordance, however, so that’s a good candidate for a component.
> perhaps that is wrong, but the web was pretty successful overall
Since HATEOAS is rarely implemented fully in practice, it would be simplistic to point to it as the reason the web is successful. The web allows an incredible variety of different architectures —- Some more closely aligned to HATEOAS and some less so. Perhaps this is the reason for the success of the web?
HATEOAS was and is implemented widely and effortlessly in hypermedia, since Fielding was describing the existing web architecture. It has failed fairly dramatically in JSON APIs, because JSON is not a native hypertext. Early API designers tried to shoehorn HATEOAS concepts into their APIs, which was somewhat plausible when APIs were XML, which, when squinted at, looked sort of like a hypermedia. Once we kicked over to JSON (and back to RPC as a network architecture) it became silly.
I’m not arguing against that, just that huge chunks of the web break the HATEOAS abstraction, as it were, and yet, as you pointed out, “the web is pretty successful overall”. It’s not super clear to me that this is because of hypermedia or not in the sense that correlation is not causality.
The Web went all JavaScript instead of HTML sometime between 2000(2005?) and 2010(2015?). But by 2000 it was already wildly successful, so apparently it must have been HTML -- "hypermedia" -- that was the key to success, not JavaScript / JSON.
HATEOAS is great, but that's not my issue with HTMX. It's coding interactivity via HTML attributes that I have an issue with. It just never works. It overcomplicates things and there's an actual programming language right there in JavaScript to solve this.
When it goes past the simplest examples it gets ugly real quick. Here's an example of client-side validation via HTMX: https://htmx.org/docs/#validation. That can't be the future.
yes, if you want to do things client side, hypermedia isn't going to be a great solution for you
htmx integrates with the existing HTML 5 validation API because, well, it's there, and that's what normal HTML does. I don't care much for the API, but that's the standard so we follow it. You can, of course, do client side validation however you'd like and integrate it with htmx using events, since events are the proper glue to tie things together in the DOM. I built a scripting language to help with this, and other stuff as well, called hyperscript: https://hyperscript.org
the "htmx" way for validation is the HTML/web 1.0/HATEOAS way: submit the form to the client side, validate and re-render. With htmx that can be inline, rather than a big clunky refresh-the-page action, which is why I say htmx extends and completes HTML as a hypermedia.
So long as you aren't willing to think about things in hypermedia terms, and htmx as an extension/completion of HTML as a hypermedia, you are going to miss the point. That's not to say that the hypermedia approach is always right, it isn't, but with htmx a lot more of the web application problem space is addressable with that approach.
And, as an aside, HATEOAS isn't great, outside the context of a hypermedia.
> The problem is the mixing and matching of state management.
This. But IMHO the right solution is to make the back-end stateless and manage all client state on the client. Each request authenticates itself, and (if you're ReSTful about it) the back-end is simply a database connector/augmenter.
In this paradigm, the SPA is basically a desktop app that retrieves data from a server, built to run within a framework, which happens to be a web browser.
In case you're jumping to conclusions, know that I'm a late-comer to the SPA party, having resisted from its inception until about a year ago, for all the obvious reasons, including those bemoaned by the OP.
Why did I relent? SPA frameworks like React now handle pretty much all the heavy lifting for you. OP, you should check out React Router, which can render this post's examples irrelevant. I'm surprised that in 2022, someone writing to the web UI layer would bother to create code to manage the address bar when there are a thousand ways to not have to.
A lot of the arguments against SPAs in TFA center on broken U/X. This is only incidentally related to SPA architecture, and has much more to do with the current SPA ecosystem. IMO it’s more just a lack of standardization and the broken U/X is just an emergent property of the vacuum. MPAs are great because the technology is old and navigation between pages has first class support in all browsers, not because of some intrinsic advantage of the way application state is managed.
>The problem is the mixing and matching of state management
Routing (history management) is also a big problem (I'm assuming you weren't including that in your definition of "state" here).
Some would say, "but React, etc. have solved the routing issues", and that's perhaps the case. But what's difficult is wiring these routers up outside of a full React app. That is, if you truly want routing "solved", you generally have to go all React (or whatever) or just let the browser handle it in the traditional request/response sense. Sprinkling in just a bit of dynamic interaction wherein you want the history managed is purgatory.
And, on mobile, things get even more interesting. Consider the simple case of popping a modal (especially a slide-out). Many users will hit "back" on a mobile device, which they would reasonably expect to simply close the modal. But, if your app/page doesn't intercede to manage the history, the previous page is loaded instead.
Routing (along with hash path and query string) IMO is definitely a part of state. Different route lead to different content, thus different state.
The problem is which source of truth we want to use. I find that I'll need to refer to routes as the source of truth to process information, meaning "almost" every react action become route-manipulation action and it's state is just derived from the route, which is very similar with what MPA already do.
Yeah, I think history is definitely a form of state, but didn't seem like the state the parent to my comment had in mind.
And, I do think they're distinct problems (but related) when you're just adding a bit of interactivity to a page or two, as this sub-thread is discussing.
In a full SPA, it's largely coupled and more inline with what you say: let the route drive the action. But, sometimes you have an MPA with a dynamic stateful feature embedded at a particular URL, but which may or may not need a sub-route of its own. In those cases, weird stuff can happen if the history is not managed correctly.
Inertia [1] is an interesting project in this space that might solve some of your problems. I haven't used it, but I believe the general goal is to make it easy to "plug in" client-side frameworks (React, Vue, Svelte) into server-side views.
Progress bars and previews are simply a matter of attaching an events to the http request and file input respectively.
When someone submits a form and navigates back, don't you just re-display the empty form?
If it needs to be a form bounded with the uploaded file (maybe as part of a wizard), then why not ask the server for it instead of storing client side state?
Maybe using Javascript without a SPA looks hard because you're trying to avoid storing state on the server as well as bypass native browser features.
This is what jQuery solved. It made it ridiculously easy (compared to not using jQuery at that time) to add a little sprinkle of progressively enhanced JavaScript to a page. It still works today (and you don't even need jQuery now, as browser standards have mostly caught up), but everyone seems to have forgotten how to do it!
I feel like react-rails (https://github.com/reactjs/react-rails) is basically perfect for this. You just make the photo upload its own react component and render it as normal from your rails view. You basically encapsulate the small bit of complex state into a component and it doesn't infect the rest of your app with SPA.
I think the best solution for this is just doing fullstack dev with SSR + partial hydration.
The issue is that, so far, we haven't figured out how to have a good fullstack DX.
Remix is probably one of the best attempts so far, but it still leans heavily towards being a very sophisticated renderer for the front end. The proof is you probably would not use Remix to create a 100% backend project with no front end. Same with SvelteKit or Next.
Until fullstack frameworks get more serious about the backend, we will be in this weird limbo.
In my current project I use Fastify as my main backend framework, and then use Svelte for SSR + hydration. I loose a lot of the frontend sophistication that SvelteKit brings to the table, but OTOH I have an amazing backend framework and total control and flexibility.
I don't disagree with most points here, but I did chuckle at this one:
> YouTube is a great example. Being able to keep a video playing while you explore other videos is fantastic.
I hate that (mis)feature. When I click something else, my attention is on the new thing. Having to go find the little still-playing video window to close it is a hassle.
As a counter: I love it. It allows me to add videos to a queue without having to build a playlist beforehand. And halfway through the queue, I can add more without having to navigate away.
And if you don't, you're probably going to open in a new tab. Too many webapps are designed with complete disregards to the fact that browsers have tabs.
YouTube is awfully slow - back when I used it I dreaded accidentally mis-clicking on something because it would initiate 3 seconds of stuttering and shit moving around everywhere while their terrible SPA (poorly) reloaded the page.
Nowadays I use Invidious which is a proxy that renders good old server-side-rendered pages and it manages to be faster despite being a proxy.
SPAs were a good solution when page load/rendering times were longer. However with todays internet speeds, edge caching, HTTP3/Quic as well as todays chips and browser implementations page reloads are barely noticeable so you get all the goodness the browser gives you out of the box creating MPAs.
There are still some examples were SPAs are useful such as replacing complex desktop applications but 95% of web apps don't need it.
It was a big day for me when I realized the CSS perf tool had been removed from my browser because the clock resolution was no longer sufficient to make it work. Everything was <1ms.
Bloom filters for CSS were a huge deal, and the CSS load and apply times are one of several things SPAs were trying to amortize.
At this point most articles like this or tools addressing the problem discussed here replace one local optima with another, I'm holding out for something radically different, maybe something like makepad or bevy like ECS with good support for UI.
SPAs were not a mistake, SPAs were an attempt to make the front end work like front ends have ALWAYS worked.
Back before the web people made C++ and VB user interfaces that interact directly with the data source. SPAs are an attempt to make the web closer to that well known paradigm. POST REDIRECT REFRESH cycle was an insane programming paradigm that wasn't found in any previous edition of programming.
Most SPAs don't interact directly with the datasource, but interact with a middle service. Also many older architecture do draw entire "pages" on the screen at once, rather than manipulating only individual components. (at least not on every interaction)
Clearly, we can't have an SPA front end performing an SQL query directly. The HTTP/GQL/REST services are the SPA equivalent of that data source (they are still a data source). The C++ and VB apps written back in the day were single user programs so yes that's a difference but not by much architecturally.
"Pages" are similarly in SPAs too. We usually have a router that helps the app decide what main "page" is on the screen.
> POST REDIRECT REFRESH cycle was an insane programming paradigm that wasn't found in any previous edition of programming
Actually I'm old enough to remember old mainframe CICS programming that was a lot closer to the form-based HTML server-side programming of old. When "the web" came out in the mid-90's, it was actually a "blast from the past".
Not so. IBM mainframe systems (3270 terminal based) that ran most of commerce for a long time are basically the same paradigm as a web browser. Send a form to the smart terminal (GET), wait for the response with the field values (POST), send another form.
In fact there are adapters that literally turn these applications into websites by translating the forms into HTML. Commonly seen when you need to do something like change the beneficiaries on your health insurance.
“Client-server” paradigm came out in the 90s when PCs became powerful enough to run a “thick client” application, and was considered revolutionary.
Of course yes, GUIs go all the way back to Sketchpad in 1963. The PARC work was more about putting files on a server than putting the application logic on a server. X11 put all of the application on a server (or as X11 calls it, a client…). Based on my bookshelf, the “client-server” paradigm of putting the UI part and a substantial portion of the related logic on the client, but with most of the business logic on the server, reached mainstream adoption in the 90s.
>Back before the web people made C++ and VB user interfaces that interact directly with the data source. SPAs are an attempt to make the web closer to that well known paradigm.
I wish more web developers were aware of just how damn good UI tooling was for native systems. Stuff like WinForms/WPF/Visual Studio and Cocoa/UIkit/Xcode on OSX. Perhaps we could get away from the insanity of rebuilding a select component for every new project.
>SPA's suck! Here's my ad-hoc replacement architecture that you should totally just use!
Really though, this is a dead horse. SPAs are just a tradeoff like everything else in engineering. We trade the simplicity of a monolith for the ability to parcel out work efficiently between large disparate teams of frontend/backend engineers.
I think people have forgotten how easy it is to set up a traditional server-rendered site with Spring Boot or Django or whatever framework. These days I see people adding a SPA as the default starting approach, which adds an entirely separate additional tech stack & deployable to the mix when it's not usually clear why.
It should be obvious why SPA/PWA frameworks were developed by the likes of facebook and Google: Offloading their content rendering to the client.
Instead of Google/Facebook CPU cycles being spent on rendering their content, it's now the client devices, while the Google/Facebook infrastructure is "just" serving the data.
First lead dev offered me advice for the rest of my career.
Most of what you are going to see people argue about are cyclical fads. A pendulum. We try A and it doesn’t work. So we try !A. And when people forget why we stopped doing A someone tries it again over and over.
How are things different this time should be your second question. Your first question is what is the middle ground? Boolean logic falls on its face in the real world. If 1 is bad that doesn’t mean 200 is better. If 200 was bad the solution is not 1. The best answer is probably three.
I'm sure the data that FB and Google are delivering is far more expensive than rendering HTML would be. I am skeptical that server side rendering was a big enough bottleneck for them for that to be their primary motivation. I'm also skeptical that SPA frameworks would have been widely adopted by developers all over the world if they were just some conspiratorial plot by tech companies to save on CPU cycles.
This is completely inaccurate. Every millisecond of load time affects conversion so much that they would trade huge amounts of CPU to speed up a page load.
The problem with SPAs is that they have been misused and overused, at least in Italy. Tons of projects built with SPA in which we have to re-implement basic browser features like the back button, just because project manager has no idea of how this technology works. I've worked on at least 4 projects which could be written in Nextjs in half of the time
I was going to say.... ironically NextJS is just an additional layer for creating static-rendered sites over an SPA framework. We have come full circle.
This is way oversimplified. You can build a completely static website with NextJS. What I love about it is that you can do everything : SSR by default, SSG as an option, and awesome features like ISR etc.
However, the way NextJS implements SSR is really weird right now : if you use their <Link/> component, the pages props are actually fetched with an XHR request, then the page is rendered. I don't know why. It does feel like an SPA in the end.
Next.js recommends delivering static assets by default and is geared towards that.
> We recommend using Static Generation (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
If you need dynamic content, they recommend server-side rendering, and lastly client-side rendering only if the page is unaffected by SEO and requires a lot of in-page updates.
I hate SPAs. I would never do another SPA again if it were up to me. It just adds too much mental context switching and overhead. I can develop fully server-side apps that are lighter, run faster, and at least 20% less development effort (I actually compared that for the same task: https://medium.com/@mustwin/is-react-fast-enough-bca6bef89a6). So why would I ever do an SPA again if it were up to me? I would use https://github.com/jfyne/live which is inspired by Phoenix LiveViews. This is my professional opinion, having many years of experience in both kinds of web apps.
"I am indeed (rude). You can have 100 years of experience, it doesn't make you more credible at thinking that engineers who makes choices that are different than yours as incompetent, by deeming them as mistakes. I mean, if a company wants to choose a tech stack, there are so many things to consider for that choice. Your article is just generic ideas about the fact that SPA are mistakes and SSR apps are better. You're not taking into consideration any of each industry's and company's means, priorities, specificities.
In fact, your 100 years of experience are what's hindering your ability to see how useful SPAs are. And you're resorting to that same old narrative of "it was better before". It's also the niche you decided to position yourself in to sell courses and stuff. Which makes your ideas even more suspicious as they can't be unbiased. Like in politics, they'll dogmatically say that their opponents are wrong, just because they have a "market" to preserve.
So yes, I'm rude, as much as you're inconsiderate to the vast complexity of software engineering, just to preserve your niche market.
And as you say in your website: "Hate the complexity of modern front‑end web development? I send out a short email each weekday on how to build a simpler, more resilient web. Join 13k+ others."
"
wow you must be incredibly smart and discerning, being able to confidently make assumptions and judgments about people's lives and background based on one or two HN comments, and then dismiss them as an ignorant and incapable fool.
I kind of have to agree here. There are very few times I've ever worked on a SPA and felt like throwing it all away and using the alternative.
The last time I thought about this, which admittedly was many years ago, the alternatives that I knew about were: server-side frameworks like ASP.NET MVC Razor, PHP, RoR templates, Node.js EJS, Jade (now Pug), and static HTML.
Nowadays, you can create extremely elegant and performant SPAs with tools like Next.js and Remix, so I really couldn't agree less to the OP.
You're being very rude and dismissive. I may well have more SPA experience than you do. Don't make assumptions about people and then use that to dismiss something they say - we're better than that here on HN.
I am indeed (rude). You can have 100 years of experience, it doesn't make you more credible at thinking that engineers who makes choices that are different than yours as incompetent, by deeming them as mistakes. I mean, if a company wants to choose a tech stack, there are so many things to consider for that choice. Your article is just generic ideas about the fact that SPA are mistakes and SSR apps are better. You're not taking into consideration any of each industry's and company's means, priorities, specificities.
In fact, your 100 years of experience are what's hindering your ability to see how useful SPAs are. And you're resorting to that same old narrative of "it was better before". It's also the niche you decided to position yourself in to sell courses and stuff. Which makes your ideas even more suspicious as they can't be unbiased. Like in politics, they'll dogmatically say that their opponents are wrong, just because they have a "market" to preserve.
So yes, I'm rude, as much as you're inconsiderate to the vast complexity of software engineering, just to preserve your niche market.
And as you say in your website: "Hate the complexity of modern front‑end web development? I send out a short email each weekday on how to build a simpler, more resilient web. Join 13k+ others."
Template based UI programming is like going back to the year 2007. Your views should be reactive. Elm, Flutter, React, Et Cetera understand this. Having a function that takes data and returns a view is much better.
Isn't a template essentially a "function" that takes data and returns a view? I don't have much experience with UI programming but I don't really see the conceptual difference.
No, the template is usually a static file that is read into memory and served over HTTP or locally if it's a JS only site. But before being served, it's being picked at via imperative operations. And if it's really bad, then when a user presses a button it triggers some event that actually statefully changes the view itself.
I don't see template operations as imperative, rather I see them as declarative: you write the HTML and declare lists/placeholders inside the HTML. You also don't generally do much with the data except insert it into HTML; the rest of the backend is responsible for fetching/etc the data.
Yeah, but the function itself doesn't return that view. The framework you're working in essentially composes the view based on the associated code and template. This is fundamentally different from just returning the view itself in the function. These are all declarative approaches, but leaving your view in code is a lot better.
Every template-based framework I've seen has involved a lot of magic, syntax and DSL. That's not to say that JSX isn't a DSL as well but you can return regular React.Elements just as easily. You shouldn't need to remember a unique expression system just to be able to conditionally render. The premise that you're just not doing much with the data except dumping it into HTML is less true the more complex your site becomes. Think of the loads of conditions and evaluations that happen when you go to your Facebook wall or friends list.
It's okay because despite JSX being a DSL you don't have to use it even though everyone does?
Your line of thought is a large part of why I prefer ClojureScript to Javascript for SPAs. With reagent your components just work like the language, with nothing particularly special to remember.
Surprisingly enough, I'm a clojure developer and I really do like the hiccup syntax in Reagent. I haven't had the courage to use it in anything professional yet though.
EDIT: To answer your question, I actually think that JSX is a downside to React. I get that it was needed to convince the web crowd to adopt the library, but we need to stop pretending like we're writing HTML. We're not. The framework will take our JSX and do whatever it wants to it. I wish we just treated the DOM like a compilation target. That's actually what I like about Flutter - it treats the view as a render target and not as a document that you write to. Flutter web actually uses the canvas API to draw actually. And because of this, you can define your View model in a much more sane way (while still being fast). E.g. Flutter's Layouts make much more sense than HTML and flexbox/grid. They managed to separate the layout from elements. https://docs.flutter.dev/development/ui/layout
Amrita for ruby. That was one of my favorite template frameworks. It parsed your HTML document as HTML and used element attributes to mark which elements corresponded to which keys in your data model. Incredibly simple and unreasonably powerful.
These days.. I just an Amrita like engine I've built myself and I just use <template> elements directly in the document. It only takes 40 lines of code to match child elements to data model items and fill in the template. Your browser already has a powerful parser and literal templates built in.. why invent a whole new language just for HTML templates?
I've never been able to fully apprehend the reasoning for JSX.
That is a usual way this is done but not what it means. In fact, HTML recently got a `<template>` tag which is definitely not a separate file or served separately. Vue.js also refers to the HTML-ish portion of a component as "template".
We can debate syntax but a Go or Python based template language is not conceptually any different to JSX based syntax. Both are essentially functions that take some input data and return the data formatted in HTML.
Sure, if you implement a trivial single-task app (one tiny feature of a Calendar app) with an unfriendly UI, you don't need an SPA. That doesn't prove anything.
It doesn't "prove" anything, but it's a data point, and quite a lot more thorough than your dismissive comment. If you want to make a well supported counter-argument, please be my guest. It's easy to criticize, it's harder to make a thoughtful argument.
Anyone can choose the tech stack, patterns, or whatever that he likes. But developers enjoy being absolutists, and deem something they don't like as a MISTAKE, or would tell you that the way they think is the ONLY way to think.
You do your app the way you want depending on the Ux you want to provide, on the tech you enjoy implementing, on the patterns you like to follow.
> Anyone can choose the tech stack, patterns, or whatever that he likes
Maybe for personal projects, but 99% of us have to use the tech stack, pattern or whatever of our employer that was decided on (presumedly by some consensus at some point in the past). Publicly pointing out the flaws in what might have made sense then but might not make sense now is a Good Thing so that those flaws might be taken into consideration in the next round of consensus building.
I don’t understand what sort of cave trolls think I get to decide for me. Even as a lead I am making decisions based on the situation on the ground and those are informed by what people are comfortable with and what I can help them get comfortable with. It’s a team activity.
But every conversation has someone spouting off like these are experiments in a Petri dish. Petri dish projects don’t matter. Haven’t for a long time.
That's what interview processes are for.
And if the consensus goes for a stack you don't like, let's say for example for doing and SPA, then maybe it's not a mistake as stated in the article. Unless you guys think that any engineer that makes choices different than yours is incompetent.
I'm not clear why the author thinks that "media sites, really" are the only SPA use case. Have they never used webmail (like GMail), map apps (like Google Maps), or social networks (like Twitter)?
I give you Google Maps is a perfect example maybe for SPA.
But Gmail? Why do we need SPA for that? Receiving emails notifications could be websockets and clicking on email should go to a new page displaying the email. I dont know if the initial gmail was SPA or not, but the current version is very very slow and consumes a lot of memory to display some emails that worked even in terminal clients, remember Pine?
The same for twitter? I anyhow have to press "Load 39 new tweets" to load the new ones so why is it a SPA? Just for that notification? If you would give me a twitter client where I need to refresh the page to load new tweets but works faster and consumes less memory I will happy use that.
Do you want a full page reload just because you deleted an email? Or flagged it? Or marked it as spam? Or even do you want your webmail in frames just to have a reader pane? Webmail is a great SPA candidate.
I do agree that perhaps email clients are a good SPA candidante, but at the same time most of that type of interaction can be done with a sprinkle of JS.
Because of a few simple interactions that are better without a page reload, people write the ENTIRE application on the frontend, with everything duplicated (modeling, validation, routing, error handling, etc).
This article seems to be directed at dealing with people that use SPAs to make more content focused websites, which quite obviously are better made with the browser.
However, for an application with a persistent UI, I fail to see how constant page loads and navigation just because "the browser can do this" make any sense at all.
Even the example is a bit silly - SPAs that should be SPAs don't really have "links" per say, they will mostly have buttons, which will have a some sort of defined action. Perhaps these buttons will navigate to some other screen in the application, however, reloading all of the client side state every time one does this is absurd to say the least.
Finally, from a technical perspective, having a clear separation of concerns by having code talk to an API as opposed to HTML being rendered on the server, you remove a lot of complexity.
It feels like the people who write these articles don't actually remember how utterly shit the jQuery days were.
It does feel like the tiring contrarianism type of heading as opposed to a particularly well thought out article.
In the same way I feel lesser able coders seem to dwell on shit-slinging against tech that is proven, works, and has solved innumerable problems when they bang on about how things were so much nicer with vanilla JS and how frameworks are lazy and slow or something, in order to cultivate a sense of superiority.
Any well made tech has it's place, skill and experience are about knowing which tech should be used where.
> Be kind. Don't be snarky. Have curious conversation; don't cross-examine. Please don't fulminate. Please don't sneer, including at the rest of the community.
>> He was just unable to follow and understand the trend, and thinks that therefore the whole industry made a MISTAKE :D
That's not true, though. If you read [his next blog post](https://gomakethings.com/how-to-make-mpas-that-are-as-fast-a...) you'll see he has lots of experience with architecting an advanced multi-page application which works and behaves much like an SPA.
It makes his anti-SPA post kind of redundant and a bit hypocritical, but he clearly knows roughly what he's doing.
Yeah - I think a lot of it might be developers who don't understand that SPA and caching go pretty much hand in hand.
I'll admit that can make your life as a developer harder sometimes (to be blunt - caching is hard - full stop) but an SPA rendering from cache is basically a rocket compared to a server rendered page on a bad connection.
Absolutely no one enjoys waiting 2-5 seconds after clicking the back button to see a page they were just on, but that's the reality of a server rendered app on a bad connection. An SPA with good caching does, in fact, feel like a native app - in lots of good ways.
I think that approach is fine if you have an application that's light on interactivity (like school curriculum or tutorial, in his case).
And I agree 100% on the service workers (Seriously, I'm right there with him - they're magic). I would also recommend leaning more heavily on persistent storage (localstorage/indexdb) but I'm also trying to handle offline only cases.
I think some of the advice falls down when you're genuinely trying to create an interactive experience. I probably won't expound much farther, since I really try to keep business out of HN, but... I write applications that have been used for following treatment programs in education environments - think speech therapy or ABA for autistic students, where the user is inputing data frequently - as often as once a second - and the UI is updating in response to calculations done client side, that influence the instructors program and plan in real time.
It's a lot of screens, often customized locally on the application, and removing JS (or even just writing pure js) hurts a lot. That's... hard to do with minimal js. Really, really hard.
>no one enjoys waiting 2-5 seconds after clicking the back button to see a page they were just on, but that's the reality of a server rendered app on a bad connection
Wait, what. Don't basically all browsers nowadays keep previous pages in memory exactly not to do this?
> However, for an application with a persistent UI, I fail to see how constant page loads and navigation just because "the browser can do this" make any sense at all.
in the same way how re-rendering the screen in a 3D game for every frame does it. Or how you buy a new pair of jeans instead of meticulously learning how to patch the old ones. It solves a lot of problems even if it sounds suboptimal. It was the standard practice when computers and networks were much much much slower, therefore it cannot be that bad.
I think SPA and their entire ecosystem give some sense of optimality to purists which is probably shortsighted and wrong.
Use them where they make sense, and don't get religious about it.
I recently wrote a site to track Apple-silicon-native audio software. It works great as an MPA. It's fast, organized, and easy to use.
And the first thing someone offered as a suggestion was: "Why don't you use https://www.sanity.io/ for the content and nextjs or gatsbyjs for the front end?"
When I was a FE developer building single page apps, I would hear these arguments and say "you just don't get it, man, SPAs are the future". Now that I don't get paid to work on SPAs, these and other downsides come into very stark relief, and it's the advantages which seem more dubious to me. I doubt I'll ever touch an SPA again, and I'm not mad about that.
Indeed: "It is difficult to get a man to understand something when his salary depends upon his not understanding it"
Every week someone posts some half-baked blog ramble about how SPAs are bad (except for media sites!).
How about this - I am just as or more efficient working with SPA frameworks such as React as working with server-side-rendering. I have invested in a skill and toolset that can deliver any sort of website or web application from blogs to Youtube to Figma. I don't see any reason I would invest in learning an MPA framework that is only good for a subset of that.
If you are delivering content-only website with simple forms you might as well just use a CMS and call it a day.
Why are wood shops loaded to the rafters with tools? Because there is no Golden Hammer, only people who think they’ve found one.
You’re saying you’re doubling down on a single solution, which is probably not actually true, but you are surrounded by younger developers who will copy what you seem to be doing rather than what you’re actually doing.
All of these unresolved arguments are about team dynamics, not technology, which is why they never get resolved. Because we talk about our experiences or “objective” things like logic.
> All of these unresolved arguments are about team dynamics, not technology
No, it's definitely about technology. Let me simplify this for you. Technology A can do thing 1 very well, and thing 2 decently. Technology B can do thing 2 well, but can't do thing 1 at all.
In reality, people who only do technology B claim technology B is necessary to do thing 2 and go to great extents to write blog posts claiming such, while people who only do technology A get on with doing things 1 and 2 without feeling the need to rant.
I can't agree with this. That list of 9 complexities are well abstracted by the most common library or framework for SPAs. Having done a lot of SPA development I can say routing is not anywhere up on the list of trouble spots you do encounter. State management is another story.
Don't limit this to SPAs, include the Jamstack, which has all the same problems, and the false promise that if you can statically render a few pages or parts of pages and put them on a CDN, everything will be fast. It won't, because to load dynamic content, you still have to do a lot of work and talk to a (gasp) centralized API over the internet.
SPAs and Jamstack favor developer convenience over end user experience. Let's have fewer loading spinners, and more SSR-by-default for pages with dynamic content.
But there's a cost savings, right? JSON requests for just the data necessary vs. sending the whole HTML page each time.
Personally I'd prefer to develop a website the old-fashioned way, but I see that the bandwidth savings is a major point for SPAs and if you're running a business...
SSR by default, and then if you want, client side hydration and navigation so subsequent page loads happen in the client. However, the size difference between an HTML response and a JSON response is negligible, and HTML responses don't have to wait for Javascript to download, parse, execute, kick off off a request over the internet, get the data back, execute the result, and update the DOM. Browsers have literally decades of optimization to show HTML to users as fast as possible, and doing this in Javascript is fundamentally slower.
Am I the only one preferring good old-school, "boring" stacks that I can run entirely on my own machine if needed and understand the sequence of operations as opposed to relying on dozens of third-parties, services, APIs, etc just to do what a stupid PHP script on shared hosting could do 20 years ago? I don't consider the modern complexity as convenience.
I think SPAs were the best solution we had at the time and I enjoyed trying to build good UX with them. Yes, React started fresh and ended up driving me back to Rails because I'm just not smart enough. That said, I am really excited about Django w/HTMX, Rails w/ Hotwire, and Next and Remix with React. Particularly Remix.
I can get good at a lot of things, but most of them (including half the ones I’m good at) really just aren’t worth my time and energy.
Ain’t nobody got time for that is more often closer to the mark. But anything that requires hypervigilance is eventually going to make you look dumb. Just don’t let them put “human error” on the RCA. Yes, You screwed up, but We put you there in the first place.
636 comments
[ 2.5 ms ] story [ 336 ms ] threadThey might not be actual requirements for a blog or a brochure site but it's not totally unreasonable for someone to want their website to work that way. The question is not whether or not an SPA is a good or a bad thing, but whether or not the cost (more code, more complexity, potentially a series of background requests after the initial page load) is worth paying in order to get the benefits.
When either an SPA or a multi-page site is done well most users can't tell which sort of site they're looking at. That should be the goal. Make stuff where the user forgets about the tech. Make a website that just works, that's fast enough for users not to think 'this is a bit slow', and that's coded in a way you can maintain and work on for a long time. If you get those things right then no one can reasonably criticise what you've made no matter how you've made it.
Practical SPA's have many more network roundtrips than the equivalent server-rendered web interface. Every AJAX request is an extra roundtrip, unless it can be handled in parallel with others in which case you're still dependent on the slowest request to complete. With SSR, you can take care of everything with a single GET and a single POST no matter what. Images load in parallel of course, but those are non-critical.
A webapp where state can exist in various components is a perfect fit for SPA-s. Clicking a button in a widget and submitting the page with every other state, updating them and refreshing the page makes no sense.
SPA-s also help with separating frontend and backend development, they are different beasts. I know many backend devs who wouldn't touch frontend with a 10m pole and not because of JavaScript, but because they don't like design and UX in general.
The distinct advantage of an SPA is that, done correctly, cached data lets you render pages instantly.
Who CARES if the SPA had to make 3xRTT in the background, if it can serve the next page up instantly because that data is already present and cached, it's a huge win.
The server rendered app will ALWAYS have to wait at least 1xRTT for every new render. The SPA does not.
Still doesn't make an SPA the right fit for everything, but on bad networks, we get incredibly improvements in performance by rendering from cache, and occasionally handling cache updates in the background. User's fucking love it compared to waiting 2-5 seconds for every page load, even if they were JUST on the page a second ago.
This doesn't matter. It's an SPA - my js context hasn't been wiped because I'm not letting the back button eat the whole thing and start over. I just complete the request and store in cache either way, and next time they hit it, it will be there.
> Often we are not the sole source of a piece of information and first load time counts.
Sure - caching is hard (full stop - it's really hard). But I think if you actually consider most applications, there's not really much difference between an SPA rendering from cache and a server-side page that a user hasn't refreshed in a bit.
Basically - it's not going to solve all your problems, but in my experience developers really underestimate how much usage is "read only" and a cache works fine.
SPAS push giant gobs of logic and CSS ahead of the Time To Interaction and cross their fingers that nobody will notice because it’s not supposed to happen the next time due to magical cache thinking.
People have been ignoring actual research on caches for the entire time SPAs have been around. First time visitors have this as their first time visit. Occasional visitors show up after the last deploy, or like and enthusiastic web users get evicted between loads. And then don’t convert because your site is “always slow”.
P95 statistics don’t tell you if 5% of your users are having a bad experience. Or if new users are massively over represented in the P95 time. You are flying blind and shouting for other people to follow you.
But yes - I explicitly handle offline-only use cases, and god-damned terrible connections (think <1kb/s). In these situations, users are going to wait a long time for the first load either way.
They call in excited as all get out when they realize they only have to wait once with my application, and not once every page load. It's the difference between having a chance to grab a coffee at the start of the day, vs pulling your fucking hair out all day long.
If you handle offline up front - you mostly get bad connection speed wins by default.
It also helps that our app really isn't intended for casual use - ex: we have very few users who just load a page or two and then leave. Mostly they'll be interacting with the page for a duration of between 30 minutes and 2 hours (sometimes as often as once a second).
So yes - very much not a "blog" or "news" or "social media" site, and I think the value proposition for most companies might be harder to sell.
But I personally find it very, very nifty that we can do this at all in a browser. 10 years ago it would have required an installed application, or a port to each mobile platform. Which is sadly still a hard sell in some of the particularly poor school districts we worked with (mostly computers from the early 2000s, if you're lucky a really cheap tablet - like a kindle - floating around)
Not necessarily. I think you're conflating caching/fetching and rendering here. You can cache rendered views in the same way you'd cache the data for those views.
Turbo does this, for example. When you navigate to a cached page it will display the page from the cache first, then make a request to update the cache in the background. So it's similar to what you describe, but with server-rendered content.
That's not what the article was about (it was very clearly advocating for the standard browser flow)
Sort of. There's been no shortage of vulnerabilities where user data has been exposed when an authenticated users request was cached and re-used for subsequent users. It's been my experience that most SSR apps disable caching for authenticated users, or rely on more explicit control of components for partial caching where possible.
This is a cop-out. Storage is insanely cheap, and abundantly available on most machines. Plus the browsers will usually give it to you.
Can I know precisely what series of pages this user might hit? Nope. Can I load ALL the data this user might need to read? Probably.
You can do that with HTTP/2 server push on a multi-page app, too. The main difference is that with an SPA, you get to reinvent it all yourself.
Server push gets you closer with a traditional app, but it doesn't really solve the problem (or rather - yes, you get to reinvent it yourself, but you can do it better, with the specific context of your app)
Basically - My argument is that developers tend to treat SPAs the same as "the website" but with a bigger js framework. And that's the wrong mindset.
Instead, you should think of the first load as an install of a native application. You want to push absolutely everything, not just the next couple of resources you might need (like imgs/css). Ideally you push the current page first, and do the rest in the background while the user can interact with the current page, since you're almost never CPU bound on terrible connections anyways.
You'll get one additional load at login (because now I'm pushing all relevant user data), but after that... basically everything is rendered from cache (including optimistic cache updates based on what the user might be adding/updating).
After that... unless you log out or have cache evicted, you're going to see immediate response times, even while offline.
---
This isn't easy, and most teams don't bother, so if the comment is "If you're treating an SPA like a traditional page - don't bother" then I tend to agree.
but an SPA genuinely opens up options for bad connections that otherwise just don't exist (or are so bad people won't use them)
Sure - that first load is going to be bigger, but literally every subsequent request will use less data.
I try to reward consistent and current customers - and I'm not selling a news article or a social media site...
Now - if you do things like attempt to refresh all of the cache often, then sure, you have some problems to deal with.
> Making wrong assumptions about my device's environment makes for a poor experience for me.
Tough shit? I go way out of my way to make sure that loads on bad lines (or no lines) don't interfere with my users. You could just as easily say "I'm on gigabit why is this app using my disk space? Making assumptions makes for a poor experience. WAAAH".
That said there are plenty of SSR sites that are terrible too. It's not the technology that's at fault, but SPAs and common dev patterns in the community definitely enable and encourage making lots of small requests.
How is that any different from SSR? Waiting for a full page reload is fine, but incremental loading is not?
If that's your targeted usecase, send an exe on a thumb drive, and get them to call you back over the phone
The internet has become essential to modern life, and many people have no choice but to use it. Most of my monthly bills don't have any alternative to paying online. (Some will take a payment by phone but they'll charge a $20 "phone fee" or similar).
There are certainly sites/apps that just can't work without requiring a big and short pipe, but it's extremely defeatist to suggest that it's hopeless and we shouldn't even try.
You're certainly not alone with your opinion. I suspect that attitude is a big part of why this is such a problem nowadays.
I can tell you - My app renders immediately in these cases, precisely because I lean heavily on a cache.
I preload basically all the data a user might need at startup (prioritizing the current page) and then optimistically render from cache.
I'm very familiar with these kind of situations (I have developed software designed explicitly to handle offline-only cases, and I'm very familiar with how bad a connection might be in rural Appalachian schools.)
This can be a great strategy when you're dealing with bounded data that's not sensitive, but it's important to recognize that this approach is often inappropriate. A web app may be allowing users to wander around terabytes of data, or it may need to make highly consistent authorization determinations before allowing a user to see specific data, or you may need to keep a highly detailed audit log of who requested what when (e.g., if the app targets healthcare or government users), which aggressive preloading would render useless.
I've once launched a product on (early days) Google App Engine and it was unusable as purely server-side rendered website because sometimes it would take ages to load or worse an error would come up eventually. With AJAX it could be solved and failing requests could be repeated. It's a pity that this is rarely done but it is of course possible.
The data is never in the cache. It is for a developer because they forget to clear their caches when testing. For most users the data will not be cached unless they're sitting on the SPA all day.
So when the user doesn't have data cached that 3xRTT on their spotty 4G signal the SPA pattern is of no help. Not everyone is sitting still on great WiFi. Someone's cellular reception can go from great to unusable just walking down the street.
Then you have a shitty app.
Between localstorage and indexdb - unless you're literally out of disk and having storage being evicted, then if you've loaded my app once (and I mean once, not once this browser session) then I have data in cache.
Basically - don't blame SPAs, blame developers for assuming that the same "request the data every page view" paradigm is still in play.
It's not, and storage is insanely cheap.
This feels a lot like you're making a “No True Scotsman” argument. The behaviour you're saying doesn't / shouldn't happen has been the defining characteristic of SPAs since the beginning and it's still immediately visible on most of them as soon as you have a less than perfect high-speed internet connection. If statistically nobody except the Wordle developer can make something which handles crappy WiFi the problem requires more than just saying someone is a bad developer.
Yeah, but full page caching is a thing, and in my experience, teams writing traditional server-rendered pages are much more aggressive in their use of response caching than are teams writing JSON APIs. Honestly, a Rails/Django/Laravel app with smart caching headers and a Varnish instance in front feels more reliably instantaneous than the bespoke caching solutions each SPA seems to invent for itself.
I’m thinking of building something that only renders public data on the server, and then later gets that which pertains to individuals through an API. But I don’t want to spend more time on a lot of complicated plumbing than on core functionality in my application.
A SPA seems like the path of least resistance here, since all you have to do is put the appropriate cache control headers on your API endpoints, and let the browser abstract it away for you. I know programmers like to build bespoke caching solutions, been there, done that, but you don’t actually need to.
My biggest gripe with SPAs is the overhead of building and maintaining an API for everything, but GraphQL takes away a lot of that pain.
There are tools which make the creation of the backend painless and utilize GraphQL for sure (i.e hasura), but claiming that GraphQL solved that issue is nonsensical, because writing the GraphQL API is generally way more annoying then the equivalent rest API is
Equivalent tools for REST and non-REST HTTP do exist, but none that I've seen has been as cohesive as for instance Apollo. I used it on a small side project and was amazed at how much time and effort it saved me. The alternatives are a patchwork of different tools with slightly different goals for things like client code generation, documentation generation, playgrounds, HATEOAS, etc. that have to each be evaluated and shoehorned in with the others.
But I'd say it's more common to just use "private" cache headers in this case for any page that differs for logged in vs unauthenticated users. The user's browser will still serve as a full page cache, but shared caches will ignore those pages.
Big assumption. This is what the whole discussion is about. Doing "correctly" an SPA is incredibly expensive.
I can also assure you that when an MVC application is done correctly you can have an equally good user experience.
> The server rendered app will ALWAYS have to wait at least 1xRTT for every new render. The SPA does not.
This is an outdated idea of how server rendered apps work. See Unpoly, HTMX, LiveWire, Hotwire, etc.
I'm personally using Livewire. I make server request in only 2 situations. 1) When going to different pages (I'd need that anyway with an SPA given I need "SSR") and 2) When I'd need to write or read data from the server, which with an SPA would mean I need an API call anyway. Every other interaction is done with Alpine, 100% client side.
These people have lives and other shit to do besides spend all day in my web app. There’s a lot more accidental and exploratory clicks than you think and all of the background requests run even though the user is only on that page for half a second.
You just have to want to do it.
This is largely solved with innovations like GraphQL (which you don't need a SPA to use). Pages that require multiple API calls can show their UIs progressively with appropriate loading indicators. For SPAs that have ~long sessions, it's arguably a good thing to have multiple API calls, because each can be cached individually: the fastest API call is the one you've already cached the response for. This is stuff we were doing at Mozilla in 2012, it's nothing new.
There's also nothing stopping you from making purpose-built endpoints that return all the information you need, too. Your proposed solution (SSR) is literally just that, but it returns HTML instead of structured data.
SPA:
Vs traditional: Also if there are sequential ajax calls required to build the page (like a list -> detail view), it goes up a lot without speed oriented (very bad code style) API design. For instance you need a separate "GetListOfUsersAndDetailsOfFirstUser()" (or more general "getEverythingForPageX" calls). You can't do "GetListOfUsers()" and "GetUserDetail()" separately.So to match traditional webpage total load time you cannot do any ajax calls in your SPA until after the first user action. And even then, you only match traditional website performance, you don't exceed it.
Time until the first thing is on screen however, is faster in SPA's. So it's easy to present "a faster website" in management/client meetings, despite the SPA version actually being slower.
You can make SPAs faster than traditional websites ... but, for example, you cannot use javascript build tools. Since you need to do the first calls server-side and have javascript process them, and only send the result of the preprocessing, and the resulting dom to the client, after optimization and compression. After that you need to then do image inlining, style inlining, etc. I know react can do it, but does anyone other than facebook actually do that?
I think your point is still correct. SPA by default are a huge regression and you have to build and maintain sophisticated web infra to reclaim it.
https://www.ireadthisweek.com/author/ireadthisweek/post/2022...
The TLDR is it takes longer than reported.
Blog or corporate/news website on one end and an interactive game or video editor in the other, with a site like FB somewhere in the middle.
It’s not a hard rule and I can already think of counter-examples but this line of thinking is useful when architecting a new project.
This is literally a core use case of GraphQL. But you probably also want to fan out to preload all of the other user data in one API call: that's the whole point of doing it on the client. If you are trimming the handful of kilobytes on the first request, you're paying the price for that on each subsequent load.
> I know react can do it, but does anyone other than facebook actually do that?
Yes. Every job I've worked at professionally since 2016 (when this became mainstream) has done this. I do it in my side hustle. It's really not that hard.
> 2. get resources (js/jpg/movies/gifs/favicon/...) from server (1 roundtrip)
This doesn't go away with traditional sites.
Can you link to a tutorial for this? I'd be most interested.
But not every AJAX request is blocking or necessary for the site to be usable.
Am I missing something with remix? Is there anything really novel that it does/introduces?
There's a bunch of other comparisons out there, too.
[1]: https://remix.run/blog/remix-vs-next [2]: https://news.ycombinator.com/item?id=29983950
Yet the reality seems to be the opposite. If my connection is flaky (which it often is) then SPA's seem to fail more often and it isn't obvious what is going wrong. They don't seem to be faster either, long pauses between pages is common.
And therein lies the problem. In the _vast_ majority of SPA sites I've been to, they are not "well done" by this definition. It is commonplace for things to break, like the back button as the quintessential example, because the developer(s) didn't spend the time to make sure things work correctly. With a non-SPA site, you generally have to go out of your way _to_ break those same things.
I like SPAs for some things (gmail being a good example), but they should not be the default implementation; there should be a _very_ strong argument before the SPA architecture is used.
Without SPA you have horrific 10page forms and the challenge of maintaining state as you go back and forth to make edits.
The hard part is managing state, and there is no way to avoid it if your product is not purely readonly. SPA is one strategy, and a pretty good one.
Not saying it can't be done, but I haven't seen many SPAs that handles well flaky connections. Most stall with no indication to the end user, endless spinners or just broken in some random way.
Whatever MPA alternative you bring will need to address other shortcomings. There will always be something quirky due to building applications in a technology designed for hypertext documents.
Outside of some lasting mistakes made a decade ago, is this still an issue?
On top of that, “SPA” is a misnomer, as many frameworks give you a hybrid anyway.
Sometimes I realize a SPA would have simply served the user better, and it doesn't necessarily take much to be in that predicament.
Too many people hate on SPAs by, presumably, just imagining static read-only content like blogs and news. Though I'm also a bit tired of "SPAs suck amirite, HN?"
Put the user first, consider the trade-offs that work towards that goal, and see what shakes out.
The browser <-> multiple pages paradigm is pretty much how the web evolved, so SPA's just end up being one giant hack to get everything working.
UWP/WPF/any other desktop app framework demonstrates how easy developing a 'single page application' can be without all the cruft you have to add to make a SPA work because it's actually a sort-of-massive-workaround.
If you ignore those things in your SPA, much of the "cruft" is negligible.
As for bookmarks, that's semi-right, but the web platform conflates the back button with bookmark-ability in ways that are quite confusing. If you want the back button to get the user out of your settings screen, then you must push a new URL onto the history stack using JavaScript, but then if the user bookmarks your site they'll bookmark the settings screen specifically even though that's very unlikely to be what they wanted. Non-web apps that have enough content to need something like bookmarks often implement a similar mechanism that's more predictable, albeit less general.
The OPs article is really a common lament amongst web developers - they're (ab)using a tool that was never meant for what they're using it for. It's a real and very serious problem in our industry.
The whole SPA vs multi-page dichotomy really emerges because the web doesn't have sufficiently rich, modular or low level APIs. If you look at non-web frameworks like iOS or Java then you get a huge number of APIs (usually classes) tackling the low levels of a problem, then more classes building on the lower levels to give you higher level solutions. Those classes can usually be customized in many different ways, so the amount of control a developer has is enormous. If you start with a high level helper class and find you can't customize it to meet your needs, you can reimplement it using the lower level building blocks.
The web isn't like this. Web APIs are an absolute pigs ear of very high level function calls and classes designed in more or less random ways to quickly hack together a solution to whatever need a browser maker had at the time. HTML5 supports peer-to-peer video streaming and augmented reality but not a virtualized table view. In many cases it's obvious that little or no thought went into the APIs, e.g. XMLHTTPRequest has nothing to do with XML, you can open a URL for a custom URL handler but not detect if that URL will work ahead of time, nor even if opening that URL worked once you try it, and so on. Instead of starting with low level APIs and composing them into high level APIs, browsers start with high level APIs and then introduce entirely orthogonal low level APIs decades later, or never.
These sorts of problems just don't occur when writing desktop apps with professionally designed frameworks - even in the rare cases that you hit some limit of the framework, you can (these days) just patch it directly and ship a fixed version with your app. If you hit a problem in the browser you're just SOL and have to hack around it.
Our industry needs to reinvigorate desktop apps. Attempting to convert browsers into Qt/Java/.NET competitors has:
a. Made HTML5 unimplementable, even by Microsoft. It's not so much a spec anymore as a guide to whatever Chrome happens to do on the day you read it (hopefully).
b. Created tons of security holes.
c. Yielded an atrocious and steadily decaying developer experience.
Here and now, WPF is much worse. Even Microsoft itself now invests into React Native.
Microsoft invests in React Native as they want to bring applications built in React Native for iOS and Android to Windows.
The fact they are investing in React Native doesn't necessarily mean much more than the fact that they want developers to compile their React Native apps to Windows as well as the mobile platforms.
But OP's point still stands - it is much less hacky than anything in the web ecosystem even so.
The hard idea from the programmer’s point of view is automatic layout, not grids; my impression was that Tk originated it (it certainly brought it to light), but now that I’m looking I’m not sure it wasn’t already in Motif to some extent, so the roots of the idea might go down into the primordial soup of Project Athena and similar places.
Additionally HTML5 wasn't really 'made for' SPAs, it added features to HTML which could help support SPA's but it's main design decision was to be 100% backwards compatible with the older HTML spec. HTML5 is made for SPAs the same way that a stretched limo is made for commuting.
It's certainly true that you don't get to lean on built-in features like history support, but that's why you can now drive history with Javascript. And all sorts of other things. And if you're smart, you're using a solution that handles these things for you.
Rich client development is always hard—on any platform—, and you always make concessions for the platform you're on. I certainly have to when I'm building iOS apps. But I see no reason for this to dissuade you if you can push a better UX to the user.
As tried-and-true as server-rendered architecture might be, there are all sorts of things it will never be able to do no matter how much of a hack you think web client development might be. Software is a hack. And at the end of the day, your users may remain unconvinced when you preach about what the browser was "meant to do" when they begin coveting a richer experience.
That's something that's often left by the wayside when we discuss these things. We talk about technical superiority and become prescriptive about what we think technology is for. While fun thought exercises as craftspeople, we too seldom talk about delivering value. If leaning into the historical nature of the browser supporting HTML pages over the wire helps you build a better experience, that's great. But that's not the only option you have today.
I think the difference is how much you as a developer have to "fight" the platform. Having to implement history management yourself very much qualifies as "fighting" to some extent in my eyes… continuing with native platforms as an example, that sort of thing just isn't necessary in most cases – like with iOS, 99% of your "history" management comes for free with some combination of UINavigationController and view controller presentation, assuming the use of UIKit.
While bad SPAs do this badly just like bad iOS clients do things badly (like incessant spinners, zero caching, and unselectable text), it's a small concesión to make in the scheme of client-side development.
On the other hand, have you ever wrestled with CoreData on iOS? It's like using the worst ORM with the worst Active Record abstraction, easy to get wrong, yet that's the tool you're given. And you're choosing between that built-in solution or going off the rails with another solution with its own trade-offs, and both paths feel like you're wrestling with the platform.
It just comes with the space of client-development, you just tend to get used to wrestling with the platform you have the most experience with, and it's easy to forget that when you judge the concessions that must be made on other platforms.
This is why React is such a popular JavaScript framework of libraries.
Acquiescing to every demand product designers and management throw into the mix is what turns beautiful, easily-maintained codebases into nightmares.
What people are talking about here is writing web apps as a series of small, tightly-coupled spas who manage state within very specific parameters. And that's a great way to build software. Until someone comes in and asks you to draw in the state from one section into two other sections. You have the choice to say a) no and explain why, to b) create a clever "glue" that will be hard to maintain and difficult to explain, or to c) take the time to refactor the code into something more general and complex to allow for the feature on a more abstract level.
Guess which one almost always gets chosen.
The upshot is this: The solution to front-end complexity may not be a technical one, it may not require a new framework or library. It may be a shift in what we expect out of the web. We could always temper our expectations in order to keep our code clean.
Likewise the solution to SPAs isn't "say no to features".
A start would be devs communicating to product & design what the technical limits are to the current approach so that it informs their decisions.
In my experience, every single team I've been part of that was building an SPA was because they put the developer experience and desires first, even if in the mid/long term the dev experience ends up being worse as the project grows.
I completely understand the historic reasons for wanting to keep JS to a minimum and stay within ERB/Ruby, but the JS component-driven UI pattern feels like the correct front end architecture for most commercial projects these days, especially now with better JS tools like Svelte, which feels like rails for the front end to me.
I started playing around with InertiaJS recently and I absolutely fell in love with it. I was surprised to see how niche it still is because it's pretty much what I've dreamed what writing JS with Rails could be for the last 5 years.
Often when I see people arguing against SPAs, they are peddling trivial toy websites that don’t do much and don’t change much.
When you need to build a serious application on the web with quickly growing feature sets and complex state management, just use a SPA. It’s 2022.
If Hey is a toy app you then you must be working on some truly alien projects from the future or something.
Compare native Mail app and Hey. If anything should be a SPA, it’s an email app.
Also who cares how much kb of JavaScript your mail app uses? It’s completely wrong metric to optimize for an app someone uses every day, multiple times a day and likely has the app open all the time.
(Also haven’t heard people using Hey much since the launch)
I can sort of see the point if the "A" part of "SPA" actually applies to your product, but for the dominant players that doesn't seem to be the case.
Believe me, I get it.
I dealt with building these kinds of features for a full decade before SPAs became fashionable.
Now I'm stuck here watching in frustration as people go all-in on SPAs because they didn't know how to solve these problems without them.
In all seriousness it feels like there must be an elegant way to do more responsive modern functions while keeping the core server rendering concept. It's 2022 after all. But no I don't know a good solution either.
Where in this process did you need to start adding navigation via JS? Did that functionality really require JS, or was it implemented that way just because other parts are JS, and the trend was continued? Could you have used Stimulus controllers to manage functionality on the page itself, but when it came time to navigate to a new page, just done a basic browser redirect?
Saying that SPAs is a mistake doesn't mean that a strong dependence on JS or frameworks in a mistake. It just means that trying to make an entire site fit into a single page load introduces more costs than savings. You can still have pages controlled via React or Vue or Stimulus, but forgoing the router functionality.
In my own apps, I use Vue and VueX to control page behaviors, but each URL is its own Rails page. For example, I have a search page that uses Vue and VueX to asynchronously load, filter, and display search results. Clicking on a result is a basic browser redirect, taking you to the result's page, doing a full Rails page load, and again using Vue and VueX to manage any dynamic state (any static content is rendered via ERB files).
This has created a very clear and simple structure that allows each page to have any dynamic functionality it needs, but no complications from having to maintain navigation or browser history via JS. The browser already does that natively, and I get to spend my time working only on actual features--not recreating the browser's built-in functionality, or debugging my own take on it.
In my specific example, the navigation itself didn't happen through JS, but you need to hook into navigation APIs to handle backing into a partially-filled form after submitting to rebuild the UI to reflect the state before the user hit submit. To make things even more fun, Turbo/Stimulus (sometimes?) breaks bfcache in Safari & Firefox so they behave different from Chrome.
Personally, I detest the vast majority of SPAs, especially the ones from Google, Facebook and Linkedin. Not even sure what they do to make them so horribly slow to use.
I'm not sure I understand. The "conventional" solution to this is not to do it -- the reason browsers don't re-fill submitted forms is to prevent duplication. Give users the ability to post-backclick-repost and your DB will rapidly fill up with duplicate rows.
If you do need to back-navigate to a page that has been submitted (say, to edit the submission w/o requiring an edit-specific URL), you have the server re-render the form with the filled values.
If that isn't sufficient, you can always push your form state on the history stack using the history API, so even though this is a bad idea (IMO), you can still do it pretty easily without needing to resort to re-writing nav.
I hate to say it, but I feel like a lot of this stuff had server-side solutions that worked fine circa 2010, but have been almost completely forgotten.
There are tons of good reasons to prefer this behavior, aside from just avoiding duplicates. Think of login forms.
In fact, browsers even have re-submit built-in: Just press F5 on a page that was retrieved using POST. That’s why so many sites use a redirect after POST.
https://www.theserverside.com/news/1365146/Redirect-After-Po...
It sounds like this warn-on-double-submit behavior is convention and not required, so maybe it has become more common for browsers to stop doing it.
> Why not try it, right here, on Hacker News.
OK, I did. HN does a 302 redirect on comment POST to the comment GET. When you back nav, you are seeing the response to a GET request to /reply?id=X
(In other words, the form is re-rendered.)
Edit: oddly, however, submitting on form rendered by GET /reply?id=X ends up creating a new comment, so that's just weird server logic. It might explain why you occasionally see duplicate comments on HN.
Edit 2: on backnav to a comment edit, the redirect is to GET /edit?id=X, which is a re-render of the edit form, the process I described in my OP. Not sure what's going on with the back-after-post...maybe a bug?
With the text you previously entered, which the browser restored. If it did not for you, you have non-standard settings like disabled caching. I can assure you that it behaves the same on Firefox for Linux, Windows, macOS and Safari on iOS: go back, form content is restored.
In fact, the entire internet is full of people trying to get rid of this behavior.
> you need to hook into navigation APIs to handle backing into a partially-filled form after submitting to rebuild the UI to reflect the state before the user hit submit
Was this a hard requirement for the feature or just a nice-to-have? I understand why you'd want that behavior ideally, but it also seems the sort of thing that adds much additional complexity for a problem that isn't (at least in my eyes) severe. Having people re-fill things isn't too much of an ask, I think, and if it's something crucial, it might be better served with a preview page that allows additional changes. Or a "Make changes" link that redirects to a page that can load from the submitted state. There are easier ways to handle it than manually controlling navigation.
But yeah. If the behavior is a hard requirement from the business/product side of things, then using an SPA is no longer an architectural decision made for technological purposes, but is a feature requirement from beyond. And that's a whole different matter.
https://github.com/seanhess/juniper
It's an implementation of Elm (imagine React if you're a JS dev), but all logic is executed on the server. State is passed back to the server whenever you choose to listen to an event. The view is then re-rendered and virtual dom diffed on the client. Non-interactive pages are just views. If you want them to be interactive, you add a Message an update function.
I used it on a client project and it was pretty delightful.
It probably isn't documented well enough yet to make total sense, but I think it's a step in the right direction.
Bringing in React and turning that non-deterministic events firing all over the place greatly improved that situation, but this has become if someone needs JS, bring in SPA framework. This is in spite of the browser world getting much better. So just as JQuery was used to make SPAs to bad effect, SPA frameworks have been used to do minimal JS actions in a bad way.
To me the answer feels like it should be "traditional web app for most things, components-as-first-intended for some things". The simplest React example is just one component that abstracts presentation and logic. The only state is its own. It does not handle an entire web app as a SPA, no Redux, prop drilling, it's just the idea of a reusable component as an HTML tag. Same with VueJS and all. If we restrict ourselves to that we're in the good path.
If there was already an HTML tag for your own specific problem, wouldn't you just use it in your traditional server-rendered app? A `<photo-upload url="/photos">` tag that does exactly what you want. Or a `<wizard pages=5 logic="com.domain">` tag. We should create just those components, either in React/VueJS/Whatever or in vanilla JS Web Components, and live with the rest as we used to.
We're basically saying that some parts of our apps are too difficult to mix and match state management, and we should offload all of the state to one of the two sides. In some rare apps indeed all of the state should be on the front end, but the use cases for that are just not as big as they're made out to be.
This approach eliminates the need for a client-side router, keeps any centralized page state small, and improves the SEO and bookmarkability of the app.
I have implemented this architecture in several projects, and it’s effective
Modern Razor's pretty good (not Razor Pages, they also suck) which is probably what you mean.
But it's not that much different from Rails, Laravel, Django, etc.
I'd love to watch people attempt to implement some of the stuff I pulled off with that back in the day now. Occasionally, very complex data driven page flows are required and you could nail that entire problem domain trivially with them.
There is a very large and well known company with a front-facing product used by millions of people which took 5 years pissing around with three different front end technologies to reimplement what we did in 3 months with that and it was slower and heavier and basically reimplemented web forms in python in the end.
It broke the the www intentionally because it sucked. And it still does.
If you don't understand continuations, then you'll struggle and invent all sorts of poor state management patterns. Web forms simplifies some of this because the continuation is captured in viewstate, but it captures too much state (there are other warts too of course).
And WebForms wasn't a misunderstanding. It was a deliberate and brilliant design that brought WinForm developers and their experience to the web and allowed them to build complex web apps two decades ago that still work to this day.
The height of WebForms coincided with an embrace of web standards and accessibility which flows into the Web 2.0 era. You had to jump through a lot of hoops to achieve what was needed WebForms to get it to behave in a web-friendly way. The underlying .NET framework and base of ASP.NET (HttpHandler and HttpModule) was outstanding though.
(I still build/maintain WinForms, ASP.NET, and WPF apps.)
Who did; which "people"?
Corporations and "Web developers" maybe; users never asked for it AFAIK.
It got better by ASP.Net 3, but MVC/Razor was much, much better imo.
I'm working on one of those webforms projects right now, a legacy project that they need some fairly trivial tweaks to and it's an absolute nightmare of bad code. And I've done this before, it's become an accidental skill that I can still fix these awful messes.
One of the (many) reasons why webforms and the new Razor Pages are bad is because the code gets split up according to the UI instead of by function. So it gets scattered all over the place and is incredibly hard to do maintenance work on it.
The page-centric code layout that Webforms/Razor Pages/all the old PHP/Perl/etc. encourage is also extremely conducive in encouraging copy-and-pasta code for programmers trying to get stuff done asap.
So not only is it a nightmare to pick apart the code, you can often fine 2 or 3 copies of the same code that you only discover when one page is working as expected, but the same functionality is used somewhere else but someone just copied the page/control instead of actually splitting the page up into controls and re-using functionality.
I've seen this happen over and over and over. A single developer can avoid these pitfalls, a team cannot.
The problems started when you wanted to put together a REAL application. Because the entire paradigm of a web application programmed like a desktop application was flawed it resulted into an endless stream of headaches and workarounds, with bloated, complex and slow applications as a result.
In that sense ASP.NET MVC was a breath of fresh air.
For its time it was some cool tech.
My kneejerk rxn was "clearly you don't get it" -- but your critique and (mis)perception speak to their marketing / messaging, which, well, yeah, this is the world we live in. SPA has become the default, and Remix is pushing back on that, hard, and I'm stoked.
I've been doing webdev for a living since 1998, and Remix (like https://every-layout.dev 's "axiomatic css" - but I digress) is doing something profoundly powerful by leveraging the amazing power of the web platform on its terms, using the native APIs, and doing much, much more by being simpler and doing less. It's so refreshing.
My point is that for very interactive web apps, this is a significantly better architecture that a huge monolithic SPA
All of that said, I'm not opposed to approaches like Next/Nuxt/Aleph.
I agree with this because you affirm my bias.
Also because I've also had success implementing this structure. This makes sure that state of each page doesn't leak everywhere, which simplifies the state management.
It also loads faster than a big SPA because you don't have to worry about bundle splitting!
I'd be very interested to hear how you solve this, or if it's less of an issue than people might think.
Different teams exist for a reason. I've deeply regretted working in "we have real Devs that can do it all" environments.
It's not so much about boundaries as it is about competence. Or lack of it with
Of course eventually you need more people and I don't love anyone touch my code, but I'd rather have someone incompetent do entire stack that at least I can review than spend weeks upon weeks communicating basic assumptions.
I have a library I’ve been playing around with for 2 years now, I should package it up
Please do.
It’s better to just write that as a component in React or Svelte or whatever. It’s more testable, easier to understand, can carry state and logic just fine. It does mean you have to communicate in JSON for a small part of your app, but that is reserved to a few well known endpoints and components like a complex form.
The penalty is loading React or some other lib just for that, but if used in this way the extra dependency isn’t really a big deal as it’s not your whole app. Just use React as a library for components the browser doesn’t already provide.
And by components here I should really clarify as “affordances”. A “primary” button isn’t an affordance, the browser already gives you a button element and CSS classes (or HTML attribute) for that. It doesn’t give you a “multi page wizard with immediate validation” affordance, however, so that’s a good candidate for a component.
a core aspect of REST was HATEOAS, which stands for Hypermedia As The Engine of Application State[2]
htmx goes about it in the same way as the original web
perhaps that is wrong, but the web was pretty successful overall
[1] - https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arc... [2] - https://htmx.org/essays/hateoas/
Since HATEOAS is rarely implemented fully in practice, it would be simplistic to point to it as the reason the web is successful. The web allows an incredible variety of different architectures —- Some more closely aligned to HATEOAS and some less so. Perhaps this is the reason for the success of the web?
When it goes past the simplest examples it gets ugly real quick. Here's an example of client-side validation via HTMX: https://htmx.org/docs/#validation. That can't be the future.
htmx integrates with the existing HTML 5 validation API because, well, it's there, and that's what normal HTML does. I don't care much for the API, but that's the standard so we follow it. You can, of course, do client side validation however you'd like and integrate it with htmx using events, since events are the proper glue to tie things together in the DOM. I built a scripting language to help with this, and other stuff as well, called hyperscript: https://hyperscript.org
the "htmx" way for validation is the HTML/web 1.0/HATEOAS way: submit the form to the client side, validate and re-render. With htmx that can be inline, rather than a big clunky refresh-the-page action, which is why I say htmx extends and completes HTML as a hypermedia.
So long as you aren't willing to think about things in hypermedia terms, and htmx as an extension/completion of HTML as a hypermedia, you are going to miss the point. That's not to say that the hypermedia approach is always right, it isn't, but with htmx a lot more of the web application problem space is addressable with that approach.
And, as an aside, HATEOAS isn't great, outside the context of a hypermedia.
https://intercoolerjs.org/2016/05/08/hatoeas-is-for-humans.h...
It is, rather, a fairly pointless category error, driven mainly by cargo-cult mentality from the early JSON API era.
This. But IMHO the right solution is to make the back-end stateless and manage all client state on the client. Each request authenticates itself, and (if you're ReSTful about it) the back-end is simply a database connector/augmenter.
In this paradigm, the SPA is basically a desktop app that retrieves data from a server, built to run within a framework, which happens to be a web browser.
In case you're jumping to conclusions, know that I'm a late-comer to the SPA party, having resisted from its inception until about a year ago, for all the obvious reasons, including those bemoaned by the OP.
Why did I relent? SPA frameworks like React now handle pretty much all the heavy lifting for you. OP, you should check out React Router, which can render this post's examples irrelevant. I'm surprised that in 2022, someone writing to the web UI layer would bother to create code to manage the address bar when there are a thousand ways to not have to.
Routing (history management) is also a big problem (I'm assuming you weren't including that in your definition of "state" here).
Some would say, "but React, etc. have solved the routing issues", and that's perhaps the case. But what's difficult is wiring these routers up outside of a full React app. That is, if you truly want routing "solved", you generally have to go all React (or whatever) or just let the browser handle it in the traditional request/response sense. Sprinkling in just a bit of dynamic interaction wherein you want the history managed is purgatory.
And, on mobile, things get even more interesting. Consider the simple case of popping a modal (especially a slide-out). Many users will hit "back" on a mobile device, which they would reasonably expect to simply close the modal. But, if your app/page doesn't intercede to manage the history, the previous page is loaded instead.
The problem is which source of truth we want to use. I find that I'll need to refer to routes as the source of truth to process information, meaning "almost" every react action become route-manipulation action and it's state is just derived from the route, which is very similar with what MPA already do.
And, I do think they're distinct problems (but related) when you're just adding a bit of interactivity to a page or two, as this sub-thread is discussing.
In a full SPA, it's largely coupled and more inline with what you say: let the route drive the action. But, sometimes you have an MPA with a dynamic stateful feature embedded at a particular URL, but which may or may not need a sub-route of its own. In those cases, weird stuff can happen if the history is not managed correctly.
[1] https://inertiajs.com/
When someone submits a form and navigates back, don't you just re-display the empty form?
If it needs to be a form bounded with the uploaded file (maybe as part of a wizard), then why not ask the server for it instead of storing client side state?
Maybe using Javascript without a SPA looks hard because you're trying to avoid storing state on the server as well as bypass native browser features.
The issue is that, so far, we haven't figured out how to have a good fullstack DX.
Remix is probably one of the best attempts so far, but it still leans heavily towards being a very sophisticated renderer for the front end. The proof is you probably would not use Remix to create a 100% backend project with no front end. Same with SvelteKit or Next.
Until fullstack frameworks get more serious about the backend, we will be in this weird limbo.
In my current project I use Fastify as my main backend framework, and then use Svelte for SSR + hydration. I loose a lot of the frontend sophistication that SvelteKit brings to the table, but OTOH I have an amazing backend framework and total control and flexibility.
> YouTube is a great example. Being able to keep a video playing while you explore other videos is fantastic.
I hate that (mis)feature. When I click something else, my attention is on the new thing. Having to go find the little still-playing video window to close it is a hassle.
Nowadays I use Invidious which is a proxy that renders good old server-side-rendered pages and it manages to be faster despite being a proxy.
There are still some examples were SPAs are useful such as replacing complex desktop applications but 95% of web apps don't need it.
Bloom filters for CSS were a huge deal, and the CSS load and apply times are one of several things SPAs were trying to amortize.
Back before the web people made C++ and VB user interfaces that interact directly with the data source. SPAs are an attempt to make the web closer to that well known paradigm. POST REDIRECT REFRESH cycle was an insane programming paradigm that wasn't found in any previous edition of programming.
"Pages" are similarly in SPAs too. We usually have a router that helps the app decide what main "page" is on the screen.
Actually I'm old enough to remember old mainframe CICS programming that was a lot closer to the form-based HTML server-side programming of old. When "the web" came out in the mid-90's, it was actually a "blast from the past".
In fact there are adapters that literally turn these applications into websites by translating the forms into HTML. Commonly seen when you need to do something like change the beneficiaries on your health insurance.
“Client-server” paradigm came out in the 90s when PCs became powerful enough to run a “thick client” application, and was considered revolutionary.
In other words, the eternal cycle continues… :)
I wish more web developers were aware of just how damn good UI tooling was for native systems. Stuff like WinForms/WPF/Visual Studio and Cocoa/UIkit/Xcode on OSX. Perhaps we could get away from the insanity of rebuilding a select component for every new project.
Really though, this is a dead horse. SPAs are just a tradeoff like everything else in engineering. We trade the simplicity of a monolith for the ability to parcel out work efficiently between large disparate teams of frontend/backend engineers.
Instead of Google/Facebook CPU cycles being spent on rendering their content, it's now the client devices, while the Google/Facebook infrastructure is "just" serving the data.
Most of what you are going to see people argue about are cyclical fads. A pendulum. We try A and it doesn’t work. So we try !A. And when people forget why we stopped doing A someone tries it again over and over.
How are things different this time should be your second question. Your first question is what is the middle ground? Boolean logic falls on its face in the real world. If 1 is bad that doesn’t mean 200 is better. If 200 was bad the solution is not 1. The best answer is probably three.
However, the way NextJS implements SSR is really weird right now : if you use their <Link/> component, the pages props are actually fetched with an XHR request, then the page is rendered. I don't know why. It does feel like an SPA in the end.
> We recommend using Static Generation (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
If you need dynamic content, they recommend server-side rendering, and lastly client-side rendering only if the page is unaffected by SEO and requires a lot of in-page updates.
https://nextjs.org/learn/basics/data-fetching/two-forms
It’s a useful brush but you can’t use it to paint everything.
"I am indeed (rude). You can have 100 years of experience, it doesn't make you more credible at thinking that engineers who makes choices that are different than yours as incompetent, by deeming them as mistakes. I mean, if a company wants to choose a tech stack, there are so many things to consider for that choice. Your article is just generic ideas about the fact that SPA are mistakes and SSR apps are better. You're not taking into consideration any of each industry's and company's means, priorities, specificities.
In fact, your 100 years of experience are what's hindering your ability to see how useful SPAs are. And you're resorting to that same old narrative of "it was better before". It's also the niche you decided to position yourself in to sell courses and stuff. Which makes your ideas even more suspicious as they can't be unbiased. Like in politics, they'll dogmatically say that their opponents are wrong, just because they have a "market" to preserve.
So yes, I'm rude, as much as you're inconsiderate to the vast complexity of software engineering, just to preserve your niche market.
And as you say in your website: "Hate the complexity of modern front‑end web development? I send out a short email each weekday on how to build a simpler, more resilient web. Join 13k+ others." "
The last time I thought about this, which admittedly was many years ago, the alternatives that I knew about were: server-side frameworks like ASP.NET MVC Razor, PHP, RoR templates, Node.js EJS, Jade (now Pug), and static HTML.
Nowadays, you can create extremely elegant and performant SPAs with tools like Next.js and Remix, so I really couldn't agree less to the OP.
In fact, your 100 years of experience are what's hindering your ability to see how useful SPAs are. And you're resorting to that same old narrative of "it was better before". It's also the niche you decided to position yourself in to sell courses and stuff. Which makes your ideas even more suspicious as they can't be unbiased. Like in politics, they'll dogmatically say that their opponents are wrong, just because they have a "market" to preserve.
So yes, I'm rude, as much as you're inconsiderate to the vast complexity of software engineering, just to preserve your niche market.
And as you say in your website: "Hate the complexity of modern front‑end web development? I send out a short email each weekday on how to build a simpler, more resilient web. Join 13k+ others."
Every template-based framework I've seen has involved a lot of magic, syntax and DSL. That's not to say that JSX isn't a DSL as well but you can return regular React.Elements just as easily. You shouldn't need to remember a unique expression system just to be able to conditionally render. The premise that you're just not doing much with the data except dumping it into HTML is less true the more complex your site becomes. Think of the loads of conditions and evaluations that happen when you go to your Facebook wall or friends list.
Your line of thought is a large part of why I prefer ClojureScript to Javascript for SPAs. With reagent your components just work like the language, with nothing particularly special to remember.
EDIT: To answer your question, I actually think that JSX is a downside to React. I get that it was needed to convince the web crowd to adopt the library, but we need to stop pretending like we're writing HTML. We're not. The framework will take our JSX and do whatever it wants to it. I wish we just treated the DOM like a compilation target. That's actually what I like about Flutter - it treats the view as a render target and not as a document that you write to. Flutter web actually uses the canvas API to draw actually. And because of this, you can define your View model in a much more sane way (while still being fast). E.g. Flutter's Layouts make much more sense than HTML and flexbox/grid. They managed to separate the layout from elements. https://docs.flutter.dev/development/ui/layout
These days.. I just an Amrita like engine I've built myself and I just use <template> elements directly in the document. It only takes 40 lines of code to match child elements to data model items and fill in the template. Your browser already has a powerful parser and literal templates built in.. why invent a whole new language just for HTML templates?
I've never been able to fully apprehend the reasoning for JSX.
You do your app the way you want depending on the Ux you want to provide, on the tech you enjoy implementing, on the patterns you like to follow.
Maybe for personal projects, but 99% of us have to use the tech stack, pattern or whatever of our employer that was decided on (presumedly by some consensus at some point in the past). Publicly pointing out the flaws in what might have made sense then but might not make sense now is a Good Thing so that those flaws might be taken into consideration in the next round of consensus building.
But every conversation has someone spouting off like these are experiments in a Petri dish. Petri dish projects don’t matter. Haven’t for a long time.
But Gmail? Why do we need SPA for that? Receiving emails notifications could be websockets and clicking on email should go to a new page displaying the email. I dont know if the initial gmail was SPA or not, but the current version is very very slow and consumes a lot of memory to display some emails that worked even in terminal clients, remember Pine?
The same for twitter? I anyhow have to press "Load 39 new tweets" to load the new ones so why is it a SPA? Just for that notification? If you would give me a twitter client where I need to refresh the page to load new tweets but works faster and consumes less memory I will happy use that.
Because of a few simple interactions that are better without a page reload, people write the ENTIRE application on the frontend, with everything duplicated (modeling, validation, routing, error handling, etc).
However, for an application with a persistent UI, I fail to see how constant page loads and navigation just because "the browser can do this" make any sense at all.
Even the example is a bit silly - SPAs that should be SPAs don't really have "links" per say, they will mostly have buttons, which will have a some sort of defined action. Perhaps these buttons will navigate to some other screen in the application, however, reloading all of the client side state every time one does this is absurd to say the least.
Finally, from a technical perspective, having a clear separation of concerns by having code talk to an API as opposed to HTML being rendered on the server, you remove a lot of complexity.
It feels like the people who write these articles don't actually remember how utterly shit the jQuery days were.
In the same way I feel lesser able coders seem to dwell on shit-slinging against tech that is proven, works, and has solved innumerable problems when they bang on about how things were so much nicer with vanilla JS and how frameworks are lazy and slow or something, in order to cultivate a sense of superiority.
Any well made tech has it's place, skill and experience are about knowing which tech should be used where.
https://news.ycombinator.com/newsguidelines.html
That's not true, though. If you read [his next blog post](https://gomakethings.com/how-to-make-mpas-that-are-as-fast-a...) you'll see he has lots of experience with architecting an advanced multi-page application which works and behaves much like an SPA.
It makes his anti-SPA post kind of redundant and a bit hypocritical, but he clearly knows roughly what he's doing.
I'll admit that can make your life as a developer harder sometimes (to be blunt - caching is hard - full stop) but an SPA rendering from cache is basically a rocket compared to a server rendered page on a bad connection.
Absolutely no one enjoys waiting 2-5 seconds after clicking the back button to see a page they were just on, but that's the reality of a server rendered app on a bad connection. An SPA with good caching does, in fact, feel like a native app - in lots of good ways.
I think that approach is fine if you have an application that's light on interactivity (like school curriculum or tutorial, in his case).
And I agree 100% on the service workers (Seriously, I'm right there with him - they're magic). I would also recommend leaning more heavily on persistent storage (localstorage/indexdb) but I'm also trying to handle offline only cases.
I think some of the advice falls down when you're genuinely trying to create an interactive experience. I probably won't expound much farther, since I really try to keep business out of HN, but... I write applications that have been used for following treatment programs in education environments - think speech therapy or ABA for autistic students, where the user is inputing data frequently - as often as once a second - and the UI is updating in response to calculations done client side, that influence the instructors program and plan in real time.
It's a lot of screens, often customized locally on the application, and removing JS (or even just writing pure js) hurts a lot. That's... hard to do with minimal js. Really, really hard.
This doesn't happen because the browser caches the last page.
Wait, what. Don't basically all browsers nowadays keep previous pages in memory exactly not to do this?
in the same way how re-rendering the screen in a 3D game for every frame does it. Or how you buy a new pair of jeans instead of meticulously learning how to patch the old ones. It solves a lot of problems even if it sounds suboptimal. It was the standard practice when computers and networks were much much much slower, therefore it cannot be that bad.
I think SPA and their entire ecosystem give some sense of optimality to purists which is probably shortsighted and wrong.
I recently wrote a site to track Apple-silicon-native audio software. It works great as an MPA. It's fast, organized, and easy to use.
And the first thing someone offered as a suggestion was: "Why don't you use https://www.sanity.io/ for the content and nextjs or gatsbyjs for the front end?"
I can't even.
Indeed: "It is difficult to get a man to understand something when his salary depends upon his not understanding it"
How about this - I am just as or more efficient working with SPA frameworks such as React as working with server-side-rendering. I have invested in a skill and toolset that can deliver any sort of website or web application from blogs to Youtube to Figma. I don't see any reason I would invest in learning an MPA framework that is only good for a subset of that.
If you are delivering content-only website with simple forms you might as well just use a CMS and call it a day.
You’re saying you’re doubling down on a single solution, which is probably not actually true, but you are surrounded by younger developers who will copy what you seem to be doing rather than what you’re actually doing.
All of these unresolved arguments are about team dynamics, not technology, which is why they never get resolved. Because we talk about our experiences or “objective” things like logic.
No, it's definitely about technology. Let me simplify this for you. Technology A can do thing 1 very well, and thing 2 decently. Technology B can do thing 2 well, but can't do thing 1 at all.
In reality, people who only do technology B claim technology B is necessary to do thing 2 and go to great extents to write blog posts claiming such, while people who only do technology A get on with doing things 1 and 2 without feeling the need to rant.
SPAs and Jamstack favor developer convenience over end user experience. Let's have fewer loading spinners, and more SSR-by-default for pages with dynamic content.
Personally I'd prefer to develop a website the old-fashioned way, but I see that the bandwidth savings is a major point for SPAs and if you're running a business...
This is really, really important though.
Ain’t nobody got time for that is more often closer to the mark. But anything that requires hypervigilance is eventually going to make you look dumb. Just don’t let them put “human error” on the RCA. Yes, You screwed up, but We put you there in the first place.