首页 > 解决方案 > 当 React SSR 发生错误时,应该返回什么样的响应?

问题描述

我想做的事

假设您在 express 和 React 中实现了 SSR,并且在渲染期间抛出了错误。捕获错误时考虑返回以下数据

此外,由于应用程序非常庞大,我们希望将传递给客户端的数据按照可以发送的顺序流式传输,以提高渲染性能。因此,可能会在 SSR 发生错误之前到达 HTTP 响应正文。

示例包括以下

示例代码

为了重现这些要求,我们使用renderToStaticNodeStream和提供了两个示例实现renderToStaticMarkup

GitHub:https ://github.com/Himenon/react-ssr-error-handle

import * as React from "react";
import express from "express";
import {
  renderToStaticNodeStream,
  renderToStaticMarkup,
} from "react-dom/server";

const SERVER_PORT = 9000;

const server = express();

const LargeApplication = () => {
  const somethingError = () => {
    console.log(`Access window object in nodejs: ${window.location.href}`);
  };
  somethingError();
  return (
    <html lang="en">
      <head>
        <title>React SSR Streaming Error Handle</title>
      </head>
      <body>
        <h1>Hello world!</h1>
      </body>
    </html>
  );
};

server.get("/sample1", (req: express.Request, res: express.Response) => {
  const stream = renderToStaticNodeStream(<LargeApplication />);
  res.type("html");
  res.write("<!DOCTYPE html>");
  stream.pipe(res, { end: true });
  stream.on("error", (error) => {
    res.status(500);
    res.setHeader(
      "Custom-Error-Code",
      "REACT:RENDER_TO_STATIC_NODE_STREAM_ERROR"
    );
    res.write(`<pre><code>${error.stack}</code></pre>`);
    res.end();
  });
});

server.get("/sample2", (req: express.Request, res: express.Response) => {
  try {
    res.type("html");
    res.write("<!DOCTYPE html>"); // can be immediately responded to
    const html = renderToStaticMarkup(<LargeApplication />);
    /* <---- I know that describing it here won't cause any problems. */
    // res.type("html");
    // res.write("<!DOCTYPE html>");
    /* ----> */
    res.write(html);
  } catch (error) {
    res.status(500);
    res.setHeader(
      "Custom-Error-Code",
      "REACT:RENDER_TO_STATIC_NODE_STREAM_ERROR"
    );
    res.send(`<pre><code>${error.stack}</code></pre>`);
  }
});

server.listen(SERVER_PORT, () => {
  console.log(`Serve start: http://localhost:${SERVER_PORT}`);
});

此服务器的 URL 如下所示

执行结果

如果您访问/sample1,将显示以下错误并且服务器停止。

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:485:11)react-ssr-error-handle/src/server.tsx:33:9)
    at ReactMarkupReadableStream.emit (events.js:210:5)
    at ReactMarkupReadableStream.EventEmitter.emit (domain.js:475:20)
    at emitErrorNT (internal/streams/destroy.js:92:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
    at processTicksAndRejections (internal/process/task_queues.js:80:21)
error Command failed with exit code 1.

原因很明显,因为响应头出现在响应正文之后,因为res.setHeader基于捕获的错误发送的res.write("<!DOCTYPE html>").

/sample2也出于同样的原因引发错误并终止服务器。

可能的解决方案

解决方案很简单。

  1. 用于renderToStaticMarkup在输出完整的 HTML 后向客户端返回响应。
  2. 实现一个不会在 SSR 上抛出错误的完整实现(例如,使用错误边界)。
  3. 始终向客户端返回状态代码 200,并且不只渲染 SSR 失败的部分。

实际的实现是*1,这就是我们当前的实现方式。有人担心(未验证)3将在 Google 搜索中被索引以查找错误条件。

在大型复杂应用程序的情况下该怎么办

此处显示的示例代码是一个相对较小的应用程序。因此,可以瞄准一个不会在 SSR 上引发任何错误的完整实现。然而,在实践中,应用程序依赖于许多库并且有各种各样的条件分支,因此很难以完整的实现为目标。此外,由于前端应用程序需要快速渲染,开发人员希望实现更快地向用户返回结果的实现。例如,缩短 TTFB。

您还有其他可能的解决方案吗?

标签: node.jsreactjsexpressserver-side-rendering

解决方案


推荐阅读