Using React Suspense for Better Server-Side Rendering

Improve user experience and performance in server-side rendering with React suspense

Pavindu Lakshan
Bits and Pieces

--

Server-side rendering is increasingly becoming popular among web developers. However, implementing server-side rendering in React applications did not meet the expectations and caused a lousy developer and user experience.

As a result, React included React Suspense in their upcoming React 18 release to improve the performance of server-side rendering in React applications.

This article will discuss what React Suspense is and how developers can take advantage of it to improve their React applications.

Introduction to React Suspense

React Suspense is a React component that pauses (suspends) an element’s rendering unless all the data it requires is available. It assures that data is ready before rendering components and helps avoid loading components with pending data.

Your child component should implement lazy loading via React.Lazy to use React Suspense. You can even specify multiple lazy loading components with a single Suspense component as well.

import React from "react"
import Spinner from "components/Spinner"

const SomeComponent = React.lazy(() => import('components/SomeComponent'));

export default function App(){
return (
<React.Suspense fallback={<Spinner />}>
<SomeComponent />
</React.Suspense>
)
}

As shown above, all you have to do is wrap the component tree with the Suspense component specifying a fallback UI. React then will prioritize rendering based on the availability of the child React components. If some elements are not yet ready, Suspense will continue to show a fallback UI in place of those React components until they are ready.

A Primer on Server-side Rendering

Server-side rendering is a technique where React components are compiled into HTML server-side and sent to clients. So, end-users will not have to wait till the client-side JavaScript is executed and the application is rendered.

Server-side rendering is increasingly becoming popular because it solves some major problems with client-side rendering. When a single page application is rendered on the client-side, the user sees a blank screen until JavaScript is executed and the UI is rendered. The larger the size of the application is, the longer the time it takes to render, causing a bad user experience. Also, search engines can only access JavaScript bundles in client-side rendering. Therefore, they cannot crawl and index the web app, which negatively affects the SEO performance.

In the current method of server-side rendering, the app fetches all data required for the entire application. After data is received, the whole application is compiled to HTML on the server, using the renderToString method. The produced markup is then sent in the response. Finally, the JavaScript is loaded for the entire application on the client-side and connected to the generated HTML.

import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './components/app';
module.exports = function render(initialState) {
let content = renderToString(<App />);
return content
};

Challenges in Server-side rendering in React

Server-side rendering in React indeed brings many advantages, but it is not a silver bullet. In the current way of implementing server-side rendering, the whole React application is compiled to HTML and then sent to the client. That is, the application should have all data fetched beforehand.

This process will not cause issues in small projects since the response size is small. But as the application grows, the response size will increase. And, it will take more time to reach and render on the client side.

Though the whole application is compiled and sent to the client, most of the time, the user will only interact with several components. Therefore, it would be faster and more efficient to compile and send those key components first and the rest on-demand.

React Suspense to the rescue

React Suspense can resolve the above problems with server-side rendering in React. Instead of the traditional renderToString method, React 18 introduces renderToPipeableStream, that support React Suspense and lazy loading out-of-the-box.

// express.js route 
const express = require("express")
const app = express()
app.get('/', async function(req, res) {
render(res);
});

//render function
import { renderToPipeableStream } from 'react-dom/server';
import DataProvider from "components/DataProvider"
import App from "./App"

const API_DELAY = 2000;
const ABORT_DELAY = 10000;

module.exports = async function render(res) {
let isError = false;

res.socket.on("error", (error) => {
console.error("Error: ", error);
});

const data = await mockServerDataFetching();

const { pipe, abort } = renderToPipeableStream(
<DataProvider data={data}><App /></DataProvider>,
{
// get script names from webpack stats
bootstrapScripts: [assets["main.js"]],
onCompleteShell() {// Set error status code, if an error happened before starting streaming
res.statusCode = isError ? 500 : 200;
res.setHeader("Content-type", "text/html");
pipe(res);
},
onError(error) {
isError = true;
console.error(error);
}
}
);

// Abandon and switch to client rendering if enough time passes.
setTimeout(abort, ABORT_DELAY);
};

// Simulate a delay caused by data fetching.
function mockServerDataFetching() {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, API_DELAY);
});
}

With this new method, the data-ready components will be compiled to HTML on the server-side as usual. Then, they will replace the components with pending data with the fallback element. Finally, when each child component receives the data, it is compiled to HTML and streamed to the client, along with a small JS script to replace the initially rendered fallback element with the new markup content.

React Suspense helps reduce the downloaded bundle size. Because it only contains the compiled React components ready to be rendered and the JavaScript needed for the hydration. Also, it reduces the initial response time as there is no need to wait for the data required for the whole application.

Conclusion

Server-side rendering React applications has become popular since it helps avoid some significant disadvantages with client-side rendering. However, it was not without its challenges due to the inefficiency of how server-side rendering currently works.

React 18 has solved most of these issues with its new Suspense architecture for server-side rendering. In addition, it includes new methods to compile React code to HTML, along with full support for lazy loading and streaming the compiled content on-demand.

I hope this article has helped you better understand the new React Suspense architecture for server-side rendering. Thank you for reading.

Build composable web applications

Don’t build web monoliths. Use Bit to create and compose decoupled software components — in your favorite frameworks like React or Node. Build scalable and modular applications with a powerful and enjoyable dev experience.

Bring your team to Bit Cloud to host and collaborate on components together, and greatly speed up, scale, and standardize development as a team. Start with composable frontends like a Design System or Micro Frontends, or explore the composable backend. Give it a try →

Learn More

--

--

Software Engineer at WSO2 | Tech Blogger | Open-source Enthusiast - Views and opinions are strictly my own