I’ve been trying to set up SSR with react for my personal project and have been running into some issues. The console is throwing a bunch of errors, the first one of which is the warning from the title, and then it defaults to CSR. I am not using NextJS, just express, ejs, node, and react. My setup is as follows:
My server.jsx file, which handles the GET request, renders the component to html, inserts it into my html with ejs, and sends it to the client:
app.get("/campus/:id/locations", async (req, res) => {
const reactComponent = renderToString(<SchoolPage />);
const filePath = path.join(__dirname, "dist", "school-page.ejs");
ejs.renderFile(filePath, { reactComponent }, (err, html) => {
if (err) {
console.error("Error rendering template:", err);
return res.status(500).end();
}
res.send(html);
});
});
My component itself:
export default function SchoolPage() {
return (
<>
<header>
<picture title="Campus Eats">
<source
media="(min-width: 400px)"
srcSet="/images/campus-eats-logo-black.svg"
/>
<img src="/images/campus-eats-logo-mini.svg" alt="campus-eats-logo" />
</picture>
<nav className="places-at">
<h2>Places</h2>
<h2>at</h2>
<div className="search-container">
<MiniSearchBar></MiniSearchBar>
</div>
</nav>
<nav className="login-signup">
<button className="login">Log in</button>
<button className="signup">Sign up</button>
</nav>
</header>
<section>
<ContentContainer></ContentContainer>
</section>
</>
);
}
The ejs file it is injected into:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/styles/reset.css" />
<link rel="stylesheet" href="/styles/school-page.css" />
<script src="/school-page.js" defer></script>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
<title>Document</title>
</head>
<body>
<div class="root">
<!-- Rendered React component will be injected here -->
<%- reactComponent %>
</div>
</body>
</html>
I have heard this sort of error comes from malformed HTML, but there seems to be little documentation on what sort of structure could cause this. I have heard issues with tables and nesting elements inside <p>, but nothing about headings. I have run the html generated by the server through several html validators and nothing seems to be wrong with it. I’ve even tried deleting portions of the component, like the header, but this will just yield a similar error with the next nested component ( Expected server HTML to contain a matching <section> in <div>). For reference, here’s the html that the server is sending to the frontend:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/styles/reset.css" />
<link rel="stylesheet" href="/styles/school-page.css" />
<script src="/school-page.js" defer></script>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
<title>Document</title>
</head>
<body>
<div class="root">
<!-- Rendered React component will be injected here -->
<header>
<picture title="Campus Eats">
<source media="(min-width: 400px)" srcSet="/images/campus-eats-logo-black.svg"/>
<img src="/images/campus-eats-logo-mini.svg" alt="campus-eats-logo"/>
</picture>
<nav class="places-at">
<h2>Places</h2>
<h2>at</h2>
<div class="search-container">
<input type="search" placeholder="Find my school!" value=""/>
<div class="suggestions">
<ul></ul>
</div>
</div>
</nav>
<nav class="login-signup"><button class="login">Log in</button><button class="signup">Sign up</button></nav>
</header>
<section>
<div class="locations"></div>
</section>
</div>
</body>
</html>
I’ve also heard that if the frontend does some operation to alter the page that it could result in a mismatch. My search bar component has a suggestions feature which fetches data and displays it, however I can’t see this being the issue because when it initially loads it should be the same. Additionally, deleting the component entirely doesn’t solve the problem either.
2
Answers
I doubt anyone will see this, but if anyone else is running into this issue I did two things to solve this problem. For one, I doubt this is important, but I changed to . What was more important, and probably causing this issue was that React injects the component inline, meaning that if you try and write your html template like so:
React will scream at you because it was not expecting the component to be injected on a new line from the root div. So you have to write it like so:
A simple fix but also an easy one to overlook.
The error message "Expected server HTML to contain a matching in " typically happens when the server-rendered HTML differs from what the client is attempting to render. This can occur when certain parts of your component use client-only data or perform operations that aren’t executed on the server.
Here are few things you can look into:
Client-Side Only Operations: If there are client-side only operations that modify the DOM or React state when the component mounts, this can cause a mismatch between the server-rendered HTML and the client. These operations include things like fetching data in useEffect, setting state based on window dimensions, etc.
Hydration Issue: React expects that the rendered content is identical between the server and the client. React’s process of making a server-rendered page interactive is called hydration. The warning is there because React expects the markup (HTML) to be identical so it can reuse (hydrate) the server-rendered HTML, instead of creating a new DOM tree. Any difference between server and client rendered content can lead to this error.
React Version: Ensure that you’re using the same React version on the server as on the client. Different versions may have different rendering characteristics.
Asynchronous Operations: Make sure that all the data fetching happens before you start rendering your component on the server side. Your MiniSearchBar and ContentContainer may need data before they can render. Consider using a data fetching library that supports server side rendering like react-query or SWR.
State Mismatch: In case of a stateful component, if initial state is different between server and client render, it could cause this issue.
Without the complete code, it’s hard to pinpoint the exact problem. However, these steps should help you to debug the issue. I would suggest starting by ensuring all components have the necessary data to correctly render on the server side, and there are no client-only operations running before the component has been fully hydrated.
For more complex apps, you might want to consider frameworks like Next.js that are built for server-side rendering and abstract away a lot of these common pitfalls.