How to Implement SSR(Server Side Rendering) in React 18
Learn how to implement the “renderToPipeableStream” server API to render a React tree as HTML to a Node.js stream.
React 18, the latest version of the popular JavaScript library for building interactive user interfaces comes with many new features and enhancements. Its improved proficiency at Server Side Rendering (SSR)
is a noteworthy feature.
In this article, we’ll explore React’s SSR feature with helpful code samples and examples. But first, let’s differentiate between client-side and server-side rendering.
Client-side rendering (CSR) is the process of rendering web pages on the client side (i.e., in the user’s web browser). The server merely provides the raw data or content, which the client-side JavaScript utilizes to construct the final rendered page dynamically.
Server-side rendering (SSR) refers to the process of rendering web pages on the server before sending them to the client’s web browser. Rather than transmitting raw data and depending on the client-side, this approach involves the server generating the final HTML markup for a web page and sending it to the client.
Implement “renderToPipeableStream” Server API
Step 1: Create a new React application using the create-react-app
command line tool. Open your favorite terminal and write the below command.
npx create-react-app server-api-demo-app
Step 2: Switch to your newly created React app.
cd server-api-demo-app
Step 3: Now, add react-router-dom
in the project to handle routing.
npm install react-router-dom
Step 4: Let’s add some pages to your application. In the app.js,
you can add the sample routes as added below:
(i) Home
(ii) About
const App = () => (
<div>
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</div>
);
Step 5: Add some content to both pages. For reference, click here.
Step 6: Create a new folder server
at the root level, and in there — new files index.js
and server.js
. Copy and paste the below code in that file.
// server/index.js
require("ignore-styles");
require("@babel/register")({
ignore: [/(node_modules)/],
presets: ["@babel/preset-env", "@babel/preset-react"],
});
require("./server");
This code fragment sets up Babel for code translation, filters out certain files like those in “node_modules”, and launches the server by importing the “server” module. This setting is commonly used in React server-side rendering to allow the server to process and serve React components to clients.
// server/server.js
import express from "express";
import React from "react";
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import App from "../src/App";
const app = express();
app.get("/*", (req, res) => {
const entryPoint = ["/main.js"];
const { pipe, abort: _abort } = ReactDOMServer.renderToPipeableStream(
<StaticRouter location={req.url}>
<App />
</StaticRouter>,
{
bootstrapScripts: entryPoint,
onShellReady() {
res.statusCode = 200;
res.setHeader("Content-type", "text/html");
pipe(res);
},
onShellError() {
res.statusCode = 500;
res.send("<!doctype html><p>Loading...</p>");
},
}
);
});
app.listen(3002, () => {
console.log("App is running on http://localhost:3002");
});
The code defines a route handler for all routes using app.get("/*", ...)
. This means that this route handler will handle any incoming request to the server. Inside the route handle:
- The
entryPoint
array is defined with the valuemain.js
. This points to the JavaScript file used to bootstrap the client-side code. ReactDOMServer.renderToPipeableStream()
accepts two arguments: aReact Node
for HTML rendering and an optionaloptions
object with streaming options. It returns an object with two methods: pipe and abort. Thepipe
method outputs HTML to the specified Node.js stream. We use pipe inonShellReady
to enable streaming. For static generation and crawlers,onAllReady
can also be used.- The
onShellReady()
is triggered when the rendering process is complete, and the HTML is ready for client transmission. It sets the response status code to200
, defines the content type header astext/html
, and pipes the rendered HTML to the response using the pipe method. - The
onShellError
() callback is triggered when an error occurs during rendering. It sets the response status code to500
and sends a basic error message enclosed in an HTML <p> tag.
7. On the client side, we need to update ReactDOM.createRoot
with ReeactDOM.hydrareRoot
in index.js
file to make the server-generated HTML interactive.
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
ReactDOM.hydrateRoot(
document,
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
8. Add the below script in package.json
file to run the code on the server side.
"ssr": "npm run build && node server/index.js"
This command will build your project, run the code on the server side, and generate the output on localhost:3002
9. Now, run npm run ssr
command to see the output.
Here, we have demonstrated one “renderToPipeableStream” API only. React also provides other APIs like “renderToNodeStream”, “renderToReadableStream”, “renderToStaticMarkup”, “renderToStaticNodeStream”, and “renderToStream” for server-side rendering based on our requirements.
For detailed information about these APIs, you may refer to the official documentation.
Conclusion
With these new Server APIs, we can render React components to server-rendered HTML, either as a Node.js stream or a Web stream.
In many cases, frameworks such as Next.js, Remix, and Gatsby may handle this process automatically. These APIs are only used to build the server-rendered HTML at the top level of your app, which will improve initial load time, SEO, user experience, and reduced vulnerability to cross-site scripting (XSS) assaults.
However, while SSR offers advantages, it also comes with drawbacks such as complex implementation, increased server load that can consume substantial amounts of processing and memory, and may not be suitable for real-time applications like chat apps and multiplayer games.
So, consider your requirements and ensure SSR implementation aligns with them.
For more updates on the latest tools and technologies, follow the Simform Engineering blog.