node.js - 当 React SSR 发生错误时,应该返回什么样的响应?
问题描述
我想做的事
假设您在 express 和 React 中实现了 SSR,并且在渲染期间抛出了错误。捕获错误时考虑返回以下数据
- 返回状态码 500。
- 用于确定错误类型的自定义标头。
此外,由于应用程序非常庞大,我们希望将传递给客户端的数据按照可以发送的顺序流式传输,以提高渲染性能。因此,可能会在 SSR 发生错误之前到达 HTTP 响应正文。
示例包括以下
<!DOCTYPE html>
- 固定
<head>
标签的内容
示例代码
为了重现这些要求,我们使用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 如下所示
- renderToStaticNodeStream:http://localhost:9000/sample1
- renderToStaticMarkup : http://localhost:9000/sample2
执行结果
如果您访问/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
也出于同样的原因引发错误并终止服务器。
可能的解决方案
解决方案很简单。
- 用于
renderToStaticMarkup
在输出完整的 HTML 后向客户端返回响应。 - 实现一个不会在 SSR 上抛出错误的完整实现(例如,使用错误边界)。
- 始终向客户端返回状态代码 200,并且不只渲染 SSR 失败的部分。
实际的实现是*1,这就是我们当前的实现方式。有人担心(未验证)3将在 Google 搜索中被索引以查找错误条件。
在大型复杂应用程序的情况下该怎么办
此处显示的示例代码是一个相对较小的应用程序。因此,可以瞄准一个不会在 SSR 上引发任何错误的完整实现。然而,在实践中,应用程序依赖于许多库并且有各种各样的条件分支,因此很难以完整的实现为目标。此外,由于前端应用程序需要快速渲染,开发人员希望实现更快地向用户返回结果的实现。例如,缩短 TTFB。
您还有其他可能的解决方案吗?
解决方案
推荐阅读
- bash - Shell脚本突出显示两个csv文件之间更新和添加的差异
- c++ - 如何在声明后为二维向量的所有元素分配相同的值
- python - 将单词列表添加到 Python 字典的时间复杂度是多少?
- java - 如何通过单击删除包含 SQLite 数据的列表视图行
- clickhouse - 将新节点添加到 Clickhouse 集群的正确方法是什么?
- sql - SQL Group By last created 每季度和组织的记录
- javascript - 返回在JS中的函数内创建的对象的正确方法是什么,
- vue.js - 是否可以使用 Nuxt JS 检查动态路由中的路由参数数据类型
- python-3.x - Pandas 检查列并添加前导零
- android - 在 Kotlin 数据类中调用重载构造函数的更简单方法