Why Universal Rendering / SSR is (almost) always a bad idea
You are having a great time using frameworks like React and Vue to build your app, but you have that one friend how keeps telling you about Universal Rendering and how it cured his depression, or you read this article once about how client-side rendering causes poor SEO. Now you are considering or have already started using a universal rendering framework like Next.js or Nuxt.js. But, whether or not you like it, Universal Rendering is (almost) always a bad idea, at least in our humble opinion.
Universal Rendering, sometimes called Isomorphic Rendering, is the process of running a client-side framework on the server to generate the initial HTML representation of the page, which is sent to the client, which analyzes the rendered DOM to build up state for the framework (rehydration), and proceed to render updates on the client side. Universal Rendering is also sometimes confusingly called "SSR" or server-side rendering, not to be mistaken with exclusively server-side frameworks which render static HTML on server side through string templating rather than running a client-side JavaScript framework on the server, with also no rehydration or subsequent client-side rendering steps. We'll just use the term "Universal Rendering" from here on to avoid any confusion, and to remind ourselves that rendering is done on both the client and the server, hence the term "universal".
In essence, Universal Rendering seeks to address some of the problems faced when having a completely client-side rendered application. However it's very important to realize that Universal Rendering is a compromise, and a bad one at that. It serves to keep all the goodness of client-side rendering while shoehorning extra complexity from server-side rendering to mitigate some of the shortcomings, while simultaneously creating new problems.
"Universal Rendering is fast"
You often get told that client-side rendering is slow, especially on older mobile devices and on slower connections, because downloading and running the JavaScript that puts stuff on the page takes too long to execute. Let's say we use Universal Rendering to help with this — since the user now gets a preview of the initial render while the JavaScript loads and the framework boots up, they avoid having to sit through a loading wheel and be more likely to bounce. Savvy. But in exchange, now it takes even longer for the device to load the page completely since it has to do the extra work of downloading and rendering the initial content, slowing down the execution of the JavaScript which actually does all the subsequent work. But the worst part is the user cannot interact with the page until all the JavaScript is loaded and the event handlers are set, so whether or not the initial preview is better than a loading screen is subjective. To put it in numbers, the Time to Interactive (TTI) takes a big hit.
"Universal Rendering improves SEO"
Gone are the days when client-rendered apps are a black box to web crawlers. Most web crawlers these days are capable of at least some degree of JavaScript rendering, and Google's crawler Googlebot is based on Chromium, which means it's actually better at handling JavaScript than your average browser (no Safari to drag things down). That being said, there are things to be aware of, and you need to be wary of the fact that JavaScript crawling is a lot slower than regular crawling, so changes on the site could take longer to reflect in search results. Hence, in theory, Universal Rendering is superior when it comes to SEO, but by how much, and does that matter?
The answer is, in most cases, it's not better enough to be worth it if SEO is the only factor driving your decision. First, you need to determine which pages of the site need to be crawled — usually, this is the lander and the links on the lander, like the terms of service, privacy policy, contact, FAQ, etc. You can ensure that just these pages render fast enough using Lighthouse. The main issue pointed out by Lighthouse would likely be "unused JavaScript", since you would not be using any code related to the actual application. You can leverage code-splitting to remove unused application code and libraries. Optimizing these page would also benefit users visiting your site to boot.
"We at <big company> use it, and so should you"
Just because they do doesn't mean you have to. The processes of a big company are very refined and when they choose to do something, it's because they have very specific needs and are willing to compromise so that they can achieve a bigger goal, like increased developer productivity. Sometimes thought, things do backfire, like it did with Netflix, where universally rendered React affected conversions on their lander.
Do you like being joined at the hip with Node.js?
Universal Rendering needs a server-side JavaScript context to function, which in most cases is Node.js, and its single-threaded event loop model means you would eventually run into scaling problems. Hence, you should not treat this Node.js instance as your "backend server", but as a stateless server that would hit your actual backend with all the state, so that it's easy to scale. Or instead, you could go with a hosted solution like Vercel for Next.js. The third option is to rely on prerendering, also known as static site generation or SSG, which involves saving the server-side rendered HTML to static files and then statically hosting the generated files for cheap.
Why Universal Rendering didn't work for us
We tried our hand at Universal Rendering, and yes, it was very fun to use and very addictive. The main motivation was productivity features like hot reloading which come standard in most JavaScript frameworks these days. We first used Nuxt.js, since I (cofounder and CTO of Hyperbeam) had used it before and liked it. We used it for the then-new version of our lander. We started off with full Universal Rendering, and decided to move to prerendering when we started getting hit with a lot of traffic for more stable response times. But the most troubling thing we saw was the high TTI value on Lighthouse for mobile.