javascript 解决React问题:警告“预期服务器HTML包含中的匹配< header>项< div>”,带有服务器端呈现和Express

hs1ihplo  于 2023-06-04  发布在  Java
关注(0)|答案(2)|浏览(517)

我一直在尝试为我的个人项目使用react设置SSR,并且遇到了一些问题。控制台抛出了一堆错误,第一个是标题的警告,然后默认为CSR。我没有使用NextJS,只是express,ejs,node和react。我的设置如下:
我的server.jsx文件处理GET请求,将组件呈现为html,使用ejs将其插入到我的html中,并将其发送到客户端:

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);
  });
});

我的组件本身:

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>
    </>
  );
}

它被注入到的ejs文件:

<!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>

我听说这类错误来自格式错误的HTML,但似乎很少有文档说明什么样的结构可能导致这种情况。我听说过表格和嵌套元素的问题,但没有关于标题的问题。我已经通过几个html验证器运行了服务器生成的html,似乎没有什么问题。我甚至试过删除组件的某些部分,比如header,但这只会在下一个嵌套组件中产生类似的错误(Expected server HTML to contain a matching in)。作为参考,下面是服务器发送到前端的html:

<!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>

我还听说,如果前端做一些操作来改变页面,它可能会导致不匹配。我的搜索栏组件有一个建议功能,它可以获取数据并显示它,但我看不出这是问题,因为当它最初加载时,它应该是相同的。此外,完全删除组件也不能解决问题。

k4emjkb1

k4emjkb11#

当服务器呈现的HTML与客户端尝试呈现的HTML不同时,通常会出现错误消息“Expected server HTML to contain a matching in”。当组件的某些部分使用仅限客户端的数据或执行不在服务器上执行的操作时,可能会发生这种情况。
这里有一些你可以研究的事情:
仅客户端操作:如果在组件挂载时,只有客户端的操作修改DOM或React状态,这可能会导致服务器渲染的HTML和客户端之间的不匹配。这些操作包括在useEffect中获取数据,基于窗口尺寸设置状态等。
水合问题:React期望服务器和客户端之间呈现的内容是相同的。React使服务器呈现的页面具有交互性的过程称为水合。之所以会有这个警告,是因为React期望标记(HTML)是相同的,这样它就可以重用(水合)服务器呈现的HTML,而不是创建一个新的DOM树。服务器和客户端呈现的内容之间的任何差异都可能导致此错误。
React版本:确保您在服务器上使用的React版本与客户端上相同。不同的版本可以具有不同的渲染特性。
异步操作:确保所有的数据获取都发生在您开始在服务器端呈现组件之前。您的MiniSearchBar和ContentContainer可能需要数据才能呈现。考虑使用支持服务器端渲染的数据获取库,如react-query或SWR。
状态不匹配:在有状态组件的情况下,如果服务器和客户端呈现的初始状态不同,则可能导致此问题。
没有完整的代码,很难找出确切的问题。但是,这些步骤应该有助于您调试问题。我建议首先确保所有组件都有必要的数据,以便在服务器端正确呈现,并且在组件完全水合之前没有仅运行客户端的操作。
对于更复杂的应用程序,您可能需要考虑像Next.js这样的框架,这些框架是为服务器端渲染而构建的,并抽象出许多常见的陷阱。

slwdgvem

slwdgvem2#

我怀疑有人会看到这一点,但如果其他人遇到这个问题,我做了两件事来解决这个问题。首先,我怀疑这是否重要,但我将<div class="root">更改为<div id="root">。更重要的是,也可能是导致这个问题的原因是React注入了组件inline,这意味着如果你尝试像这样编写html模板:

<div class="root">
  <!-- Rendered React component will be injected here -->
  <%- reactComponent %>
</div>

React会对你大喊大叫,因为它不希望组件从根div注入到新的一行。所以你要这样写:

<div class="root"><%- reactComponent %></div>

这是一个简单的解决办法,但也是一个容易被忽视的办法。

相关问题