Server-Side Rendering (SSR) means generating the HTML of your React (or any front-end) components on the server, so the browser gets a fully rendered page immediately — improving SEO and performance.
Here’s how you can implement SSR step by step 👇
🧠 1. Set up a Node.js server (e.g., with Express)
import express from "express";
import path from "path";
import { fileURLToPath } from "url";
const app = express();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.use(express.static(path.join(__dirname, "public"))); // serve static files
app.listen(3000, () => console.log("SSR Server running on port 3000"));
🧠 2. Render React components on the server
Use React and ReactDOMServer to convert components into HTML strings.
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "./App.js"; // your React root component
app.get("*", (req, res) => {
const html = ReactDOMServer.renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR Example</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
✅ This sends fully rendered HTML to the browser.
🧠 3. Hydrate on the client side
Once the page loads, React attaches event listeners and makes the page interactive using hydrate instead of render.
import React from "react";
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
hydrateRoot(document.getElementById("root"), <App />);
✅ hydrateRoot matches the already rendered HTML and makes it live.
🧠 4. Handle Data Fetching Before Render
If your app depends on data (like API calls), fetch it on the server before rendering to avoid flickering.
app.get("*", async (req, res) => {
const data = await fetch("https://api.example.com/data").then(r => r.json());
const html = ReactDOMServer.renderToString(<App initialData={data} />);
res.send(`
<!DOCTYPE html>
<html>
<body>
<div id="root">${html}</div>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(data)};
</script>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
On the client:
hydrateRoot(document.getElementById("root"), <App initialData={window.__INITIAL_DATA__} />);
✅ This avoids unnecessary API calls after hydration.
🧠 5. Optional — Use Frameworks That Simplify SSR
If you don’t want to build everything from scratch, you can use:
- Next.js (React)
- Nuxt.js (Vue)
- SvelteKit (Svelte)
These handle routing, bundling, hydration, and code splitting automatically.
✅ In short:
- Use Node.js with Express to handle incoming requests.
- Use
ReactDOMServer.renderToStringto render HTML on the server. - Send pre-rendered HTML to the client.
- Hydrate with React on the client side.
- Pre-fetch data server-side to improve UX.
- Or use frameworks like Next.js to make SSR much easier.