At Bitovi, we’ve been working on utilizing HTTP streaming to speed up single-page application (SPA) load times for the last couple of years. We’ve developed a technique that accelerates page load times that we call incremental rendering, which went into DoneJS 3. Today we’re happy to announce another project, Velocirender, which brings incremental rendering to any framework.
This article will:
-
Explain what incremental rendering is and how it improves page load times.
-
Explain how Velocirender makes it easy to get the benefits of incremental rendering with any framework.
If the above is enough to sell you on this amazing technology, you can skip the rest of this article and go straight to Velocirender’s Getting Started Guide.
SPA Performance FAQ
What about performance?
This article focuses on page load times, not other aspects of SPA performance like diffing optimizations or bundle size. Velocirender tunes performance by improving the utilization of the network.
There are two terms to understand when analyzing network performance.
-
Latency refers to the time it takes for a packet of data to get to its destination over a network. On the web, it’s the round-trip time from when your browser requests something to when the first packet arrives.
-
Throughput refers to the bits per second (bps) at which data can travel over a network.
So to summarize, latency is the time it takes data to get somewhere; throughput is how big the pipes are.
What slows down SPA page loads?
There are really two different ways to serve a SPA and they each have their own performance characteristics:
A client-only SPA is served from an empty “shell” of HTML. To boot up, the browser must:
-
Download the HTML.
-
Start downloading styles (<link>) and scripts.
-
Execute the scripts.
-
Retrieve data via an API that the scripts call to fetch().
We call this type of performance bottleneck — where each step is dependent on the completion of the previous — a waterfall.
Because of this, client-only SPAs are susceptible to conditions of low throughput. We cannot take the next step when the data travels so slowly through the network.
The second way to serve a SPA is…
Traditional server-side rendering
In the context of a single-page application, SSR refers to the practice of executing the app on a (Node.js) server and rendering the result to a string of HTML. When the browser receives the HTML, it is able to show it to the user immediately. This improves perceived performance.
However, SSR actually slows down the time until the application is fully booted (time to interactive). This is because the process of rendering on the server delays the time from which the browser can start downloading assets. Those steps described in the previous section still need to happen.
There’s a secondary problem with SSR. SPAs are usually broken up into components; small isolated pieces of code that render one part of the app. Naturally these components have all of their logic self-contained; including data-fetching. SSR makes it harder to colocate data-fetching within the component because an outside process, the route handler, is what’s actually in control of loading data. In short, SSR decreases the maintainability appeal of a SPA. This is probably why SSR is used less in the wild than it should be.
So what is incremental rendering?
Incremental rendering is a technique that bridges the performance advantages of both SSR and client-side SPAs:
-
Start downloading the app as quickly as possible.
-
Show the user something while everything is loading (perceived performance).
Incremental rendering achieves this by spreading the cost of rendering between the server and the browser.
How does incremental rendering work?
Like SSR, incremental rendering executes the application code on a server.
The process looks like this:
- The browser makes a request for the page (Start).
-
The server sends only the HTML that is rendered before any data requests. In addition, it also attaches a script to the page that is able to receive mutation commands and begins loading the client SPA JavaScript code.
-
While the client app begins to load, the server continues to execute the app in much of the same way: data requests come back and the DOM changes. When changes occur, they are converted into a bytecode format and streamed back to the browser as mutation commands.
-
The incremental rendering (IR) script receives these mutations and updates the DOM.
-
Meanwhile the client JavaScript application boots in the background. The IR script has a heuristic to determine when the application has “caught up” with the mutations. At this time it cancels the mutation stream and hands over control to the app.
What is Velocirender?
Velocirender, a project you should star on GitHub right now, is an HTTP server with incremental rendering built in. It can render any HTML page, either a local file or even an external URL. This means it’s compatible with nearly every client-side framework. Best of all, it requires no modifications to your code!
It is able to do this because:
-
jsdom is used to mount the app the same way it is done in the browser.
-
The HTML is instrumented to include a script that applies mutations and disconnect once the app is booted.
Example apps are available for React, Vue, and Angular.
The below video shows an app running on Velicorender side-by-side with one running on static hosting:
Visit the homepage to try out the side-by-side demos yourself.
Trying out Velocirender
Getting started is easy. The Getting Started guide takes you through all of the steps in detail, but the tldr version is:
Install the CLI:
npm install -g @bitovi/velocirender
Run the CLI, providing any statically hosted SPA’s URL. Here’s the example React app hosted on GitHub pages:
velocirender https://bitovi.github.io/dog-things-react/
That’s really it. If you have your own SPA that’s hosted on an accessible URL, you can try that one out too.
Next Steps
-
Check out the Getting Started Guide and try Velocirender out in your app.
-
Star Velicorender on GitHub and file issues.
-
Join Bitovi’s Slack Community, come to #performance and tell us what you’re building. We’d love to help!