
The Performance Cost of Server Side Rendered React on Node.js - dberhane
https://malloc.fi/performance-cost-of-server-side-rendered-react-node-js
======
laurencerowe
It would be worth verifying that NODE_ENV=production is set during these
benchmarks (I see no mention in either the article or the source repository.)
Running in development mode has a significant performance impact.

Edit: This PR shows that is likely to be the case. Running in production mode
along with some other minor changes shows React running at 88% the speed of
Pug. [https://github.com/janit/node-templating-
benchmark/pull/1](https://github.com/janit/node-templating-benchmark/pull/1)

~~~
anatoli
NODE_ENV=production definitely wasn't set based on some basic testing I did
(that's my PR there), which accounts for the atrocious performance.

Also, the React component is created on each request instead of just being
created once. Unless one's goal is to specifically colour the perception of
React, I don't understand that decision alongside the code for Pug which
precompiles the template a single time.

(Also declaring the map function outside the component and not creating a
closure makes for a tiny extra boost.)

It's disappointing to see that the article took off to this extent given how
misleading and inaccurate it is.

~~~
velmu
Hello Anatoli. Thank you for this. It was clearly an oversight on my part, not
intentional to make React look bad. I have added a note on the article and
will run the tests with the correct settings and your PR with further
optimisation.

~~~
jeswin
Then you should withdraw the article until you get real numbers. This utterly
misleading data (as of now) is going to be picked up by search engines and
other blogs.

Add: on a positive note, this aspect of React is worth benchmarking. Thank you
for that.

------
MatthewPhillips
Note that React doesn't do streaming HTML in the traditional sense. You can't
send out initial HTML while you wait on an API response, for example. Instead
only the serializer is streaming. Your app renders to a VNode synchronously
and then the serializer traverses the VNode, synchronously, and streams the
strings out as it goes. This is why streaming is only a little bit better in
these numbers.

If/when React gets real streaming it will compete better with traditional
templating engines, most of which _don 't_ do streaming. If you use Node.js
and want a traditional templating engine that uses JS template literals and
_does_ do streaming, check out my project:
[https://github.com/matthewp/flora](https://github.com/matthewp/flora)

~~~
bringking
React 16 supports streaming!
[https://reactjs.org/blog/2017/09/26/react-v16.0.html#better-...](https://reactjs.org/blog/2017/09/26/react-v16.0.html#better-
server-side-rendering)

~~~
bringking
Oh sorry I should read better! Sorry

------
kraftman
I thought the idea was to render it server side just once, then have the
client take over for all future requests? In which case it's 10x slow for the
first page load, but all future requests are just hitting API endpoints and
can be fairly fast?

~~~
_greim_
Correct, except that initial app load time for _un-cookied users with cold
browser caches_ is a critical metric, since it largely determines whether
they'll keep using your app. Fortunately, those are the scenarios where
server-side caching can have a decent benefit, since you're essentially
rendering the same view to all such users.

~~~
Swizec
If all first time users see the same thing, why not serve a precompiled static
html that then gets taken over by React.

Isn’t that the whole point?

~~~
abritinthebay
> If all first time users see the same thing

That's a big, important, if.

~~~
_greim_
It's good odds, though. Obviously depending on the app, these would be non-
logged-in users who don't get personalized content.

~~~
abritinthebay
There can be (though you’d be surprised- there’s rarely such a thing as a non-
identifiable user nowadays). Just pointing out it’s a very big conditional.

For example: I work at Bleacher Report and our non-logged-in users get data
_customized for them_. That can be as simple as GeoIP data or can be us
knowing (and inferring) what articles or content they’ve seen.

The permutations are quite large.

Now some blog, or a very simple app won’t do something like that (hell, they
might not have the depth of content to be able to) but it’s reasonbly common
to have that requirement on large sites outside of v.limited pages (like
login/signup etc)

------
mottomotto
If you're on the fence about server-side rendering being needed for your
webapp don't do it! As a team of two with one engineer, I decided YAGNI and
instead focused on the responsiveness of the React and Redux-based web
application. It is very snappy to load and our (paying) customers are happy.

You might not need SSR. I don't. I expect there will be more work to optimize
it and eventually it'll be obviously a good idea. But you don't need it right
now if you're working on a SaaS product.

~~~
timr
It's the other way around: you might not (in fact, probably do not) need
React.

Start with server-side rendering, and move to React if your application's
dynamism demands it. Most applications don't. It's painful to see so many
websites building huge, slow, JS-rendered monsters for one or two dynamic
elements per page. Unless you are Facebook (and have your JS cached within one
hop of every internet POP in the world), server-side rendering is going to
yield performance wins for nearly all visitors.

~~~
lmm
The difference there is that it's very hard to port a non-JS server-side-
rendered site to React if it turns out you do need to. Whereas it's relatively
easy to port a React application to additionally do server-side rendering.

~~~
always_good
The other difference is that I'm having a hard time imagining a scenario where
someone is getting blindsided by "damn, it should've been a SPA after all!"

I'd be wary of all of the technical decisions that were made if the decision-
maker knew so little about the requirements that they couldn't even call that
shot correctly.

Of course, on a long enough time scale, all bets are off.

~~~
lmm
> The other difference is that I'm having a hard time imagining a scenario
> where someone is getting blindsided by "damn, it should've been a SPA after
> all!"

I've seen it happen more than once. Start with what looks like a
straightforward form flow, seems like it should be fine in rails or whatever.
Then it turns out some fields depend on other fields, so you've got to
dynamically show/hide parts when other parts are set. And doing a server
roundtrip, even AJAXey, is just too slow. So you put a bit of client-side
Javascript to handle that. And then as the product evolves the interrelations
between the form elements become more and more complex and businessey, and you
have more and more logic split or duplicated between backend and frontend.

------
qaq
OK from single core $10 VPS you will be able to serve 1,080,000 unique initial
loads per hour and depending on your pattern of traffic say 15,000,000 in a
day could you remind me what the issue is again?

~~~
dustingetz
It's a real issue, my startup's react SSR times are ~5 seconds of blocking the
node thread on my $40 droplet. It's about 50% react. Our pages are way more
sophisticated than your average page with like a query and a form, though.
There are many ways to make faster though, one of which is to simply configure
your CDN properly.

~~~
batmansmk
50% on plain rendering is very unexpected. Complex apps tend to have
bottlenecks on IO/data fetching more than just plain rendering.

I would love an article detailing your experience and measurements.

~~~
dustingetz
our renders are compute heavy because it is so dynamic (most of today's apps
aren't). Think of it this way. Virtual DOM is like a spreadsheet. You only
want to recompute the stuff downstream of what's changed. That's basically
what React's render-tree pruning is all about. But there are a couple
problems.

First, naive server rendered React isn't diffing anything at all, it
recomputes the whole virtual dom from scratch every time. So there isn't any
render-tree pruning since we're not doing a diff. That's what the walmart-labs
patches help with, since obviously a lot of SSR'ed components aren't changing
between SSRs, that can be reused, and we can be fast again.

Second, React.js views are more than just banging html together, it also has
to call a bunch of functions as part of that process. For example client side
sorting, or map/reduce/filter. So if those functions are expensive, your views
are slow, even if the html itself isn't all that complex.

Third, since much of our "view rendering" cost is actually computation not
directly related to making html, React can't reuse pieces of those
computations anyway. You'd need to code your "math" in terms of React
components even though there's no html associated with them.

Fourth, if your dataflows are dynamic enough, like a spreadsheet, even if
React actually could optimize those computations (which it cant), it wouldn't
even be helpful, because while Views are trees, many computations are not. For
example, in a spreadsheet: Edit cell C7, quick what is the tree of
computations you can skip? The question doesn't even make sense, because
spreadsheets aren't trees. Spreadsheets do complicated graph dependency
tracking, with cycle analysis and all that, in order to make this fast. React
doesn't do that.

As an example of how dynamic Hyperfiddle is, go to
[http://dustingetzcom2.hyperfiddle.net/](http://dustingetzcom2.hyperfiddle.net/)
and in the top toolbar click "data" and then click "dev". Dustingetzcom2 is
not coded in javascript, its coded in data, and you edit the data live in the
dev pane, and the right things update live as you make changes. That's why the
render computation is so expensive.

~~~
batmansmk
No need to try to explain why you think it is slow based on your understanding
of React.

Facts are always better. Good news is: I suspect you have no React issue. How
is your NODE_ENV? Set to production? After looking at your website, you have
no perf issue caused by React itself! Remove the blinking cursor made with
setInterval, and use CSS animation for that. You'll see :)

~~~
dustingetz
Here are the walmart labs patches I mentioned:
[https://github.com/walmartlabs/react-ssr-
optimization](https://github.com/walmartlabs/react-ssr-optimization)

That link has the data points you are looking for, react SSR problems are well
understood and documented and have been for some time now, that repo is a year
old

also see
[https://www.reddit.com/r/reactssr/](https://www.reddit.com/r/reactssr/)

------
kennu
Isn't the whole point of server-side rendered React to render it just once,
when building the website, and then serve it as static HTML/JS/CSS assets
using a static webserver?

~~~
andrewwharton
That's one use case.

The other is for performance/seo where the initial markup for the app is
generated and served to the client as HTML, then the React app bootstraps on
the client and takes over managing the DOM.

~~~
richdougherty
This is what I'd like to see a performance comparison for.

1\. Initial load using client-side vs server-side rendered. (Server-side
should be faster.)

2\. Warm load using client-side vs server-side rendered. (Client-side should
be faster.)

Comparing to static is interesting, but not really an option if you're serving
dynamic content.

------
ec109685
The author mistakes increasing concurrent request as being some sort of proxy
for concurrent users. Once you have maximized rps or maximized cpu, increasing
concurrent just artificially hurts your average latency.

Max RPS equals number of users you can serve per second. Concurrency should
just be tweaked until you eliminate the network latency of your test
framework.

------
makmanalp
So I thought the entire point of SSR was to speed up initial page load - if
you pre-render all the static parts of the page, including what happens after
routing, but before any API calls, so that your webserver can cache that
static HTML for that route and serve it up. What this means is that the user
immediately sees much more of a rendered HTML page, rather than seeing a blank
page for 2 seconds and having to wait to download and run a massive JS bundle
first.

~~~
k__
No, it was to improve SEO and other clients without JS

~~~
dntrkv
That's part of it, but getting viewable content to the user as soon as
possible is just as big. Without SSR, the user has to wait for the JS to
download, parse, and execute before they see anything on the page.

------
olegkikin
Related:

Node.js templating engine benchmarks:

[https://github.com/marko-js/templating-benchmarks](https://github.com/marko-
js/templating-benchmarks)

Framework SSR benchmark (Vue, React, Preact, Rax, Marko):

[https://hackernoon.com/server-side-rendering-shootout-
with-m...](https://hackernoon.com/server-side-rendering-shootout-with-marko-
preact-rax-react-and-vue-25e1ae17800f)

------
STRML
For this reason, we don't use SSR for pages that change often, and turn it off
completely for authenticated users. Some performance analysis showed it was
actually _slower_ in the average case than shipping the JS and having the
client render it themselves, especially with a hot cache.

This still provides what we wanted anyway; the ability for pages to easily be
viewable without JS and easily indexed by search engines.

------
jondubois
These results are not surprising to me but it's great that they were published
because people have been very quick to jump on the hype bandwagon for these
kinds of projects.

I think that the idea of the isomorphic JavaScript app never actually worked
in practice. MeteorJS already had a really good go at it.

Sharing JavaScript modules between the client and the server is great but when
you start to pretend that the backend and the frontend are the same
environment, you're bound to run into all sorts of problems and create
inefficiencies.

The kind of performance penalty incurred by server-rendered React is bound to
make your system vulnerable to DoS attacks or at best make it unnecessarily
expensive to operate.

When programming abstractions shift too far away from the reality of the
underlying architecture, you will start paying dearly and the cost won't be
worth it in the long run.

In my opinion, the client-server divide is a fundamental aspect of multiuser
applications and no amount of abstraction can make it go away.

------
dustingetz
[https://github.com/walmartlabs/react-ssr-
optimization](https://github.com/walmartlabs/react-ssr-optimization)

React definitely needs work here and i think the right people are quite aware
of the issues.

------
styfle
Could the performance be improved if you call React.createFactory() and then
stream the factory call like this[0] code?

[0]: [https://github.com/styfle/react-server-example-
tsx/blob/4586...](https://github.com/styfle/react-server-example-
tsx/blob/4586f3431b4bfd424179da3aad2c6adbddb0de10/src/server.tsx#L12)

------
tabeth
I'd be curious to know if this cost is similar with Ember's implementation of
SSR, Fastboot.

------
yoshuaw
If you're looking for DOM in the front, Strings in the back, check out Bel –
[https://github.com/shama/bel](https://github.com/shama/bel)

------
justin808
Did anybody try
[https://github.com/shakacode/react_on_rails](https://github.com/shakacode/react_on_rails)
for this comparison?

------
merb
well wasn't that clear? Isn't something like a Template Engine quite CPU
intensive? Things were node.js performce poorly?

~~~
ralusek
They include a comparison with using ES6 Template literals (native JavaScript
string interpolation), and saw 10x increase over React. The native string
interpolation actually came out just behind serving straight static files.
JavaScript CPU performance is actually quite high among interpreted languages.

So to answer your question more specifically, no I don't think it is clear.
What this is specifically isolating as the performance problem is that using
React as a template renderer on the backend is not performant, not because of
NodeJS performance, but because React is realistically not really specialized
for this.

~~~
foota
Does anyone actually use vanilla JavaScript string literals for all their
backend rendering?

~~~
mstade
I do this. It’s surprisingly effective, both in actual performance and
developer productivity. The one area where it is useless however is for
mutating DOM. It’s easy enough to create DOM using template strings, but if
you say need to render something and then mutate it later on in you render
function, it becomes terrible since you either have to do tricky string
manipulation, actually create the DOM and use methods to mutate it, or use
some framework at which point you may as well just give up on template strings
altogether.

But for any case where you’re basically going in the one direction, i.e. data
-> DOM and not changing things once rendered it’s great.

~~~
reificator
I have no personal experience with it, but Google's been toying with this
project: [https://github.com/PolymerLabs/lit-
html](https://github.com/PolymerLabs/lit-html)

Might help with some of those pain points

------
arkh
I'd like to see the results with php through node-phpcgi to render those
pages. So we'd have gone full circle at last.

------
opvasger
My clients get to render their own html - it's not really that bad if you
bundle, split and cache the code.

------
pteredactyl
Been using template literals for basic apps instead of the usual overhead.
Mostly loving it.

------
miohtama
Does React build any cached or compiled intermediates for its templates?

------
simplify
I'd love to see a side-by-side comparison to Marko.js
([https://markojs.com/](https://markojs.com/)). It has a lot of react-like
features, but was built from the ground up for SSR speed.

~~~
mattrick
Marko keeps some bechmarks here: [https://github.com/marko-js/isomorphic-ui-
benchmarks](https://github.com/marko-js/isomorphic-ui-benchmarks)

Not too sure how accurate or biased they are, though.

------
k__
Render on write, not on read.

