I wonder why the article doesn't mention utilizing the browser cache for your static CSS and JS assets instead of introducing a service worker as first measure.
Few years ago I built a shopping site MPA this way and the page transitions were almost not noticable.
Especially since the `stale-while-revalidate` and `immutable` Cache-Control directives are well supported nowadays.
Stale-while-revalidate: see https://web.dev/articles/stale-while-revalidate & https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ca...
Immutable: https://datatracker.ietf.org/doc/html/rfc8246 & https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ca...
And if using a CDN, `s-maxage` (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ca...) is quite useful. Set it to a long time, and purge the CDN cache on deploy.
Immutable
is what you need. Crazy enough, Chrome has not implemented it. Bug open since 2016: https://issues.chromium.org/issues/41253661------------------
EDIT: appears that Chrome suddenly had decided in 2017 to not validate at all on reload anymore, after Facebook had complained to Chrome devs about Chrome being more a drag on their servers compared to other browsers.
To be fair, it’s because Chrome handling of a soft reload is different from Firefox or Safari, and does not lead to revalidating (let alone refetching) assets files. See https://blog.chromium.org/2017/01/reload-reloaded-faster-and...
Quoting https://engineering.fb.com/2017/01/26/web/this-browser-tweak...:
> We began to discuss changing the behavior of the reload button with the Chrome team. […] we proposed a compromise where resources with a long max-age would never get revalidated, but that for resources with a shorter max-age the old behavior would apply. The Chrome team thought about this and decided to apply the change for all cached resources, not just the long-lived ones.
> Firefox was quick in implementing the cache-control: immutable change and rolled it out just around when Chrome was fully launching their final fixes to reload.
> Chrome and Firefox’s measures have effectively eliminated revalidation requests to us from modern version of those browsers.
I just wrote my edit before I saw yours. Must have been a cache problem :)
“There are only two hard things in Computer Science: cache invalidation and naming things.” — Phil Karlton (cf. https://www.Karlton.org/2017/12/naming-things-hard/ or https://MartinFowler.com/bliki/TwoHardThings.html)
;-)
And off by one errors
Same (but not a shopping site). Bundle the JS and CSS into one file each and cache it forever (hash in the filename to bust the cache). Then with each page transition there's exactly one HTTP request to fetch a small amount of HTML and it's done. So fast and simple.
+ you need to pair that with immutable, otherwise you are still sending validation requests each reload time, so you are doing more than one HTTP request.
Exactly this. In our case we went so far to cache all static assets. Putting them into a directory with a hash in the name made them still easy to bust.
# Cache static files
location ~* \.(ico|css|js|gif|jpe?g|png|svg|woff|woff2)$ {
expires 365d;
add_header Pragma public;
add_header Cache-Control "public";
proxy_pass http://127.0.0.1:9000;
}
This has been the common/best practice for so long I don't understand why TFA is proposing something different.
Edit: below isn’t true. You could set immutable in cache header and the browser wouldn’t verify.
—— Original comment:
With browser cache, the browser stills need to send a HEAD request to see if the content was modified. These requests are noticeable when networks are spotty (train, weak mobile signals…)
Service Worker could cache the request and skip the head requests all together
Set max-age in the Cache-Control header to a high value and the browser will not need to revalidate. When deploying a new version, "invalidate" the browser cache by using a different filename.
Or keep the file name and send the file with a query string of some changed numeric value
Not if you tell the browser it's guaranteed fresh for 10 minutes and then use cache-busting in the URL
Oh yeah. The immutable tag right? Total forgot about that
> I like to argue that some of the most productive days of the web were the PHP and JQuery spaghetti days
I've wondered if going back to that paradigm would be more productive or not than using React et al.
Plenty of big sites like Amazon or Steam still are made this way. Not exactly PHP + jQuery but rendering HTML on the server and sprinkling some JS on top of it.
Has anyone gone back to working like that?
I just finished migrating a fairly large legacy vue2 app to server side rendering and HTMX. Its thousands of lines less code and also hundreds if not thousands less dependencies. Most importantly I’m not worried about the tech stack becoming abandoned and un updatable in 5 years like vue2 was.
There are some pages that require more local state. Alpine.js has been great. It’s like Vuejs that you can initialize on your page and doesn’t require a build step.
How much of that code and dependency reduction is due to having the entire app to use as a spec? How can you be so sure this new stack won't be "abandoned"? (Vue has received regular updates for 11 years)
I've found that to be the case for my personal projects. Not less for the fact that I don't have to spend any time googling anything. I can just write code. I've used web tech for so many years that I don't need to learn anything anymore if I'm not in a framework. Outside a framework, it's all just the same stuff that's always been there.
Even today's LLM-assisted programming doesn't give me that fluidity. If I use LLMs to assist me in writing a big framework, it'll autocomplete stuff I haven't learned yet, so I need to retroactively research it to understand it.
JS soup on a webpage is a mess, but it's all using the same tools that I know well, and that to me is productive.
Not sure if this counts as “going back” but I have been managing a legacy project that is built like this for the past year or two. I hated it at first, but now I’m starting to appreciate it. My approach is to try to put as much as possible in php, but for parts of the page that are going to be manipulated by js/jquery, just have php pass the data as json and build that portion of the dom on the front end.
I am have several side projects, and they mostly follow this pattern. For one app, I developed around 100 views (plus the admin views) in Django in just a few months, I could have never done it if I was using a "modern" stack. In many applications most pages (login, registration, logout, entering data etc.) can be built using traditional server-side rendered HTML forms with a little Javascript sprinkled on top, and most of the JS can be handled by Alpine.js. For the pages that need more interactivity, I use HTMX and Alpine.js. It works really well.
we more or less do for all the applications in my team, which don't follow the corporate standard.
They're all Django applications, and the limited dynamic elements are just simple jquery. We have some bootstrap stuff and elements like form elements in javascript, but that's about it.
We are extremely productive, especially compared to our official apps which follow the .NET/Angular stack, that run into all kinds of API versioning issues and errors, it's not even a faster user experience. The problem with such a stack is that you need a few highly skilled architects/system designers. We just have regular programmers piecing it all together, most of them learned these frameworks on the job and come from a regular app dev background, not web.
Granted, we only serve something like 20-30 concurrent users for each of tthe Django apps (as in, page requests/second), but still...
I think the old way works well for a smaller scale app that doesn't need to change often. Otherwise, I find the components-based code reuse to be a pretty valuable pattern, especially when working as a team.
There's no reason at all why you can't also organise code into components under this model. Orthogonal concepts
Most (if not all) backend frameworks provide components these days.
See Laravel, Dotnet, Rails, etc.
Steam has largely abandoned it in favor of React in their big facelift a couple years ago.
You mean the Steam client or the store?
I was referring to the store. Just checked it and there's tons of jQuery and vanilla stuff (at least on the homepage).
It depends.
A shopping site or similar, then sure - the "one thing at a time " workflow works.
For internal line of business apps? I'm not sure sure anymore. From a comment of mine a few days ago: https://news.ycombinator.com/item?id=42148627
> Which is a pity; I was watching a client do some crud work in a webapp.
> Web - 1 form per page:
> Click "back". Copy some text. Click forward. Paste it. Repeat for 3 different fields. Click submit.
> Native apps (VB/Delphi/etc) used to be:
> Open both forms. Copy and paste from one to the other. Open another one on the side to lookup some information, etc.
> Webapps, even moreso with MPA, force a wizard-style interface - you only go forward through the form. This is not how people used to work; they would frequently have multiple forms in the same app open at the same time.
> With SPA and html "windows" made out of movable divs you can probably support multiple forms open at the same time, but who does that?
IMO MPA apps that don't break the back page and support multiple tabs and windows are far easier to have the side by side comparison model than SPAs which get fun dom nonsense the moment you try and do something the dev didn't precisely expect.
I use this general approach in most cased.
Yes, this is 100% of my work.
It’s really not more productive.
Working with react has a higher barrier of entry (something which is becoming less true over time given that many templates etc exist for it), but if you want to be producing pages and functionality, a good react dev will run leagues around even an a exceptionally good jquery dev.
I’m solid at both and we are talking a dev cycle that is magnitudes faster once you are set up reasonably well. There’s a reason it’s popular. Typescript makes a lot of testing unnecessary and gives many guarantees on robustness. React makes it a breeze to create complex state full components. Nextjs makes it super easy to deploy all this to a web app and quickly create new pages.
For as much as you're right, your taking a very narrow short-term view of productivity.
The maintenance demands for using a heavy, evolving framework and a large graph of churning dependencies are tremendous and perpetual. While you can make meaningful wins on delivery time for a new feature, once "you are set up reasonably well", you've impliclty introduced new costs that don't apply to "vanilla" code that stays closer to established standards and only includes code it actually uses.
That's not to argue that vanilla is strictly better (both have a place), but just to acknowledge a latent but significant tradeoff that your comment seems to ignore.
> The maintenance demands for using a heavy, evolving framework and a large graph of churning dependencies are tremendous and perpetual
Counterpoint: If you replace a frontend framework with backend components, as has been suggested a few times in this thread, those frameworks are even larger, and a lot of the basic functionality provided by React (such as components) is often provided by third-party packages.
Sure, if you're using something like jQuery, then it's smaller and more stable, but the functionality is limited compared to both backend and frontend frameworks. Which of course might be 100% appropriate depending on the use case.
> and a large graph of churning dependencies are tremendous and perpetual
Absolutely. This the reason I'm moving on from JS in the backend. You're constantly stitching up dependencies which might be abandoned and/or become incompatible at any moment.
> a good react dev will run leagues around even an a exceptionally good jquery dev
It probably depends on the use case, no?
If you only need links, forms, and data tables what advantage does React have over SSR + jQuery?
Composabilty and abstraction. I can bang out a react form in our app in minutes just by defining the shape of the fields. All the loading states are handled automatically. Mutations and refreshing data works out of the box. Navigating between pages is instant. Data is cached even when navigating between pages. Some pages that require many user inputs utilize optimistic updates to afford very low latency feedback.
React makes all of that easy to compose. I tell it how to render my state, and I write code that updates state.
Non-spaghetti dynamic forms and their validation (yes you need to validate on server as well).
Reusable components that can be tested in isolation. Type support. That leads to easier evolution and refactoring.
With good architecture you can go mobile with react-native.
Most modern backend frameworks provide components, types, and validation.
Laravel, Dotnet, Rails, etc.
Can someone explain what the service worker strategy accomplishes that plain old http Cache headers don't? It saves a (almost zero weight) network roundtrip, but feels like it's re-inventing the entire wheel for that small (I think) optimization? Am I missing something?
For a multi-page app, one of the important uses of serviceworkers is pre-loading and caching resources that aren't on the first page. eg you might have a background image that's only displayed three pages deep but can download and cache it as soon as the site is open.
You can potentially use http2 push to send down files from the server early but I've seen the browser drop the files if they're unneeded for that page load.
Yes, there are other hacks you could do to make the browser download the resources early, like invisible images or preload audio files, but if you're going down that path why not put in a service worker instead and have it download in the background.
Unfortunately HTTP/2 push is basically dead. Very few sites made use of it and Chrome removed it: https://developer.chrome.com/blog/removing-push
A minimal network roundtrip is pretty minor only so long as you're on a reliable connection to a nearby server. Add even a little packet loss or moderate latency jitter and 5,000 miles and suddenly any roundtrip avoided is a good thing.
It was designed for apps, extensions and pages that behave like apps (stuff that might not have a server anywhere, just a manifest and some static HTML/JS). The cache is only one of the use cases.
I think some pages still use them for running background stuff. My browser is setup to clear all of them upon closing the tab.
This whole direction is being silently discontinued anyway. Running browser apps has become harder, not easier.
>This whole direction is being silently discontinued anyway. Running browser apps has become harder, not easier.
I'm outta the loop, can you expand on how this is the case?
When these things appeared, both Mozilla and Google were signaling the intention of distributing some kind of standard webapp. At that time, via FirefoxOS and ChromeOS. Even MS was signaling web with Windows 8 (WinJS apps, even for Windows Phone).
So, there is some piece of infrastructure for this future here and there. Service Workers is one of those pieces. But the apps only achieved some success in closed markets (extension stores). It never became a standard (visit a page, pin it, becomes a fully fledged app).
Instead, the web moved to mobile and desktop apps through other means (super-Cordoba/Electron-like apps, little JS/HTML insertions in traditional apps, other inventive web ways that do not involve a collaborative standard).
The leftovers of this imagined distribution mechanism are being pushed aside (hidden in weird menus or options). Tech is still there because it is a standard, but the counterpoint UI and market decisions are pointing in other directions.
For example, both in Chrome and Firefox, the ability to invoke the browser "chromeless" that was a part of this whole thing has been removed or muted in some way. It was never a standard, so it was removed as soon as possible (probably few people working on it).
Does that make sense?
You program servive workers in the client whereas headers are controlled by the server. Among other things, this means that service workers work when you have no internet access.
that they can be used to compute stuff locally
I imagine ideally we want user choice of where the computation is happening. if on a mobile device I'd save battery in exchange for network latency
but in a desktop computer I'd rather do as most local computation as I can
For the sort of thing that are fast enough that network latency is relevant, on a mobile device you save battery by doing them locally. The radio takes more power than the cpu.
I'm not asking about web workers generally. I'm specifically asking about their use as a client side cache as described in the article.
Radio is one of the biggest users of battery on mobile devices.
Full title is "You Can't Build Interactive Web Apps Except as Single Page Applications... And Other Myths"
Omission of trailing part changes the meaning and makes it clickbaity.
You are right. The original title was too long for HN. I have since edited it to fit inside the requirements while keeping the spirit.
It’s fine. I don’t see the click-bait-ness of what the other person is talking about. Especially since I’ve run into the title length limit before. Some people have bad days and are more (overly?) critical.
“You Can't Build Interactive Web Apps Except as Single Page Applications” is false, which would make that title clickbait, specifically of the “ragebait” family.
You obviously shouldn't build interactive pure-HTML apps, but that’s a talk for another day ;)
I am no frontend-guy, so I don't understand why in the age of node.js web-servers this ditchonomy exists between server-side and client side (SPA). Can't you initialise/pre-render most of your your stuff on the server, serialise it and push it through the client, which then acts as an SPA already initialised and then updates itself on its own. After all, both are JS? Why is the decision not more flexible where to run code, depending on latency, compute intensity etc. Maybe someone can enlighten me, as this is often left out, probably because it is obvious to someone working with these technologies.
I'm answering your initial question directly, but there is more going on that may be more informative.
This is a distinction between computation that is performed on a server, using arbitrary tools, and computation run on a user's machine locally. The former is much more flexible, but comes at a cost of latency; all user input and output must be serialized, and transmitted a long distance. Front end code can result in much lower latency to the user due to ommitting this transmission.
It makes sense to apply a suitable mix of these two tools that varies based on an application's details. For example, an immediate UI response that can be performed using only information already present on the client makes sense to be handled exclusively there, as computation time is minimal compared to transmission and serializtion.
I believe that JS being able to be used on a server is not relevant to the core distinction.
You can! The terms to search for are "isomorphic web apps" and "hydration". It's definitely not a panacea though.
why? what are the main drawbacks? I imagine the complexity, but can you go a little bit into the details for someone with only little frontend experience
Hydration is not needed tho, frameworks like Qwik allow isomorphic apps without hydration.
On the server, applications are generally super close to their backend dependencies, so multiple round trips to data stores, caches and whatnot are no problem. On the client, this would be deadly.
So it’s not just easy to take code that runs on the server and run it on the client. Anytime the client needs to do more than one round trip, it would have been faster to render the data completely on the server, html included.
Additionally, with SPA’s there’s a lot of nuance around back/forward handling, page transitions, etc. that make a page based application awkward to turn into a purely client side one.
One thing might be that you can build an SPA into a mobile app, which maybe would have a harder time passing review with app stores if half the code is running somewhere else? Having said that, of course backends do already exist, but I wonder if it might be viewed slightly differently.
This is what frameworks like SvelteKit and NextJS do
You don’t need a SPA or to pre render anything. The reasons these things occur is because most of the people doing this work cannot program and cannot measure things.
> The myth that you can’t build interactive web apps except as single page app
You can do it, but you might paint yourself into a corner. For example, your manager might at some point say: please load X, while Y is animating; that will not work if the entire page is reloading. A SPA will give you more control and will also reduce the probability of having to rework entire parts of the code.
The most dangerous thing in programming is developer boredom with a dash of hubris and ignorance of the past.
What if we took these micro services and grouped them into a single block so they can talk to each other faster? Call it… a macroservice!
The HN headline is more combative than the actual headline:
You Can't Build Interactive Web Apps Except as Single Page Applications... And Other Myths
This is an essay contributed by Tony Alaribe, based on his talk at BigSkyDevCon this past year, discussing techniques to make a non-SPA based web application feel fast and slick. He mentioned some techniques that were new to me and I thought it was the best talk at the conference.
I have some "internal" web apps that I use for myself, and while I do use Remix which is a framework that allows me to use React, I just use SSR and HTML default form controls as interpreted by the browsers, minimal client side processing and almost no styling. I love it so much compared to the "modern" cruft. It's responsive by default because I don't really style it. It has a high signal to noise ratio.
I wouldn't change it for the world, but I've been told multiple times I'm very much in the minority.
The final spaghetti point really takes the wind out of this article's sails. A lot of it is good information, but too many parts make me think "That's a stretch."
To me it seems that if you have decent cache control headers, then SSR can be decent even without very specific optimizations.
When choosing a tech stack, normally I’d also look for which one rots the slowest. Writing a SPA will typically mean that in the case of the libraries/frameworks there becoming untenable, at least you have an API that you can use when writing a new client.
I have this PrimeFaces/JSF project at work - it’s unpleasant to work with and feels brittle (especially when you have to update components, in addition to nested table row updates being pretty difficult). I’ve also helped migrate an AngularJS project to Vue, the former was less pleasant to use than the latter but the migration itself was also unpleasant, especially when you wanted to get close to 1:1 the functionality. I like how Angular feels more batteries included than the other options, but I’ve seen some overabstracted codebases. React seems like it has the largest ecosystem but the codebases seem to rot pretty quickly (lots of separate libraries for the typical project, lots of updates, things sometimes break). The likes of Laravel and Rails let you be pretty productive but also have pretty tight coupling to the rest of the codebase, same as with PrimeFaces/JSF. I’ve also seen attempts at putting as much logic in the DB as possible and using the back end as more or less only the view layer, it was blazing fast but debugging was a nightmare.
Honestly, just pick whatever technology you think will make the people working with the project in 5 years the least miserable. For me, often that is something for a SPA, a RESTful web API, some boring back end technology that connects to a relational database (sometimes SQLite, sometimes PostgreSQL, sometimes MariaDB; hopefully not Oracle). Whatever you do, try not to end up in circumstances where you can only run a front end with Node.js 10 or where you can't update your Spring or ASP.NET version due to there being breaking changes in your coupled front end technology.
I dream of the day that we are finally free of the iron grip the web browser has on the minds of those that would create the future of technology.
Well there was this idea about 16, 17 years ago called “Rich Internet Applications.” It flopped for many reasons.
The only reason to use spa as far as I am concerned is that’s the way the industry was going 10 years ago… so the community and controls etc… were / are for spas.. any other reason to me was just chasing new tech.
I made the switch and the community it stronger than ever for vuejs and react
when i discovered angularjs i thought it was a revelation. finally i could write webapps without having to track UI state in the backend, only treat the backend like a database. all the UI logic was dramatically simplified. it went so far as making my backend so reusable that i don't have to do any backend coding at all anymore. creating a complex webapp is now as simple as writing a desktop client.
sure, i could do the same thing with a traditional fullstack framework. with discipline i would be able to keep frontend and backend code separate. but i have yet to work on a project where that is the case.
i don't build SPAs because the industry demands it. i build SPAs since before the industry even heard about it. and i build them because they make for a cleaner architecture and give me more flexibility than any fullstack framework would.
My feeling nowadays (i.e. since around a decade maybe) is that there is a lot of technical complexity going on which should imho be either an internal part of the browser itself, or should not be used that widely.
It's probably all good for something... But I would love to just make my web app out of what the browser itself can do, without a tech stack as high as a skyscraper for me to handle.
I know, this way the ecosystem can develop more rapidly (compared to waiting for improvements in the official web standards), and it's also fun to play with toys, and everyone can raise his/her value by learning more and more of these tools.
On the other hand, the web was imho in a better shape before all that began. From user perspective and from developer perspective.
I could be wrong... I'm not primarily a web developer at all...
This kind of stuff misses the entire point of frameworks like react or rails for that matter
There might be some technical advantages you can argue but there’s an undeniable economic advantage to not needing to make a bunch of disparate choices and wire things up in a way that you hope will not bit you in the ass later.
Test automation.
Do I have to point out that "you can do X without Y" is never an interesting nor insightful statement? No shit! so? Does that mean you should do X without Y?
Doing X without Y seems very one-dimensional though.
It's interesting when the common wisdom is that you cannot do X without Y.
This is largely code for "I used to slap shit together and nobody told me I did a bad job. Now I slap shit together and people tell me it's just shit slapped together and I don't like that."
The real gradient is more like:
1) a SPA is needed because it might have to run offline
2) A SPA is extremely beneficial because the state of complex interactive are difficult to maintain on the server
3) A SPA helps because we have complex interactions and want to keep the server stateless
4) A SPA is used because we only have developers who haven't done it any other way and won't be fast enough trying something new
5) A SPA is strictly detrimental and ought not be used because there aren't any complex interactions and the added weight to processing / network traffic / etc overwhelm whatever justification we had.
This is not really novel, newsworthy or even worth yet another rambling blog post.