Server-Side Rendering at the Edge with Node.js

July 22, 2020

As you build your website, you inevitably make a series of foundational decisions that will impact the entire architecture of your applications. One of these core decisions is where to implement rendering logic in your application.

Approaches to Rendering

There are several approaches to rendering (thanks to Google for the definitions and diagrams). Decisions around these approaches typically revolve around performance impact, including Time To First Byte (TTFB), First Paint (FP), First Contentful Paint (FCP), and Time To Interactive (TTI).

SSR: Server-Side Rendering

Rendering a request for a page as HTML on the server, on each request.

server-side rendering

CSR: Client-Side Rendering

Rendering an app in a browser, by manipulating the DOM.

client-side rendering

Rehydration

“Booting up” JavaScript (JS) views in the browser so they reuse the server-rendered HTML’s DOM tree and data.

rehydration rendering

Pre-Rendering

Running a client-side application at build time to capture its initial state as static HTML.

static rendering

Each approach has pros and cons in relation to performance. In reality, you can opt to use different approaches for different pages. Netflix, for instance, server-renders its relatively static landing pages, while prefetching the JS for interaction-heavy pages, giving the heavier client-rendered pages a better chance of quick loading.

In this post, we’ll focus on server-side rendering (SSR), starting with a quick overview of what SSR is, then explore how to do it with Node.js, finishing with a look at SSR at the Edge.

What is Server-Side Rendering?

Server-side rendering is the process of taking a client-side only single page application (SPA) and rendering it to static HTML and CSS on the server, on each request. SSR sends a fully rendered page to the client. The client’s JS bundle then takes over and the SPA framework can operate as normal.

Why is this important?

  • Improved performance - The wait time needed to download, parse, and execute the JS code in the browser is eliminated. With static websites or pages, SSR can be useful as it generates the full HTML for a page on the server in response to navigation. This avoids additional round-trips for data fetching and templating on the client since it’s taken care of before the browser gets a response, therefore, server-side rendering helps you get your website rendered faster.
  • Faster load times - SSR generally produces a fast First Paint (FP) and First Contentful Paint (FCP). Faster load times equal a better user experience.
  • Improved SEO - Another benefit of using SSR is having an app that can be crawled for its content even for crawlers that don’t execute JavaScript code. This can help boost SEO.
  • Social sharing - With SSR, you get a featured image and elaborate snippet when sharing your website’s content via social media. This isn’t possible for just client-side rendered apps.

SSR is often used to build complex applications that involve user interaction, rely on a database, or where content changes happen frequently. When content changes often, users need to see the updated content as soon as it is available. It also helps applications that have tailored content depending on who is viewing it, such as social media sites and applications in which you need to store user-specific data, such as email and user preference while also delivering SEO.

Using Node.js for SSR

Node.js is a server-side JavaScript runtime designed to build scalable network applications.

SSR breaks many of the assumptions behind how Node.js can be best used, because it is compute intensive. Node.js can handle large amounts of asynchronous I/O in parallel, but it runs into limits on compute. As the compute portion of the request increases (as it does during SSR), concurrent requests will slow because of contention for the CPU.

This becomes important for SSR when a server process handles multiple concurrent requests. The other requests being processed will delay the concurrent requests. This issue worsens as concurrency increases. This means that when Node.js is used for SSR, it’s being used not for its concurrency model (as it is for many applications) but rather for its library support and browser characteristics. There are ways to handle concurrent requests in parallel, such as by running multiple processes of Hypernova via the built-in Node.js cluster module along with a buffering reverse proxy to handle communication with the clients, as has been done successfully at Airbnb.

Some JS frameworks, such as server-side rendered React apps use Node.js for the server, which is an important difference from traditional server-rendered apps.

Server-Side Rendering at the Edge

Utilizing Firebase hosting and dynamically generating content with serverless functions will allow you to store content in an edge platform or CDN cache. This allows you to compress and cache content near end users for lower latency in the network call, meaning when the next user visits the website, it won’t have to do the generation of content again. It will simply serve it from the local Edge closest to that end user. This further improves performance and takes load off the server.

Looking at a real use case, we worked with our client Adore Beauty to deploy their Nuxt app at the Edge using the Section platform. We used Section’s Node.js module to spin up a SSR framework for Vue.js, but it could be used for any other front-end framework.
node.js ssr
As a result, Adore Beauty now has a distributed Vue.js Server-Side Rendering framework for which they no longer have to think about the infrastructure on which their app runs. Section has also configured Adore Beauty’s Bitbucket Pipeline to automatically deploy to Section on a git push to their master branch.

This example can be adapted to other frameworks, web servers, and use cases. The possibilities of using Node.js for applications you want to run at the edge are as varied as your imagination.