Progressive Rendering

Progressive Rendering is the technique of sequentially rendering portions of a webpage in the server and streaming it to the client in parts without waiting for the whole page to rendered.

Note: The content of this post may be relevant to all types of servers but written within the context of a node.js server. The perf metrics are assumptive and may differ based on network bandwidth and latency.

To understand progressive rendering we must first understand how client side rendering and server side rendering work.

Client Side Rendering

Client side rendering is the technique in which the server sends a simple HTML without any content in the body and script tags in the head . This is what create-react-app builds output.

Typical page load behaviour in CSR —

Browser requests the server for HTML Server sends HTML with script tags in head and no content in body Browser parses the HTML and makes http requests to load the scripts Once the scripts are loaded, the browser parses them and makes API requests and loads all the content asynchronously

Client Side Rendering

Since the all the content starts loading only after loading the initial JavaScript, it takes a longer time to show any content on the page. If the user is on a slow network, the content is blocked for an even longer time due to lower bandwidth and higher latency.

Client Side Rendering FP metric

Pros

Once the initial JavaScript is loaded, the content can be loaded asynchronously. We can load the critical content first and then load non-critical content later. Server serves static HTML and initial load JavaScript bundles can be cached in the browsers which may benefit frequent users.

Cons

Initial load JavaScript blocks content load. The larger your bundle size is, the longer it will take to show content on the page. API requests to load content have to travel all over the world to fetch data as they are not co-located with the browser location. This may take even longer in slower networks due to high latency and low bandwidth. SEO scores take a huge hit since search engines don’t execute JavaScript (arguably) and only an empty page is indexed. This matters when you want your page to be public and searchable.

Server Side Rendering

Server side rendering is the technique in which the whole HTML is rendered on the server and sent to client. This is what Next.js or Gatsby (build time server render) does.

Typical page load behaviour in SSR —

Browser requests the server for HTML. Server makes API requests (usually co-located) and renders the content in the server. Once the page is ready, the server sends it to the browser. The browser loads and parses the HTML and paints the content on the screen without waiting for the JavaScript bundle(s) to load. Once the JavaScript bundle(s) are loaded, the browser hydrates interactivity to DOM elements, which is usually attaching event handlers and other interactive behaviours.

Server Side Rendering

Since the APIs are usually co-located with the server, the content is loaded super fast (faster than CSR) and the HTML is sent to the browser. Initial JavaScript load doesn’t block content load as the HTML sent by the server already has the content.

Server Side Rendering FP metric

Pros

Since the server serves HTML with content, neither the browser nor search engines have to depend on JavaScript for the content. Content is not blocked by the time taken to load JavaScript bundle(s). Page loads a lot faster than CSR.

Cons

The entire HTML has to be rendered on the server before sending it to the client. This means, even the non-critical content has to rendered on the server to before sending the response HTML to the client.

Progressive Rendering

Progressive Rendering (aka Progressive Server Side Rendering) is a technique in which once you render the critical content on the server, you start streaming it to the client without waiting for non-critical content. You then stream the non-critical content later once it’s rendered on the server. The browser starts to progressively render (paint) the HTML on the page as soon as a chunk for critical content is received. Non-critical content is then later rendered (paint) on the page when the browser receives it from the server.

Typical page load behaviour in PSSR —

Browser requests the server for HTML. Server makes API requests (usually co-located) and renders the critical content first in the server and streams it to the browser. The browser receives the chunk of HTML and renders (paints) it on the screen. The server renders non-critical content after rendering critical content and streams it to the client. The browser receives and renders (paints) the non-critical content later. Once the entire page is loaded, the browser hydrates interactivity to DOM elements, which is usually attaching event handlers and other interactive behaviours.

Progressive Rendering

Progressive rendering bridges the benefits of both CSR and SSR. Content is rendered quickly since the APIs are co-located in the server and at the same time, critical content can be rendered quickly without having to wait for non-critical content.

With Progressive Rendering, you can make your site load faster asynchronously without relying on JavaScript to load content.

Progressive Rendering FP metric

The whole idea of progressive rendering depends on the concept of streaming HTML from the server to client. You can read more about it here.

You often get magnitudes of performance boost if you do PSSR right. Here’s an example of the same website rendered with SSR vs PSSR. (Assuming the site is loaded over a low bandwidth network with high latency).

SSR vs PSSR

To put things into perspective, perf profiling SSR vs PSSR gives a clear perf boost in how quickly content shows up in the page.

SSR vs PSSR

How to progressively render a page?

Node.js has this Readable API which will let you pipe a readable stream. An example code to stream HTML might look like this

router.get("/stream", async function(req, res, next) {

res.status(200);

res.type("text/html; charset=utf-8"); // myRendered renders the HTML content

const DOCTYPE = await myRendered.DOCTYPE();

const openHTML = await myRendered.openHTML();

const head = await myRendered.head();

const openBody = await myRendered.openBody(); res.write(`${DOCTYPE}${openHTML}`);

res.write(head);

res.write(openBody); const pageChunks = [

myRendered.header,

myRendered.criticalContentOpen,

myRendered.nonCriticalContentOpen,

myRendered.nonCriticalContentClose,

myRendered.criticalContentClose,

myRendered.closeBody,

myRendered.closeHTML

]; const pageStream = new Readable({

async read(size) {

if (!pageChunks.length) {

pageStream.push(null);

} else {

const chunkToRender = pageChunks.shift();

const renderedChunk = await chunkToRender();

pageStream.push(renderedChunk);

}

}

}); // stream content as soon as they are rendered

pageStream.pipe(res);

});

The example for this website built with Express and vanilla JS is here —

https://github.com/flexdinesh/progressive-rendering

The idea is you predictably slice portions of your HTML content and stream it to the browser as soon as it is rendered.

Pros

Render on server but stream critical content to client without waiting for non-critical content. Content is not blocked by the time taken to load JavaScript bundle(s). Page loads a lot faster than both CSR and SSR. Build fruitful user experiences even for users on slower networks.

Cons

The browser will hydrate the DOM and attach event listeners only after the whole page is loaded. Even though content shows up quickly, interactivity will be enabled only after non-critical content is loaded. (But showing up content a lot quicker is still a win). There is no established framework for progressive rendering and is highly dependent on the web application and its limitations.

Tips for effective progressive rendering