Learn to build React Server Side and Client Side components in a NodeJS app.
Building React Server Side components doesn’t have to be this hard. Let me show you a simplified walk-thru with up to date technologies on how to build a simple Node app that uses both Server and Client Side React Components.
Estimated reading time: 9 minutes
Let’s begin with an outline of what tools we’re going to need to build this app:
- Node Version 20 or higher
- package.json
- Webpack, Babel and accompanying loaders.
- React & React-Dom 18 or higher
- Express v4.19 or higher
Here’s a breakdown of the sections covered in this article
- Learn to build React Server Side and Client Side components in a NodeJS app.
- Let’s begin with an outline of what tools we’re going to need to build this app:
- Node Version
- Package.json
- Babel & Register.js
- To build a NodeJS app with React, Create the Node server in the app.js
- To Run React on the Server
- Run React Client Components on your React Server Components
- Bringing it all together
- Resolving Text content does not match server-rendered HTML. Minified React error #425
Node Version
The first step in building a Node app that uses both Server and Client Side React is configuring your node version and package.json. If you don’t have node & npm installed, please check out their how to here. Once you have Node & NPM installed, you can check your version in your command line tool with the following command:
node -v
If you don’t have node version 20 or higher, I suggest installing and setting up Node Version Manager or nvm. Check out this great article from freecodecamp on how to get started using this tool.
Once you have at least Node version 20 installed you can open up your IDE, I’m using VSCode, and you can create your package.json.
Don’t forget to add a .gitignore file to your root directory! Be sure to ignore the node_modules:
/node_modules
Package.json
To create your package.json, be sure you’re in your project root directory and then in your terminal you can simply type in the following. The -y flag will just move you faster through this process of creating a package.json:
npm init -y
Update Package.json
For this project, we are going to change the “main” property from index.js to app.js. And we’re going to add a “type” property of “module”. This will allow us to use import statements instead of node’s customary require statements.
Update your package.json so it looks like this:
{
"name": "node-react-app",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "app.js",
Install Dev Dependencies
We can install the following dev dependencies for our project you can do these all at once or a few at a time. For simplification I’m going to put these all into one command:
npm install --save-dev webpack webpack-cli mini-css-extract-plugin css-minimizer-webpack-plugin css-loader babel-loader @babel/preset-env
Install Dependencies
Next up let’s install all of our dependencies we might need for the front end:
npm install @babel/core @babel/preset-react @node-loader/babel express react react-dom
Create start and build scripts
In your package.json under the “scripts” property add the following script commands. We’ll come back to making these files soon but for now, we’ll just create the commands that tell our project what to do.
Update your package.json with the following:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node --import ./register.js app.js",
"build": "npx webpack"
},
If you didn’t update your node version to at least version 20 this start script will give you problems!
Babel & Register.js
The second step to getting a Node app that uses both Server and Client Side React set up is configuring Babel. Now that we have our package.json all set up let’s get our babel configuration working. In your root directory, create a file named babel.config.js. Paste in the following code.
export default {
presets: [
[
'@babel/preset-react',
{
runtime: 'automatic',
}
]
]
}
Next and also in your root folder, create the register.js file and paste in the following:
import { register } from 'node:module'
import { pathToFileURL } from 'node:url'
register('@node-loader/babel', pathToFileURL('./'))
Now we’re done setting up Babel and it will compile the React code for us :tada:
To build a NodeJS app with React, Create the Node server in the app.js
Next let’s set up our Node server in the app.js file. Start with our imports, we’ll import the following:
import express from 'express';
import { renderToPipeableStream } from 'react-dom/server';
import process from 'process';
Notice there is a react import above. renderToPipeableStream is a function that will allow us to serve React on the server. We’ll come back to this in the next section!
We are going to set up our app and middleware with the following code. Note that we are pointing to a “views” folder and a “public” folder. Keep in mind that the views folder is where our React components will live and the public folder is where webpack is going to build our front end assets.
const app = express();
app.set('views', './views');
app.use(express.urlencoded({ extended: false }));
app.use(express.static('public'));
Next, we’ll set up our port and tell our app to listen to port 3000, or whatever the process.env.PORT is.
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
app.listen(port);
Test that your node server runs
In your app.js file add a console log before the app.listen(port) command.
I added the following:
console.log('i running?')
Then in my terminal I ran by start script with:
npm run start
In my terminal I can see my console.log which means my server is running :tada:

To Run React on the Server
To run React on the server and build our build a NodeJS app with React we’ll start by getting our React Server Side components set up.
We first need to create a views folder in our root directory. Within the views folder I’ll create an App.js file. This will be my entire app’s entry point built with React. My App.js file is going to serve the HTML head content and html doc as a server side rendered component. Within my body, I’m creating my root element where my React Client Side Components will render later.
Add the following to your App.js file:
export default function App() {
return (
<>
<html lang="en">
<head>
<meta charSet='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<!-- we will add important link tags here for our client-side code -->
</head>
<body>
<div id="root" className='wrapper'></div>
</body>
</html>
</>
);
}
Render the App.js file on the server in our app.js file
Back in our app.js file, we can now import our App.js component and use the renderToPipeableStream function to render our app on the server.
At the top of our app.js file let’s import the App.js component. Be sure to add the .js! This is needed when using the module type in package.json.
import App from './views/App.js'
Inside the app.js file before the normalizePort code, and after the middleware “app.use(express.static(‘public’))”, add the following:
app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
});
});
What this code is doing is telling the server that the document type is text/html, and it’s rendering the App.js component as HTML.
You can check that this worked by running npm run start and then visiting the following URL
http://localhost:3000/
If you open the dev tools you should see your HTML document. Now we have created a React component that’s running on the node server. Not so difficult right!
Run React Client Components on your React Server Components
Here comes the fun part. Now that we have Server Side React working, if you want to be able to useState, useEffects, perform Javascript Event handling and more, you’ll need to use React on the Client side. We already have everything set up on the server so without further ado let’s get React working on the Client too.
The React Client Side Entry file and hydrateRoot function
In our views folder, let’s create the index.js file. This is our React Client side entry file. It will be bundled & compiled by webpack into browser compatible code. We’re going to use the hydrateRoot function to render our components inside the “root” element we placed in the App.js file.
We’re going to build the BodyContent.js component in a minute, but let’s go ahead and import this file and utilize this component in our hydrateRoot function:
import { hydrateRoot } from 'react-dom/client';
import BodyContent from './components/BodyContent.js';
hydrateRoot(document.getElementById('root'), <BodyContent />);
BodyContent Component
For the sake of simplicity, I’ve named my component the ‘BodyContent’ component. You can name yours whatever you want. In the views folder, create a ‘components’ folder and within that create a BodyContent.js file.
Let’s go ahead and create a hello world statement within the BodyContent App like this:
import * as React from 'react';
const BodyContent = () => {
return (
<>
Hello World!
</>
);
};
export default BodyContent;
Set up webpack bundler
Your Client side code still has a ways to go before the browser will be able to read it. Let’s get webpack set up to run our code on the front end for us.
First create an asset folder in the root directory and then create a style.css file like this ->. /assets/style.css
You can place styles in here if you want to, I’ll leave that up to you.
Next, create a public folder in your root directory like this ->. /public
Finally, in the root folder, create a webpack.config.cjs file.
Be sure to use the .cjs extension because this will allow you to make your webpack config run seamlessly in your module type project.
Add the following code to your webpack.config.cjs file. This webpack is set up to take a CSS file from the entry point assets directory named styles.css, optimize it, and output it into the public folder titled index.bundle.css. It’s also set up to take the index.js file in the views folder (our React Client Side Entry file), and output it into the public folder and title it index.bundle.js.
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
index: [
__dirname + '/views/index.js',
__dirname + '/assets/styles.css',
],
},
output: {
path: __dirname + '/public',
filename: '[name].bundle.js',
},
module: {
rules: [
{
test: /\.(?:js)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: 'defaults' }],
],
},
},
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
optimization: {
minimizer: [new CssMinimizerPlugin()],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].bundle.css',
}),
],
};
Bringing it all together
Now that we have webpack set up, and the Client Side React component built. We need to do a couple of more things to wrap this up and get everything functioning.
Run npm install to ensure your packages are all installed
Run this in your terminal before you continue on:
npm i
Run the webpack build script
In the terminal run the following to ensure your front end assets and React Client Side components will work:
npm run build
Be sure to check the /public directory for the new files index.bundle.js and index.bundle.css
Add bundled scripts into the App.js head tag
Inside the App.js file, add the following link tags including the async and defer on the script tag, where the HTML comment was:
<link
rel='stylesheet'
href="/index.bundle.css"
>
</link>
<script
type='text/javascript'
src="/index.bundle.js"
async
defer
/>
Run the following to start your node server and then view your React Server and Client side components on the front end:
npm run start
You should see Hello World! in the browser window when you visit localhost:3000 like this below:

Resolving Text content does not match server-rendered HTML. Minified React error #425
If you open up the console, and see the following Error, then you have just one more thing to do to get your Client Side React component set up properly:

Back in the BodyContent.js file, update your component in the following ways:
import * as React from 'react';
const BodyContent = () => {
const [isClient, setIsClient] = React.useState(false);
React.useEffect(() => {
setIsClient(true);
}, []);
return (
<>
Hello World!
</>
);
};
export default BodyContent;
Note in the above code that we’re incorporating useState and useEffect in our component. Now we just need to set up our component to only render if isClient is true. This tells the app that if useEffect and useState are working (ie. This is a Client component) then render the component, otherwise render nothing.
Complete the BodyContent component with the conditional render:
const BodyContent = () => {
const [isClient, setIsClient] = React.useState(false);
React.useEffect(() => {
setIsClient(true);
}, []);
return <>{isClient && <>Hello World!</>}</>;
};
export default BodyContent;
Now, go ahead and close the server (Ctrl + C for Mac users) and then run a fresh build:
npm run build
Finally, run your server again and visit the app at localhost:3000
npm run start
You should no longer see the React Minified Error #425 in the console and you should see “Hello World!” on your screen. Congratulations! You did it! You just made a Node app with React Server Side and React Client Side components :tadaco:
Photo by Taylor Vick on Unsplash