
在react服务器端渲染(ssr)应用中,当服务器生成html并发送到客户端后,客户端的react会尝试“接管”这份html,将其转化为可交互的组件树,这个过程称为hydration(水合)。如果客户端react在hydration过程中发现服务器端渲染的html结构与它期望的结构不匹配,就会抛出类似“expected server html to contain a matching <header> in <div>”的警告。这种不匹配会导致react放弃hydration,转而进行客户端渲染(csr),从而失去ssr带来的性能和seo优势。
这种警告通常不是因为HTML本身语法错误,而是因为客户端和服务器端渲染的HTML在细节上存在差异,例如多余的空白字符、注释或属性顺序等。React对这种匹配要求非常严格。
在不使用Next.js等框架,而是采用Express、EJS和React构建SSR应用时,开发者通常会遇到上述问题。以下是一个典型的设置:
Express服务器接收请求后,使用renderToString将React组件渲染成HTML字符串,然后将该字符串注入到EJS模板中。
// server.jsx (简化示例)
const express = require('express');
const path = require('path');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const ejs = require('ejs');
const SchoolPage = require('./dist/SchoolPage').default; // 确保路径正确
const app = express();
app.use(express.static(path.join(__dirname, 'dist'))); // 假设静态文件在dist
app.get("/campus/:id/locations", async (req, res) => {
const reactComponentHtml = ReactDOMServer.renderToString(<SchoolPage />);
const filePath = path.join(__dirname, "dist", "school-page.ejs");
ejs.renderFile(filePath, { reactComponent: reactComponentHtml }, (err, html) => {
if (err) {
console.error("Error rendering template:", err);
return res.status(500).end();
}
res.send(html);
});
});
app.listen(3000, () => console.log('Server running on port 3000'));一个包含HTML语义元素的React组件。
// SchoolPage.jsx
import React from 'react';
import MiniSearchBar from './MiniSearchBar'; // 假设有这个组件
import ContentContainer from './ContentContainer'; // 假设有这个组件
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>
</>
);
}这是将React组件HTML注入到完整HTML骨架的地方。
<!-- school-page.ejs (存在问题的写法) -->
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... 省略head内容 ... -->
<title>Document</title>
</head>
<body>
<div class="root">
<!-- Rendered React component will be injected here -->
<%- reactComponent %>
</div>
<script src="/school-page.js" defer></script>
</body>
</html>在上述EJS模板中,开发者可能会习惯性地将<!-- Rendered React component will be injected here -->注释和<%- reactComponent %>放在新行,这正是导致Hydration警告的常见原因。
Hydration警告的根本原因在于EJS模板中注入React组件时,父容器div.root与实际注入的reactComponent之间存在了多余的空白字符或换行符。当服务器端渲染时,renderToString会生成不包含这些额外空白的HTML字符串。然而,当EJS模板将这个字符串插入到<div class="root">标签内部时,由于模板文件中的换行和缩进,最终生成的HTML会变成类似:
<div class="root"> <!-- Rendered React component will be injected here --> <header>...</header> <section>...</section> </div>
而客户端的React在Hydration时,期望的是一个紧凑的结构,例如:
<div class="root"><header>...</header><section>...</section></div>
这种服务器端和客户端期望的HTML结构差异(即使只是空白字符的差异),都会被React的Hydration机制检测为不匹配,从而触发警告并回退到客户端渲染。
解决此问题的关键在于确保EJS模板中React组件的注入是完全内联的,不引入任何额外的空白字符或换行符。
将EJS模板中的注入点修改为以下形式:
<!-- school-page.ejs (修复后的写法) -->
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... 省略head内容 ... -->
<title>Document</title>
</head>
<body>
<div class="root"><%- reactComponent %></div>
<script src="/school-page.js" defer></script>
</body>
</html>通过将<%- reactComponent %>直接紧跟在<div class="root">之后,并在其关闭标签</div>之前,可以消除中间的所有空白字符和换行符。这样,服务器端生成的HTML与客户端React期望的结构就能精确匹配,从而成功进行Hydration。
虽然不是导致Hydration警告的直接原因,但将根容器的class="root"改为id="root"是React官方推荐的做法。客户端React通常通过ReactDOM.hydrateRoot(document.getElementById('root'), <App />)或ReactDOM.render(<App />, document.getElementById('root'))来挂载或水合应用,使用id选择器能够更清晰、更高效地定位根元素。
<!-- school-page.ejs (更佳实践) -->
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... 省略head内容 ... -->
<title>Document</title>
</head>
<body>
<div id="root"><%- reactComponent %></div>
<script src="/school-page.js" defer></script>
</body>
</html>同时,客户端的入口文件也需要相应调整:
// client-side.js (或 school-page.js)
import React from 'react';
import { hydrateRoot } from 'react-dom/client'; // React 18+
import SchoolPage from './SchoolPage';
// React 18+
hydrateRoot(document.getElementById('root'), <SchoolPage />);
// 或者 React 17及以下
// import ReactDOM from 'react-dom';
// ReactDOM.hydrate(<SchoolPage />, document.getElementById('root'));通过上述方法,特别是确保EJS模板中React组件的内联注入,可以有效解决React SSR中的Hydration警告,确保应用能够充分利用服务器端渲染的优势,提供更好的用户体验和SEO表现。
以上就是解决React SSR中Hydration警告:EJS模板注入的细微之处的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号