使 renderToNodeStream 与异步头盔版本一起工作
Making renderToNodeStream work with an async Helmet version
我正在尝试将我的工作 SSR 代码从 renderToString
转换为 renderToNodeStream
,后者在 React.JS 中可用,以缩短生成第一个字节的时间。我知道 react-helmet
是同步的,不能与 renderToNodeStream()
一起使用,但人们使用一些“异步”分支使头盔与 async/stream 渲染互操作。我为我的实验选择了 react-helmet-async
库,并将我的代码转换为以下内容:
const helmetData = new HelmetData({});
store
.runSaga(rootSaga)
.toPromise()
.then(() => {
frontloadServerRender(() =>
// to support AMP, use renderToStaticMarkup()
// we no longer care for AMP
{
const routeMarkupNodeStream = renderToNodeStream(
<Capture report={m => modules.push(m)}>
<Helmet helmetData={helmetData}>
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<Frontload isServer={true}>
<App />
</Frontload>
</StaticRouter>
</Provider>
</Helmet>
</Capture>
);
if (context.url) {
// If context has a url property, then we need to handle a redirection in Redux Router
res.writeHead(302, {
Location: context.url
});
res.end();
return;
}
// Otherwise, we carry on...
const state = store.getState();
const { helmet } = helmetData.context;
// Let's format those assets into pretty <script> tags
const extraChunks = extractAssets(manifest, modules).map(
c =>
`<script type="text/javascript" src="/${c.replace(
/^\//,
''
)}"></script>`
);
console.log('starting to write node-stream');
console.log('html:', helmet.htmlAttributes.toString());
console.log('title:', helmet.title.toString());
res.write(
injectHeaderHTML(cachedHtmlData, {
html: helmet.htmlAttributes.toString(),
title: helmet.title.toString(),
meta: helmet.meta.toString(),
headScript: helmet.script.toString(),
link: helmet.link.toString()
})
);
console.log('wrote head');
// On fastify, we got to set the content to html
// res.header('Content-Type', 'text/html');
routeMarkupNodeStream.pipe(res, { end: false });
routeMarkupNodeStream.on('pipe', () => {
console.log('Piped!');
});
routeMarkupNodeStream.on('end', () => {
res.end(
injectFooterHTML(cachedHtmlData, {
scripts: extraChunks,
state: JSON.stringify(state).replace(/</g, '\u003c')
})
);
console.log('finished writing');
});
}
);
})
.catch(e => {
res.status(500).send(e.message);
});
// Let's do dispatches to fetch category and event info, as necessary
const { dispatch } = store;
当我运行上面的代码时,它崩溃了:
starting to write node-stream
html:
title: <title data-rh="true"></title>
wrote head
events.js:292
throw er; // Unhandled 'error' event
^
Invariant Violation: You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.
at Object.invariant [as default] (/.../node_modules/invariant/invariant.js:40:15)
at e.warnOnInvalidChildren (/.../node_modules/react-helmet-async/src/index.js:147:5)
at /.../node_modules/react-helmet-async/src/index.js:188:14
at /.../node_modules/react/cjs/react.development.js:1104:17
at /.../node_modules/react/cjs/react.development.js:1067:17
at mapIntoArray (/.../node_modules/react/cjs/react.development.js:964:23)
at mapChildren (/.../node_modules/react/cjs/react.development.js:1066:3)
at Object.forEach (/.../node_modules/react/cjs/react.development.js:1103:3)
at e.mapChildrenToProps (/.../node_modules/react-helmet-async/src/index.js:172:20)
at e.r.render (/.../node_modules/react-helmet-async/src/index.js:229:23)
Emitted 'error' event on ReactMarkupReadableStream instance at:
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:80:21) {
framesToPop: 1
}
我也尝试 let helmetContext = {};
并用 <HelmetProvider context={helmetContext}>
包装我的应用程序。在那里,我的 helmetContext
保持设置为 {}
,所以 const { helmet } = helmetContext;
returns undefined
.
在我看来,react-helmet-async 需要多次渲染,就我而言。具体来说,为了让它工作,在 const { helmet } = helmetData.context;
之前,我们需要做一些类似 renderToString(app)
或 renderToStaticMarkup(app)
的事情,否则上下文中什么也没有。这意味着要使用异步 renderToNodeStream
,我们需要先进行同步渲染。
如果以上是正确的,我不明白将 react-helmet-async
与 renderToNodeStream()
结合使用如何比 react-helmet
提供更好的 time-to-first-byte (TTFB
) renderToString()
.
我正在尝试将我的工作 SSR 代码从 renderToString
转换为 renderToNodeStream
,后者在 React.JS 中可用,以缩短生成第一个字节的时间。我知道 react-helmet
是同步的,不能与 renderToNodeStream()
一起使用,但人们使用一些“异步”分支使头盔与 async/stream 渲染互操作。我为我的实验选择了 react-helmet-async
库,并将我的代码转换为以下内容:
const helmetData = new HelmetData({});
store
.runSaga(rootSaga)
.toPromise()
.then(() => {
frontloadServerRender(() =>
// to support AMP, use renderToStaticMarkup()
// we no longer care for AMP
{
const routeMarkupNodeStream = renderToNodeStream(
<Capture report={m => modules.push(m)}>
<Helmet helmetData={helmetData}>
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<Frontload isServer={true}>
<App />
</Frontload>
</StaticRouter>
</Provider>
</Helmet>
</Capture>
);
if (context.url) {
// If context has a url property, then we need to handle a redirection in Redux Router
res.writeHead(302, {
Location: context.url
});
res.end();
return;
}
// Otherwise, we carry on...
const state = store.getState();
const { helmet } = helmetData.context;
// Let's format those assets into pretty <script> tags
const extraChunks = extractAssets(manifest, modules).map(
c =>
`<script type="text/javascript" src="/${c.replace(
/^\//,
''
)}"></script>`
);
console.log('starting to write node-stream');
console.log('html:', helmet.htmlAttributes.toString());
console.log('title:', helmet.title.toString());
res.write(
injectHeaderHTML(cachedHtmlData, {
html: helmet.htmlAttributes.toString(),
title: helmet.title.toString(),
meta: helmet.meta.toString(),
headScript: helmet.script.toString(),
link: helmet.link.toString()
})
);
console.log('wrote head');
// On fastify, we got to set the content to html
// res.header('Content-Type', 'text/html');
routeMarkupNodeStream.pipe(res, { end: false });
routeMarkupNodeStream.on('pipe', () => {
console.log('Piped!');
});
routeMarkupNodeStream.on('end', () => {
res.end(
injectFooterHTML(cachedHtmlData, {
scripts: extraChunks,
state: JSON.stringify(state).replace(/</g, '\u003c')
})
);
console.log('finished writing');
});
}
);
})
.catch(e => {
res.status(500).send(e.message);
});
// Let's do dispatches to fetch category and event info, as necessary
const { dispatch } = store;
当我运行上面的代码时,它崩溃了:
starting to write node-stream
html:
title: <title data-rh="true"></title>
wrote head
events.js:292
throw er; // Unhandled 'error' event
^
Invariant Violation: You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.
at Object.invariant [as default] (/.../node_modules/invariant/invariant.js:40:15)
at e.warnOnInvalidChildren (/.../node_modules/react-helmet-async/src/index.js:147:5)
at /.../node_modules/react-helmet-async/src/index.js:188:14
at /.../node_modules/react/cjs/react.development.js:1104:17
at /.../node_modules/react/cjs/react.development.js:1067:17
at mapIntoArray (/.../node_modules/react/cjs/react.development.js:964:23)
at mapChildren (/.../node_modules/react/cjs/react.development.js:1066:3)
at Object.forEach (/.../node_modules/react/cjs/react.development.js:1103:3)
at e.mapChildrenToProps (/.../node_modules/react-helmet-async/src/index.js:172:20)
at e.r.render (/.../node_modules/react-helmet-async/src/index.js:229:23)
Emitted 'error' event on ReactMarkupReadableStream instance at:
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:80:21) {
framesToPop: 1
}
我也尝试 let helmetContext = {};
并用 <HelmetProvider context={helmetContext}>
包装我的应用程序。在那里,我的 helmetContext
保持设置为 {}
,所以 const { helmet } = helmetContext;
returns undefined
.
在我看来,react-helmet-async 需要多次渲染,就我而言。具体来说,为了让它工作,在 const { helmet } = helmetData.context;
之前,我们需要做一些类似 renderToString(app)
或 renderToStaticMarkup(app)
的事情,否则上下文中什么也没有。这意味着要使用异步 renderToNodeStream
,我们需要先进行同步渲染。
如果以上是正确的,我不明白将 react-helmet-async
与 renderToNodeStream()
结合使用如何比 react-helmet
提供更好的 time-to-first-byte (TTFB
) renderToString()
.