You need to go through a TCP handshake for every connection you open. You can reuse the connection but that means queuing requests and hitting the latency as a multiplier. Browsers also have a cap on parallel connections so when you get enough requests it queues either way. HTTP/2 reduces the problem with multiplexing but it still doesn't go away.
If your app has to make 40 requests pulling in js files, CSS files fonts, API calls etc. to show something useful that will be visibly slow the moment a user is out of region. If it's a single server rendered piece of html it's a single request.
Then for real fun be in an enterprise environment with Kerberos Auth adding a load more round trips to the server. For even more fun add in a mobile browsing solution that only has ingress in a far region when the site is hosted locally!
Absolutely. This is the prime hallmark of shitty, slow web. If you fight your battles here everything else will fall into place.
I've started building our B2B web apps where the server returns 1 final, static HTML document in response to every resource. There are no separate js/css resource URLs at all in our products anymore. Anything we need is interpolated into the final HTML document script or style tag by the server.
If you back up for a few minutes and really think about what kind of power you get with server-side rendering relative to # of requests... It's a gigantic deal. Composing complex client views shouldn't happen on the client. It should happen on the server where all of the answers already live. Why make the client go read a bunch of information piles to re-assemble Humpty Dumpty every goddamn time they want to view the homepage?
In terms of latency, the game theory for me is simple: Either do it in exactly one request, or it doesn't matter how many requests it takes. That said... caching effects are really important for many applications, and separating sources out can save you a lot of $$$ at scale. So, you will need to balance the UX and economic effects of some of these choices.
Correct me if I'm wrong, but originally it was because "servers" had order-of client compute power. Ergo, it was necessary to offload as much compute to the client as possible.
And then in early js era it was because network latency and bandwidth limitations precluded server-client round-trips for anything user-interactive.
Now, we've got surplus compute and bandwidth on both sides (order-of text payloads), so caching architecture and tooling become the limiting factors.
Still do. Biggest difference: If you intend to serve the same complexity of web experience today that you were trying to serve 20 years ago, you will find a single MacBook would likely be enough to handle the traffic. The challenge is that we've taken advantage of the exponential compute growth over the years and now it feels like we still have the exact same scaling issues on the server relative to the client.
If you constrain your product & design just a tiny bit, you can escape this horrible trap and enjoy those 4-5 orders of magnitude. If you design your website like Berkshire Hathaway or HN, you are on the right power curve. Seriously - go open Berkshire's website right now just to remind yourself how fast the web can be if you can lock that resume-driven ego crap in a box for a little bit.
My issue with these kinds of discussions is that they're inevitably using outright false arguments.
You can make a well performing website through SSR and a backend templating language, yes.
However , In a business setting with lots of developers this usually becomes an abhorrently performing website despite the SSR with the same templating language.
You can make a pwa with any of the usual frameworks and it's going to perform wonderfully... The bad performance begins when people start to write terrible code or just keep adding dependencies to cyberstalk their users.
But doing the same in the backend puts you into the same position wrt poor performance, so it's just not an argument for or against SPAs.
It's totally fine if you decide to write your website with a SSR stack however, and it could very well be the better choice for you, depending on how your skillset is aligned with the technology.
>However , In a business setting with lots of developers this usually becomes an abhorrently performing website despite the SSR with the same templating language.
I'm not sure this follows. It's harder to do client side rendering because your data is across a slow HTTP barrier. And the client is maintaining state which is harder than stateless.
The problem with SSR is that it's usually tied with single applications which are usually monoliths. But neither of these are requirements for SSR. If you break up your monolith into modules and/or SOA you get the best of both worlds.
But for reasons I don't understand nobody does it this way. It's either monolith + SSR or microservices + client rendering
So it's 2023 and I've been concatenation all my css, and all my js, into a single css and js request since like 2005. So a call to the site results in maybe 3 or 4 requests, not 40. But I've noticed most sites don't do this. Perhaps because I was trained when we had 28k modems not 100mb fibre?
Http/2 makes a big deal of multiplexing, but its less convincing when there are only 3 connections already.
I guess I also don't have a million separate image files, or ads etc so that likely helps.
Same goes back when we could just load a single library from the Google public version (cough-jquery-cough) and then all the on-page JS was just in-tag handlers. Not that it was better but boy howdy was it faster to load.
The former is specifically for reducing requests, while the latter is more about UX.
For css sprites, you could submit an image to a folder in the monorepo, and a couple of minutes later an automated system would update the image with all the sprites, and create constants that you could you in your templates/css to use the icons with the coordinates/size of the image.
(As Deno shows, ESM imports make a lot of sense from CDN-like URLs again. It just won't perform well in the browser because there is no shared cache benefit.)
Because the trend went from having Webpack produce a 5+MB single JS bundle to splitting based on the exact chunks of code that the current React view needs, particularly to improve the experience of mobile users.
Cheers Bruce
As it stands your site has to go through a TCP handshake then send the request and get the start of the response - at minimum that's 2 round trips. It then gets the JS and CSS tags in the head of the HTML and has to request those - let's assume HTTP/2 so that's another single RT of latency (on HTTP it's at least two depending on if you queue requests or send in parallel).
On 215ms RT latency that is an absolute minimum of 645ms before your JS has even started to be parsed. For a large bundle with a load of vendor code at the top in practice were talking several seconds to getting to the code that makes the API calls to get the content needed to render the first page.
And this is before we talk about any Auth, images, opening a websocket or two and people using microservices as an excuse to require a request for nearly every field on the page... (Or worse multiple requests and applying some logic to them...).
There is a fundamental minimum RT bound to a static JS based web app that is a multiple of a server rendered page. If you cache the HTML and JS bundle it can be worth it but you still need to aggregate data sources for the page on a backend and/or host it in multiple regions.
To put this in a way the common man can appreciate, imagine you're at a restaurant and you ordered some clam chowder and your waiter brings it to you.
If the waiter brings you the chowder complete in one bowl, you get the chowder then and there. That is HN and most websites from ye olde Web 1.0 days.
Waiter: "Your clam chowder, sir."
You: "Thanks!"
If the waiter brings you the chowder piece by piece, spoon by spoon on the other hand...
Waiter: "Your bowl, sir."
You: "I thought I ordered clam chowder?"
Waiter: "Your clam, sir."
You: "Uhh--"
Waiter: "Another clam, sir."
You: "What are yo--"
Waiter: "Some soup, sir."
You: "..."
Waiter: "An onion, sir."
Waiter: "Another clam, sir."
Waiter: "A potato, sir."
Waiter: "Another onion, sir."
Waiter: "Some more soup, sir."
<An eternity later...>
Waiter: "And your spoon, sir."
You: "What the fuck."
The bigger issue is that a lot of requests are dependent on previous requests so all the latency adds up. E.g. when you fetch Reddit, it only downloads the JavaScript and then fetches the post content. The move away from server rendering of webpages has really made things slower.
The initial TCP window is usually around 4KB so you have the handshake SYN RT (+ another two for TLS) followed by at most 4KB of request data. Cookies, auth headers etc. can make that a relatively low number of requests before you need to wait for the next RT. You also have the TCP window on the responses and again headers for large numbers of requests eat into that.
And then as you say dependencies - load the HTML to load the CSS and JS to Auth the user to make the API calls (sometimes with their own dependency chains)...
This is partially true. HTTP/2 is capable of issuing multiple requests on a single connection and retrieving multiple responses in a single packet (assuming it fits of course). So while you probably won't get the same benefit as you would with multiple parallel connections, the overhead is likely to be much less than just a simple multiple of the latency. This is especially true if you have a large number of small assets.
> HTTP/2 reduces the problem with multiplexing but it still doesn't go away
Even multiplexed you hit initial TCP window limits + all the response header overhead.
And many cases you have dependencies - need the first HTML content to issue the JS request to issue the Auth request to issue the API call to issue the dependent API call...