Hacker News new | past | comments | ask | show | jobs | submit login
Scaling React Server Side Rendering (arkwright.github.io)
551 points by boernard 23 days ago | hide | past | web | favorite | 131 comments



This is a fantastic article. I was daunted when I saw the length, but everything about it - the prose style, the mixture of drawings, the pace - led me through and out with a whole load of valuable lessons about load balancing, caching, isomorphic rendering and more. Thank you!

One question for the author, if they're reading: how did you prep for writing this piece and gather the story details? It's quite a journey, and - if I was writing something like this - I would have a hard time keeping track of all the different twists, stats and lessons as they happen so they can be written up later. Do you keep a notebook, or did you rebuild the story from artifacts?


Thanks for the kind words! You made my day.

I didn't really do any preparation. I had recently been thinking that pretty much every website I had ever built was sadly no longer in existence, and so I wanted to start producing real, "tangible" artifacts from my work; something that might have a shelf life of more than a couple of years. I had those recent SSR adventures in mind, and wanted to write them down before they faded from memory.

I usually begin with a bulleted todo list of insights or topics I want to cover. Then I dive in, and write in a more or less stream of consciousness fashion, which causes me to think of more topics to add to that list. I comb over the article and the list iteratively, reordering stories, editing, and and adding context as I go, until the result feels right. In this case I didn't have any notes, the content was rebuilt from memory.


Great post! What tool(s) did you use for the drawings?


The response, much further down: https://news.ycombinator.com/item?id=21916700


Curious to know this as well. The drawings, diagrams, and flow charts are an absolute delight!


I also want to compliment the mobile view. These days I almost always use the mobile safari reader view which unifies the design on websites and increased readability. This doesn’t work on your site, but it doesn’t have to, because the design is actually pretty similar and easily readable.

So thanks for this great article.


Totally agree. If I had to criticize anything, I'd say the title is too specific to react. As a dev who avoids react/js/node as much as I can, I still found lots of very interesting and informative stuff in there. Probably helps that it was very well written.

If for some reason you're a bitter backend dev who doesn't use react, but are reading this comment... Do yourself a favor and read the article. Really good stuff about load balancing, keeping an eye response times by percentile, etc.


An amusing tidbit about optimization, toward the end:

> Because we were seeking to improve performance, we became interested in benchmarking upgrades to major dependencies. While your mileage may vary, upgrading from Node 4 to Node 6 decreased our response times by about 20%. Upgrading from Node 6 to Node 8 brought a 30% improvement. Finally, upgrading from React 15 to 16 yielded a 25% improvement. The cumulative effect of these upgrades is to more than double our performance, and therefore our service capacity.

Free optimization, ripe for the taking!


Depending on the code and the dependencies it might not be that free if you run on some unexpected problems after those upgrades due to some incompatibilities or even reliance on now-fixed bug. While I understand that the latter should not be the case given the code is written properly but with sufficiently large projects this is not as uncommon as we would like it to be.

Also there is a more common scenario where updating one thing requires updating other packages and through a long chain of denependencies one of the pieces being updated has something missing in the new version (that was available in the previous version) and anything that relies on that will stop working.

Anyway, even the best case scenario where everything is perfectly fine after the updates still requires detailed testing to ensure that really everything is as OK as it seems. So even then this is not totally free

But then of course, it may still be the easiest path for improving the performance.


> easiest path for improving the performance.

upgrading should not be seen as an alternative to performance engineering though. Even if upgrading _does_ bring in some performance improvements.

Upgrading should be because of reasons such as security updates, and bug-fixes, and to continue to reap the improvements/features in the next version.


No, not an alternative, but I'd argue that considering new versions should be a part of performance engineering. If one of the "improvements/features in the next version" is more optimized and performant code, you want to "reap" it (after of course considering and testing the upgrade from other points of view.


I developed the JS architecture of https://www.productreview.com.au and we have faced tons of issues getting SSR right, but it was worth it. We're getting more than 10M pageviews a month and I'd like to share our experience:

* Upgrading NodeJS indeed gives us massive performance boosts, but apply with caution. Ideally have a set of visual regression tests just to be safe

* Profile your NodeJS code just like you do with browser code. Sometimes the bottleneck could be in an Express middleware or in reading a massive Webpack manifest file

* If a component doesn't need to rendered on the server, don't do it. Don't waste CPU cycles (for ex, out-of-view content). Just make sure you got your SEO meta tags right

* Don't load more data than you need. It takes time to parse, it takes time to loop through and it takes time to stringify for rehydration

* Enable BabelJS's debug mode and remove unnecessary plugins

* Don't import more stuff than you need. Tree-shaking is important on the server-side too

* If you're using CSS modules, use the Webpack loader `css-loader/locals` on the server so that it doesn't emit CSS files (useless). The client compiler should do so

* Monitor your server-to-server requests. They're usually what take the longest, so cache the most important ones

* As with the majority of websites, cache is king

* Properly serialize your JSON strings. That's what we use: https://gist.github.com/eliseumds/6192135660267e2c64180a8a9c...

* It can be worth it to return a dangerous HTML string from a component instead of a tree of React nodes. We do that when we render SVGs and microdata tags

Again, it's a pain-in-the-butt. You'll have checksum errors, need to synchronize clock, polyfill Intl APIs because they're inconsistent and so on.


> * Properly serialize your JSON strings. That's what we use: https://gist.github.com/eliseumds/6192135660267e2c64180a8a9c....

That doesn’t look like “properly”. The double escaping is overcomplicated and no safer compared to a direct

  window.__productreview_data = ${escapedReduxStateJsonString};
(and forgets about \v, maybe others), the transformation doesn’t preserve “</_escaped_script”, and it doesn’t address a vulnerability involving <!-- that’s contrivable.

Closer to correct:

  JSON.stringify(data)
    .replace(/\u2028/g, '\\u2028')
    .replace(/\u2029/g, '\\u2029')
    .replace(/</g, '\\x3c')
Better, if you put the JSON in an inert <script> (type="application/json"), it’s only necessary to escape < (or /<[/!]/g). This is a good idea so you can use restrictive CSPs.


Thanks for pointing out the `<!--` vulnerability. In regards to rendering the string inside a JSON.parse, we do that because of performance: https://v8.dev/blog/cost-of-javascript-2019. From what I remember, we had some issues with IE11, thus the replacement for the other characters.

We'll consider "application/json", makes sense.


Given a correct function that converts a JSON-representable value to embed-safe JSON, you can use it on the JSON to get your JSON.parse performance:

  const inlineJSON = data =>
    JSON.stringify(data)
      .replace(/\u2028/g, '\\u2028')
      .replace(/\u2029/g, '\\u2029')
      .replace(/</g, '\\x3c');
with:

  const escapedReduxStateJsonString = inlineJSON(JSON.stringify(data));
But yeah, the isolated <script> thing is usually even better (more compact in addition to the security benefit).


I've been recommended this library rather than figuring it for myself:

https://github.com/yahoo/serialize-javascript

You can use it in JSON mode like this: `serialize(obj, {isJSON: true});`


This looks like a great piece of article. Kudos to the people who wrote it because the most sure-shot problem in SSR is running to scaling issues and this is a much needed one.

But here's an unpopular opinion: Server side rendering shouldn't even be a thing. Running a language as dynamic as Javascript on servers, is at best - a problem that can be dealt with, but not necessarily the solution.

I'm saying this as a full-time Javascript developer. We can do better than mandating JS on the servers.

#1. SPA, Components and functional programming is the best thing that happened to web development in the recent past. So, let's stick with it.

#2. But we are stuck with Javascript to embrace these otherwise abstract engineering methods, because browsers are stuck with JS.

#3. Webassembly is here. So why not a UI-framework, that embraces components, SPAs and functional programming but with a better language (something like Elm). A language that compiles to webassembly for browsers to run logic & build UI and runs natively on servers? This hypothetical system should compile to HTML on the servers and support smooth progressive hydration.

Running a bunch of JS on the servers, on a piece so critical like rendering HTML will always be a suboptimal solution. Imagine saving all that server-scaling costs with a much server-cost-friendly language like Rust or Swift?


Rather than running JS on the server, one way I found things to be effective is using any server side templating capability to basically "bootstrap" what the JS might typically do on start-up. So the server renders you a page that looks well formed and then the JS hooks into it for client interactions.

The issue that can be runned into here is possibly duplicating efforts on the server and JS side. You can keep them separate enough but it's tough if you're used to creating everything either through the server or through JS on the client.

The last web app I worked on like this is unfortunately not public but performed rather well and wasn't all that difficult to maintain either.


This is called “progressive enhancement”, and is, in fact, the way that you’re supposed to write webpages: they should come pre-rendered, and only if the client has JS enabled do you enable more dynamic features. You fall back to full pageloads to handle form submissions with not-very-clever coding, and can usually preserve most of the functionality of a webapp even without JS.

Until about 5-6 years ago, this was the way people tried to write webapps (they didn’t always get there, but at least they aimed for it). It’s really startling that it’s something that people have already forgotten.

It really isn’t terribly difficult to do this. Frameworks like Rails make it pretty easy to do, out of the box.


> This is called “graceful degradation”,

Isn't that strategy "progressive enhancement"?

https://www.w3.org/wiki/Graceful_degradation_versus_progress...


Yes, it is. I corrected my post around the same time you replied. I’ve colloquially used the term I originally cited, but the correct term is progressive enhancement.


I thought the selling point of React SSR was caching and pre-rendering into static HTML? How is it being used that causes it to become a performance bottleneck?


The whole point of SSR is SEO and increased performance.


Wasm isn't capable of manipulating the DOM yet.


Yes, but it can send commands to construct and manipulate DOM. See - https://github.com/yewstack/yew


> SPA, Components and functional programming is the best thing that happened to web development in the recent past.

Citation needed.


There was no mention of why server-side rendering was needed at all. Based on my companies research Google and Bing do just fine without it.

> when the server is under high load, skip the server-side render, and force the browser to perform the initial render.

With this type of contingency plan, I don't see any reason to use server-side rendering at all. We build all of our sites and apps with React and don't do it at all, for any reason at this time.

Is there something we're missing?


Try using client-side rendered website on a cheap Android phone on a 3G connection sometime - or even a cheap Android phone on LTE.

That's how most of the world's population will experience your site.

I'm baffled that so many sites have invested in multiple megabytes of JavaScript to render their pages. It's like our entire industry has forgotten how to build sites that can be used by anyone who's not on an LTE iPhone.


>That's how most of the world's population will experience your site.

Unless you're FAANG you probably don't care about most of the world's population, but the tiny slice that is most likely to see your site and generate revenue for you.

It's not baffling that most developers don't make things for most people. That's just a waste of time and money.


> Unless you're FAANG you probably don't care about most of the world's population, but the tiny slice that is most likely to see your site and generate revenue for you.

That is simply not true for pretty much most companies in existence. Also those users will still be mostly using phones that are significantly slower than iPhone.

> It's not baffling that most developers don't make things for most people. That's just a waste of time and money.

Empirical proof shows that most web developers make things for other web developers (optimizing for 4000$ MacBooks and 1000$ iPhones while forgetting that poor LTE signal areas exist) instead of making it for targeting your actual companies user reach.

(Remember, people who don't return on your slow loading JS bloated web site do not appear on simple dumb Google Analytics.)


What people fail to consider is even with the high end smartphones, how do we know you are guranteed full speed 4G all the time? The network speed varies greatly in various places (in the subway, through the tunnels etc) and more often that not, we have sucky network speeds even though we are on "4G"


I suppose it depends on how much you want to put effort into optimizing a single app for all users, rather than having a lite version of the site.

For my company, we serve images and video. Even multi megabyte JavaScript only equals one minute of a 720p videos runtime.

For people on restricted connections, we have a lite versions with less than 100k JavaScript and videos transcoded to 260p.


it's not, because phone coverage can be spotty when in a car using the train, busy wifi connections etc. aren't we supposed to be optimizing for mobile? SPAs should only be used for things like gmail. Newspapers offering megabytes of javascript , when they know that a lot of their reading happens in trains is obscene.

On the one hand google promotes SPA frameworks. on the other hand , google makes AMP because they are too slow. It's not stated often enough, but modern web is schizophrenic


Facebook decided to tackle this by having traffic shaping one day a week. On that day of the week, traffic shaping means the developers get to experience the Facebook site as if they were on high latency / low bandwidth connection.


This doesn't exist anymore. :( I just tried to see if I could opt into it, but it caused issues so we have to do it on a machine by machine basis.


Not exactly the same, but there's a throttling feature in Chrome dev tools.


Recently, due to a router placement issue I used up all my mobile data so my connection was slowed down to 16kbps.

Most sites/apps don't work at all at this speed with the notable exception of Hacker News and Facebook Messenger.


Our industry didn't forget, a generation of developers have come up who never developed web pages that way at all.


Thankfully most of our Web projects keep being SSR in Java and .NET stacks, with minimal JavaScript.


One of the main reasons I never bothered to learn React was that using Razor syntax in .cshtml on my ASP .NET projects has all the convenience of react with no client-side overhead. However I understand there is some benefit to delegating some of the work to clients considering the power of modern devices.


Whether Razor or Server Side React, it's the same thing. Both are compiled and produced on the server and sent to the client. No difference except that react is a much better template engine.


You had me until that last part.

Why is React superior to Razor Templates (or for that matter, Razor pages) for server side templating?

I support the assertion that it’s a good paradigm for creating consumable SSR templates I just don’t see anything that empirically cuts it above Razor other than if you know JSX/JavaScript it’s obviously more natural but I’m assuming that’s taken for account here


React is component based, this allows for a level of encapsulation and reusability that cannot be achieved with Razor. You might not truly appreciate this until you try out React.

If you don't want to dig too deep though, compare how to add a datepicker to a view in both systems. With React it is just another component, with Razor it is a bit more finicky.


On a bad connection, I'd pick a react SPA with a slow upstart over a classic server-side rendered one any day all things being equal.

With the react app there's a chance I'll have the js already cached and I'm only fetching the data. With the SSR I'll probably be fetching data and view continuously.


Nonsense. You haven’t experienced a slow connection recently, or you’re just loading the same sites over and over again.

People keep asserting it’s impossible to do all of the stuff we were doing on a daily basis in 2012, without the magic of megabytes of client-side code.


> or you’re just loading the same sites over and over again.

That's right, I am loading the same sites again every day consuming different data. Both for work and fun. That was the whole point.


[flagged]


Please don't be a jerk in comments here. I'm sure you can make your substantive points without that.

https://news.ycombinator.com/newsguidelines.html


Hopefully your site renders just fine without needing to throw servers at it. Unless you're getting into fancy webapp territory, just drop your page complexity.


> Try using client-side rendered website on a cheap Android phone...

Just not true. We test all your react sites with $100 Android phones. They render them all fine and fast.


Client side rendering sucks for anyone on high latency networks (a significant potential customer base). From an SEO perspective it leaves you subject to whenever the search engine has rendering capacity available.

There's some good write up here: https://medium.com/@benjburkholder/javascript-seo-server-sid...

Essentially content that requires client side rendering gets stuck waiting for Google to have resources available to render the content, which is likely scaled to keep the queue at what _they_ determine to be a reasonable length. That can mean a lag of up to days or weeks after the page was first touched before they have the content indexed and in SEO. Worse, search engines don't cache javascript the same way that your end users do. An end user returning to your site likely has all your javascript pre-cached ready to go. A search engine will be starting from scratch each time. That's a lot more requests hitting your server (vs server-side rendering) and so a slower page response time (which impacts SEO rank)

My first gut instinct to this hybrid SSR-CSR approach in the article was that it's nasty, but I really like the trade-off they hit. That's a really nice failure mode. Something happening on the client side is way better than nothing, even if that something is slow.

On a side note, server-side rendering is almost certainly more efficient overall. When running under a JIT environment, methods will have already been compiled down to native code. The code is likely already in memory and ready to execute so there is no parsing / compilation time involved (and you have the advantage of OS level caching, cpu caches etc on your side). The server CPU is almost certainly faster than the client's CPU and will be more likely to be in an awake state (there is measurable latency in a CPU waking from any of the sleep or lower power/frequency states). You're trading off once-per-server parse/compilation phase vs one-per-client-per-access.


As a plus to the SSR part, you’ll also likely have any API resources either already loaded out of the database, or very close in latency to the renderer, whereas on the client you could easily be 100ms of latency plus other overheads away.


The two main benefits of SSR are SEO and performance.

SEO: If you don't server-side render your website, your SEO will certainly suffer. Google says they execute your JS, and to some extent they do, but your ranking will be worse and fewer pages will be scraped. We experienced this first hand.

Perf: User will stare at a blank page while they wait for your JS to download and execute. On a slow connection this could be many seconds slower than SSR.


To expand on this, Google execute your JS, but in a slightly odd JS environment (I.e. randomness isn’t random, time might not be correct, etc). They give each site a CPU time budget, and your JS execution eats into this.

If you have 5 pages that take seconds to render you might be fine, if you have 100k pages that take 50ms to render you might be in trouble.

Some of the detail of this is exposed in the webmaster tools that Google provide.


I haven't seen anyone reply to you yet specifying social media and SEO.

For example, copying a route from your SPA and pasting it into Twitter, Facebook, Slack, Teams, etc. Preferably you want the copied page's title, description and icon and not just your SPA's generic info.


I find it ironic and explicatory of the incompentency level we have reached in IT today.

Technologies used outside of their context and "developers" having to deal with complex problems they should not even have in the first place.


Do people call returning HTML/CSS/images from a server "rendering" now? The browser still "renders" the HTML into a visible UI.


In this context, 'render' means transforming React (or similar framework) output into a DOM tree, plus the magic that goes into rehydrating dynamic elements once the static copy of the page is loaded.

The benefit of that is getting all the effects of 'just plain HTML' while still being able to dynamically update parts of the page, and doing the entire thing in a single framework such that (if you do it right) your code doesn't have to explicitly do anything to specially handle the two different use cases.


yes they call this rendering now but just in the context SSR vs SPA


> With this type of contingency plan, I don't see any reason to use server-side rendering at all

A case of vital context missing. Ever tried something like Next.js? It's a react framework for client + server-side rendering. What's really cool about it is that it does SSR for the initial view and the client-side react picks it up from there. It doesn't sound very impressive like that but where it blew my mind was when I realized that I could save URLs for some deeply nested view in a react site, enter that URL later and I'd get an instantaneous SSR'd view seamlessly.

I guess what I'm trying to say is that some tech is cool like that, not really a contingency plan but a plan.


In a previous job where we worked with extremely low margins and very un-invested users who would bounce if the load time was too low, we used react's server-side rendering without using any react in our pages just to get a faster page load.


SSR was eating up the RAM of our production server like 7.5 GB for Node and 0.5 for our Elixir backend. That was insane. We decided to shutdown SSR and check if anybody would notice. Apparently they didn't. However that webapp is a service that customers use from their computers in their office, not something mobile. We expect fast connections.


I'm fairly confident "SSR" itself wasn't the culprit. Maybe there was something in the way your developers were implementing the site within it, but just with pages as rendering targets? Doubtful!


If you have a small website google will render your JavaScript for every crawled page. For large websites only a sample of pages are fully rendered. Your seo will suffer.


SEO alone is a poor reason to use server side rendering. Services like https://prerender.io/ completely solve SEO for SPA's and can be as cheap as $0 per month. Compare that to dozens/hundreds of hours of engineering time spent setting up an SSR implementation; it is a no-brainer.


That service is like solving a problem with duct tape. The service could at any time disappear, introduce compatibility issues etc. It is a useful stopgap for some situations, but I would never want a high-traffic site with big uptime demands relying on a black box like this.

Especially when server-side rendering is so easy nowadays if you pick the correct stack.


This advice is badly out of date. Crawlers no longer respect the fragment meta tag, so unless the pretender is actually served, this service does nothing


Article mentions that server-side rendering was needed for SEO (Search engine optimization) and first-page load speed

(normally server-side rendering first-page loads faster because it is pre-rendered (and often cached) ).

>"... SEO was a very important consideration — we had full-time SEO consultants on staff — and we wanted to provide the best possible page load speed, so server-side rendering quickly became a requirement. ..."


In practice google does not really work with SPAs. This is why internet research can be dangerous.


>Is there something we're missing?

Yes: when the user's browser is under high load.


Thought it's known that SSR is better at SEO.


what do you do with the intial "loading" screen? I would prefer if my app didn't need that.


You are not missing anything. This is a reheated anti-pattern that was (poorly) conceived in the 90's, and managed to crawl back. Was a great way to break a monolithic app back then, and it's still a great way to break a modular, scalable, cloud based web app now.


The easiest way to do this is to not use javascript for everything in the first place and to actually generate html instead of expecting people to run your code, then having to do it yourself in a convoluted workaround when they won't or can't.


This is probably the worst possible takeaway from this article.

> not use javascript for everything

Using JS for frontend + backend has significant advantages. Your developers only need to know one language/codebase. You don't need to hire/maintain separate frontend/backend teams who need to figure out how to coordinate with each other.

> actually generate html

That's what server-side rendering does.

> expecting people to run your code

This is how the web works, for the vast majority of users and markets worth serving.

> having to do it yourself in a convoluted workaround

Running your frontend code on the backend to generate HTML is an elegant solution and extremely easy to implement. These days it works out of the box. Not sure where you got the idea it was a "convoluted workaround".


> Running your frontend code on the backend to generate HTML is an elegant solution and extremely easy to implement. These days it works out of the box. Not sure where you got the idea it was a "convoluted workaround".

Oh boy, this must be the overstatement of the decade. I've done SSR with a few frameworks now, including Meteor, Nest, and Next. Saying that "it works out of the box" is so disingenuous, it borders on fake news. Even ignoring the trillion edge cases involving authentication, cookies, localstorage, dynamic components, promises/futures, async components, and so on, it will take dozens of man-hours to get properly-rendered server output that works with server-side routing, hydration and looks good on Google's SEO crawlers.


From my experience, this is not the case, not for a long time now, at least.

With proper architecture, all you need is plain React (no frameworks) with `styled-components` for everything - i.e., server rendering, client rendering, and hydration.

The architecture I'm describing is a fully top down approach, with concerns separated into API, UI, and App components, where the route dictates the data to be fetched, which is passed as a `store` to the layout to be rendered, with each layout being a composition of the UI components, App components, and occasional API components. I've had a lot of success with this approach and it is, in my opinion, the simplest way, incredibly easy to follow and maintain, and extremely minimal for what is actually being achieved.

With this architecture, switching between server-only rendering and client-only rendering and/or some combination of both becomes a matter of minutes, or even just seconds if you have some env var switches in place.


This hasn't been my experience. How does SSR not work out of the box with NextJS?


Simple example: I worked on an app where I wanted to use the @elastic/eui UI framework[1]. That framework (a fairly popular/vetted one) used some DOM manipulations somewhere deep for some thing or other and Next broke with some bizarre error. Had to find this snippet in someone's reported (Gatsby) issue and stick it in my next.config.js file:

      /**
       * We have to force the bundling of @elastic/eui and react-ace
       * as Gatsby, then all loaders below will match and avoid HTMLElement issues
       */
      config.externals = config.externals.map(fn => {
        return (context, request, callback) => {
          if (request.indexOf('@elastic/eui') > -1 || request.indexOf('react-ace') > -1) {
            return callback();
          }

          return fn(context, request, callback);
        };
      });

      config.module.rules.push(
        {
          test: /react-ace/,
          use: 'null-loader',
        },
      );
And this was just my first SSR issue. Definitely not "out of the box."

[1] https://github.com/elastic/eui


> You don't need to hire/maintain separate frontend/backend teams who need to figure out how to coordinate with each other.

In my experience the language is very little of what makes a frontend / backend developer, and you still need separate teams.


Seriously! I find backend work unintuitive and colorless. Others on my “full-stack” team have about as much love for the frontend as I do the backend.


   > Your developers only need to know one language/codebase
As one wise man said: make it easier for the users, harder for a database. The same applies to developers. We tend to forget who we make products for.


This holds true until about a year into the project and everything is complicated beyond the desire of your developers and productivity has tanked.


OP answers this in the article:

> Isomorphic rendering is a huge simplicity booster for developers, who for too long have been forced to maintain split templates and logic for both client- and server-side rendering contexts. It also enables a dramatic reduction in server resource consumption, by offloading re-renders onto the web browser. The first page of a user’s browsing session can be rendered server-side, providing a first-render performance boost along with basic SEO. All subsequent page views may then fetch their data from JSON endpoints, rendering exclusively within the browser


> dramatic reduction in server resource consumption

What is the the measurement of dramatic reduction?


Aren't you back to the scaling problem when you generate the HTML on your server? As in, you now have frontend code to run on your server, which is more or less the same as SSR.

Addendum: That is, if your loads are large enough of course. Small projects will not have scaling problems either way.


One of the bigger problems these days is that people don't bother assessing whether their use case is actually complicated enough to actually need any of this stuff, they just want to use it for the sake of using it.

People are allergic to actually profiling anything. I think, some days, that it is intentional, because if they had real data, the stories they tell themselves to justify what they are doing would fall apart.


The issue here is nodejs, react ssr is costly, taking all you cpu time, blocking the event loop and slowing down your app. On a multi-threaded (async or sync) web server this wouldn't be a issue most of the time.

Some add additional nodejs servers specifically and only for SSR, increasing complexity.

airbnb created https://github.com/airbnb/hypernova to solve exactly this problem.


That's exactly the point he's making. If you use a frontend js framework and you have to do SSR, then there's no longer any reason to use a JS frontend framework, because the reason they were made was to offload to clients the rendering, at the price of more complexity.

Back then, the cloud was not a thing yet, there was no Docker, no Kubernetes, no nice APIs to start instances, so it made sense to offload some computation to clients. Today, no longer.


What... the reason for JS frontend frameworks is to be able to dynamically update data visible without a full page refresh/refetch


JS does add some capabilities around avoiding full page refresh/refetches. See e.g. Turbolinks or PJAX for libraries that help with that.

Frontend frameworks do a hell of a lot more than that, though. Much of the time, they're doing things that are either the result of shortcomings in browser standards and implementations or things that are just stupid.


The reason React was made is because it's an easier way to reason about apps at scale, with many components and people working on them. As a result, there will be less errors and your team can move faster. It has nothing to do with offloading rendering to the client.


How is "reasoning" about apps at scale easier with React vs static or classic server-side HTML rendering where everything is addressed by a URL?


It isn't, but requirements rarely allow for static websites.


Amazon could be a server rendered web site and it is. AWS's and Google Cloud's consoles are borderline. Google Docs must be a client side app.

I worked on 3 projects this year, using Rails, Django and Phoenix. Only one of them has a front end that requires a client side app. The other two are within the bounds of server side rendering. That saves time and money to the customer.


Exactly. It is pretty amazing how developers went from server side rendering to SPAs just to get back to server side rendering few years later. The projects I have been working on never went down the SPA path so there is no need to do SSR in JS and doing it in other languages like Rust, Java or C++ gives you significantly higher base line performance (that you can optimise further).


Are you advocating for avoiding dynamically generated pages altogether? I agree that would be simpler but try winning that argument with the bizdev folks :)


I think he/she advocates for websites to be generated on the server (if they're dynamic) and not passing on that burden to the browser. Specially if the browser language was created to add drop-down menus and cool snow effects, not to generate a complete html website.


That's why I'm confused, that's exactly what this article is about. You're using React SSR to do the same thing you would do using RoR or JSP... unless the gripe is specifically with JavaScript as a programming language.


Interactive applications? Ever heard of them?


How many such web pages to you hit daily? I think the only one I seen in the last month was some solar system thing, and some work related stuff that are actually apps.

Most news sites are not apps and recently I am starting to turn off JavaScript on this websites and I am considering instead of blacklisting pages I maybe whitelist stuff if things get even worse, we ended up like in Flash days where you had an extension or a setting to Allow flash on this page.


Sluggish webpages. Ever heard of them?


tbh they are very hard to miss, and the reason is the loading spinners on every box.


Creating solutions for auto-created problems.


Indeed. The whole point of the web, as the name implies, was to have a simple portable HTML frontend, with a wide choice of backends that could be easily bolted on top of existing data and code bases. There never was a lack of native frontend frameworks for sophisticated interactive apps.


Nice article. It does a great job of explaining just how much unproductive work Google has created for developers. They created SPAs with Angular in 2010, but 10 years later, their search engine still can't properly index client-side-rendered SPA applications, forcing you to jump through all these hoops, to undo the benefits of client-side rendering, just to fix what they should be fixing with their search engine. It's truly insane. What a waste of time and energy. I hope they are working on a solution. I guess this represents an opportunity for a competitor to come in and do it better.

Following your journey through troubleshooting load balancing and caching brought back memories for me. I don't know what you're using for caching, but JSR-107 has been around for nearly 20 years. You might want to check out https://commons.apache.org/proper/commons-jcs/. I know it's not Javascript, but it will solve your caching problem in an orderly way. You shouldn't have to start from scratch on caching. You might even consider telling your content creators something like "updates to the site will only take effect the next day" so you can just invalidate the entire cache once a day and be done with it. Keep it simple.


This is from October 2017. It remains a fantastic explanation of load balancing and server-side React rendering performance techniques.


This is really well written, the illustrations help visualize the content, and the content itself is largely novel.

Really nicely done. Thanks for taking the time to write this — I enjoyed reading it!


Isn't the first half of this: "use haproxy" (eg: see [1]) (or any other real load balancer)?

I'm not sure if the second half is "... And squid or another caching web proxy" - but I'm open to the ssr pipeline being far enough from REST (the architectural pattern) that caching is broken, and something more application level, like redis/memcache or a custom cache is needed.

[1] https://www.haproxy.com/blog/four-examples-of-haproxy-rate-l...


How about just generate and serve static pages? Aren’t most apps on Netlify and serverless doing this now?


If you want an “app,” it makes sense to do a lot of stuff client side — especially if you’re trying to edit or create something in the app (I.e. Google Docs, Gutenberg editor in WordPress, Microsoft office online apps). If you want a landing page for your new startup, static pages on a serverless architecture make way more sense. It really depends what the use case is.

Probably more of the confusion lies in the line between those things — like a CRUD app or a dashboard or management tools. You could do them server-side for better initial performance, but you could also get better interactivity client-side.

I think a lot of new projects go towards the interactivity and “slick UI” side of things which is partially why we see more focus on client-side things these days. Speaking to myself (a full stack dev with a front end focus), we front end devs would really benefit from caring about performance and stability more.


People debate SPA vs SSR without any context. Both have their use case:

Everything before a login => SSR, everything after => SPA.

Why? SSR is proven to be much better at SEO. But SPAs offer best UIs. Nobody wants to click through stuttery SSR dashboards in 2019, wait for page loads, submits, etc. People prefer slick UIs, that was one of the reasons DigitalOcean got big (because of their then stunning dashboard or after-login-experience [1]) and hence every other hoster copied their interface.

[1] DigitalOcean's dashboard experience was for a long time the main teaser (as an animated gif/video) on their landing page.


> But SPAs offer best UIs

I don't consider this a settled conclusion. Mostly because it's a false dichotomy. There are alternatives to both SSR of component sites and SPAs, and every solution has its inherent advantages and disadvantages.

UI isn't even the primary feature of an SPA, that would be offline usage.

Having more interactive elements without page loads is a feature of "DHTML", and that can be had from anything between small embedded snippets of vanilla JS to full-fledged SPA frameworks. Intermediate solutions like StimulusJS or AlpineJS seem to be getting a bit more popular, too.

But in this Fallen World, it seems we're usually getting the worst of either extreme usually. Either a long rendering time for a whole page, then delivered in one "flash" (assuming you've got a fast connection), or multiple elements popping in and out while several JSON-RPC requests are made.

You can optimize both cases, of course. Proper caching/DB views etc., or things like HTTP2/GraphQl/React Suspense etc.

But it's definitely not an either/or answer. Few things in fiddling around with computers are.


Good point and I agree that the borders between SPA and SSR are blurry. However, I just wanted to stress that a debate without having requirements is useless, it's like saying a racing car is better than a truck. But for what? Building websites is not like building websites 20 years ago. There are many uses cases and saying one is better than the other rather shows that you never experienced the other side. I mean, there are still people out who never touched react, how should they fully grok what mighty system and ecosystem react has created and choosing another stack comes with much smaller or dying ecosystems.

To your points, I still think a proper SPA without any quirks such as DHTML, React Suspense, etc. gives the best UI for dashboard and logged-in kind of uses cases. However, having mixed environments is from a production and dev perspective subpar and hence you end up with setups like Next (SSR) with some Next pages having a stronger SPA notion (SPA within SSR).


Yeah, I never quite liked the currenly en vogue mixed approaches, where it's harder to draw a line, you often have to serve two masters and you feel it's mostly done that way because React developers don't want to learn anything else, despite cases where a complete server-side approach with an old-school template language might be a better fit, despite how hip functional reactive component based development is.

But about the smaller stacks other environments have: That's quite often because you either don't need additional parts (e.g. there's no desire for a huge Django template "community") and/or because other stacks are more full-fledged and thus the horizontal size is a lot smaller, with no need for umpteen state management solutions, state management solution helpers and state management solution application templates.

Dashboards could probably serve as a whole different topic. It was easy to beat the old school ones, where Perl CGIs roamed the prehistoric landscape. More modern CSS, and JS graphs alone beat the old rrdtool setups you often saw.


Re your second point: How is the SSR scene in Go land? Are there thriving ecosystems?

Besides, it took me a long to leave pug/stylus, I'am still not sure if a pug-based SSR is still the best way to get stuff out of the door. But again, opting for an 10 years old stack let you miss lot of things (eg maintainability of react code is top-notch).


I don't think I heard "SSR" in a context where it's just about backend HTML generation like we did in the olden days, only when it comes to reify JS views on the server. Haven't heard of an embedded JS interpreter or transpiler that does that.

When it comes to generating dynamic or static web page content, the pathological framework-aversion of the Go community strikes hard. Probably nothing that doesn't use the built-in Go templates with any sufficient user backing. This doesn't appear to be a language that can birth something like RoR.

As for the maintainability of react, I'm not so excited. It's a pretty decent templating system, and it seems easy enough to compose components, but beyond that it's each to their own, with some approaches being better than others. And redux still doesn't grab me as that great, it's just the sheer amount of developers resulted in a nice toolset. Whether it's frontend or backend, the twin async and dependency hells of JS don't manage to make me sleep any better, either.

I don't give a flying frick for age myself. Sure, there's less tooling for partials than for components, but that might have a reason.


Thank you for this very practical advice. The thought of trying to dynamically decide whether to render on the client or server side, as the author of this article did, seems so over-the-top complicated to me. Taking your advice is something doable for us mere mortals.


Great article.

Only thing that I thought was out of scope/unrealistic for most teams was having a 6 month time bomb on traffic and deciding to build a load balancer.


Incedibly nice writeup! I really like how the drawings help to better understand the concepts. A lot of time must have gone into crafting this article.


I think it would be a good idea to see this setup in a larger comparison like https://www.techempower.com/benchmarks. It is not my impression that node based apps did particulary well here in these tests. So if performance is important why not use something that has proven to be fast.


Great article, interesting read indeed.

> The cumulative effect of these upgrades is to more than double our performance, and therefore our service capacity.

That's a risky conclusion, in that it's likely over-generalised.

The upgrades may have improved the average performance, but they might introduce some performance impact on less well trodden paths, things that may strike at the least convenient time. There are performance gotchas that show their faces when load increases (system cache inefficiencies, etc. etc.) Some of the times I've been most hurt, operationally, have come when what looks great in generalised circumstances turns out to have a nastier under-load behaviour.

That said, always watch out for upgrades and make patching/upgrading a priority task. If there is a CVE attached to an upgrade, you want to be deploying that as fast as humanly possible. That means making sure there are as few human-involved steps as possible in your build/test/deployment chain.


Great article and interesting points about backpressure and load balancing. But not sure what to think about the end result where you have a cluster of machines transforming HTML and you still have to drop requests. A huge simplicity booster?


Awesome article! Out of curiosity, can you share the site this work was for?


Lovely, and informative. Style Reminds me of learn you a haskell, did you consider putting this out as a book?

Web app production optimization with this kind of style would be a godsend.


The Table of Contents should be first, then the content. I clicked Introduction expecting to link to something new, but it took me backwards.


Isn’t that a matter of style? It’s not uncommon for books to have the introduction preceding the table of contents. I’m literally looking at one right now that does this (publisher Wordsworth Classics).


Great read ! What did you use to draw the diagrams ?


Diagrams are drawn with a Pilot Fineliner (greatest pen ever) in an artist's sketchbook. I then take a photo with my phone, crop to size, and run the image through a two stage conversion process. First ImageMagick converts the image to grayscale and cranks up the contrast. Second, Potrace converts the grayscale bitmap to an SVG. This was my hack way of avoiding the purchase of a tablet.

One interesting consequence of this process is that every drawing needs to be perfect the first time. I didn't realize how much I lean on undo/redo until it wasn't there any more!


I can't get my diagrams to look a tenth as good as yours, even with a tablet and undo. Nice job!


Even my diagram sucks! Thanks for this amazing trick!!!


I have easy solution: don't use React!




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: