Part 4 of Teaching Myself Some More React
Chapter 4: Basic SSR Setup
Dhruv
Senior Frontend Developer specializing in React
Introduction: From Concept to Code
In this chapter, we will implement a basic Server-Side Rendering (SSR) setup for a React application using Node.js, Express.js, and React’s renderToString()
function. By the end, you’ll have a functional SSR app capable of rendering React components on the server and delivering pre-rendered HTML to the client.
1. Rendering Your First React Component on the Server
The foundation of SSR is rendering React components to HTML strings on the server. Let’s create a basic example.
Step 1: React Component
// src/shared/App.js
import React from 'react';
const App = () => {
return (
<div>
<h1>Hello, Server-Side Rendering!</h1>
<p>This is a React component rendered on the server.</p>
</div>
);
};
export default App;
Step 2: Server Code
// src/server/index.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from '../shared/App';
const app = express();
const PORT = process.env.PORT || 3000;
app.get('*', (req, res) => {
const appHtml = renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSR Example</title>
</head>
<body>
<div id="root">${appHtml}</div>
</body>
</html>
`);
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
Run the Server
npm run dev
Expected Output
When you navigate to http://localhost:3000
, the server renders the React component, and you see the HTML content.
2. Structuring a Dynamic HTML Template
Hardcoding HTML isn’t scalable. Let’s create a template function for dynamic HTML generation.
Template Function
// src/server/template.js
export const renderHtml = (reactHtml) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSR Example</title>
</head>
<body>
<div id="root">${reactHtml}</div>
</body>
</html>
`;
Update the Server
// src/server/index.js
import { renderHtml } from './template';
app.get('*', (req, res) => {
const appHtml = renderToString(<App />);
res.send(renderHtml(appHtml));
});
3. Adding Dynamic Data
React SSR supports rendering dynamic content from the server.
Server with Dynamic Props
app.get('*', (req, res) => {
const appProps = { name: 'React Developer' };
const appHtml = renderToString(<App {...appProps} />);
res.send(renderHtml(appHtml));
});
Update the React Component
// src/shared/App.js
const App = ({ name }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>This is a React component rendered on the server with dynamic data.</p>
</div>
);
};
Expected Output
The app now dynamically greets the user based on server-provided props.
4. Setting Up Hydration
Hydration ensures the static server-rendered HTML becomes a fully interactive React app.
Client-Side Entry Point
// src/client/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from '../shared/App';
const root = ReactDOM.hydrateRoot(document.getElementById('root'), <App />);
Update Webpack Config
Add a client-side Webpack build:
const clientConfig = {
entry: './src/client/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'client.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
};
module.exports = [serverConfig, clientConfig];
Serve the Client Script
Update your HTML template to include the client-side bundle:
export const renderHtml = (reactHtml) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSR Example</title>
</head>
<body>
<div id="root">${reactHtml}</div>
<script src="/dist/client.js"></script>
</body>
</html>
`;
5. Handling Routes
React Router enables dynamic routing in SSR.
Add React Router
npm install react-router-dom
React Component with Routes
// src/shared/App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = () => <h1>Home Page</h1>;
const About = () => <h1>About Page</h1>;
const App = () => {
return (
<Router>
<Switch>
<Route exact path='/' component={Home} />
<Route path='/about' component={About} />
</Switch>
</Router>
);
};
export default App;
Support Routes on the Server
app.get('*', (req, res) => {
const context = {};
const appHtml = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
res.send(renderHtml(appHtml));
});
6. Debugging Common Issues
-
Mismatched HTML:
- Ensure server and client use the same React version.
- Fix dynamic data discrepancies.
-
Missing Routes:
- Use
StaticRouter
on the server andBrowserRouter
on the client.
- Use
Key Takeaways
- SSR allows you to serve pre-rendered React components as HTML.
- Hydration bridges server-rendered HTML with React’s interactivity.
- React Router works seamlessly in SSR with
StaticRouter
.
Quiz
- What is the role of
renderToString
in SSR? - Why is hydration necessary for SSR apps?
- How do you pass dynamic data to server-rendered React components?
Practical Assignment
- Assignment: Extend the SSR setup to include routing for
/
,/about
, and a dynamic/user/:id
route. Render user details server-side based on the route parameter.
Part 4 of Teaching Myself Some More React
Dhruv
Dynamic Frontend Developer specializing in React.js and Next.js. Creating engaging web experiences with modern technologies and beautiful animations.