You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React DOM Server中结合模板引擎使用renderToNodeStream?

Combining React's renderToNodeStream with Template Engines (like art-template) + Redux Store Injection

Great question! The key here is to split your HTML template into static parts (where you inject data like Redux state) and the dynamic React component stream, then pipe them sequentially to the response. Here are two practical approaches to achieve this:

Approach 1: Manual Stream Chaining (Keep Using art-template)

If you want to stick with art-template, you can split your HTML template into head and tail sections, render the static head with your injected data, then pipe the React component stream, followed by the static tail. This avoids rendering the entire React app to a string while still injecting your Redux state.

Step 1: Split Your Template

Create two art-template files:

  • index-head.art (static top part with data injection):
<!DOCTYPE html>
<html>
<head>
  <title>My Streaming App</title>
</head>
<body>
  <div id="root">
    <!-- React stream will go here -->
  </div>
  <script>
    // Inject initial Redux state
    window.__INITIAL_STATE__ = {{ store | json_encode }};
  </script>
  • index-tail.art (static bottom part):
<script src="/client-bundle.js"></script>
</body>
</html>

Step 2: Chain Streams in Your Server Code

Use Node.js streams to pipe the rendered head, React stream, and tail one after another:

const http = require('http');
const fs = require('fs');
const { Readable } = require('stream');
const template = require('art-template');
const React = require('react');
const { renderToNodeStream } = require('react-dom/server');
const { createStore } = require('redux');
const App = require('./App');

http.createServer((req, res) => {
  // Create Redux store and get initial state
  const store = createStore();
  const initialState = store.getState();

  // 1. Render the head template to a string, convert to a readable stream
  const headHtml = template(__dirname + '/index-head.art', {
    store: JSON.stringify(initialState)
  });
  const headStream = Readable.from([headHtml]);

  // 2. Create the React component stream
  const reactStream = renderToNodeStream(React.createElement(App, { store }));

  // 3. Read the tail template as a stream
  const tailStream = fs.createReadStream(__dirname + '/index-tail.art');

  // Pipe streams sequentially (don't end the response until all are done)
  headStream.pipe(res, { end: false });
  headStream.on('end', () => {
    reactStream.pipe(res, { end: false });
    reactStream.on('end', () => {
      tailStream.pipe(res);
    });
  });
}).listen(3000);
  • The { end: false } option ensures we don't close the response stream prematurely, letting each part finish before the next starts.

Approach 2: Use a Template Engine with Native Streaming Support

If you're open to switching templates, engines like Nunjucks or EJS have built-in streaming APIs that let you embed React streams directly in your template, making the code cleaner.

Example with Nunjucks

Step 1: Install Nunjucks

npm install nunjucks

Step 2: Create a Single Template (index.njk)

<!DOCTYPE html>
<html>
<head>
  <title>My Streaming App</title>
</head>
<body>
  <div id="root">
    <!-- Embed the React stream directly -->
    {% renderReactStream %}
  </div>
  <script>
    window.__INITIAL_STATE__ = {{ store | dump | safe }};
  </script>
  <script src="/client-bundle.js"></script>
</body>
</html>

Step 3: Server Code with Nunjucks Streaming

const http = require('http');
const nunjucks = require('nunjucks');
const React = require('react');
const { renderToNodeStream } = require('react-dom/server');
const { createStore } = require('redux');
const App = require('./App');

// Configure Nunjucks (disable autoescape for raw HTML/JSON)
nunjucks.configure(__dirname, { autoescape: false });

http.createServer((req, res) => {
  const store = createStore();
  const initialState = store.getState();

  // Create the React component stream
  const reactStream = renderToNodeStream(React.createElement(App, { store }));

  // Use Nunjucks' renderStream to pipe the template + React stream
  const templateStream = nunjucks.renderStream('index.njk', {
    store: initialState,
    renderReactStream: () => reactStream
  });

  // Pipe everything to the response
  templateStream.pipe(res);
}).listen(3000);
  • Nunjucks' renderStream handles the stream chaining for you, so you don't have to manage multiple stream events manually.

Key Notes

  • XSS Safety: Always properly escape your injected data (like the Redux state). Most template engines have filters (like Nunjucks' safe or art-template's json_encode) to handle this.
  • Performance: Streaming lets the browser start parsing HTML and downloading resources (like your client bundle) before the entire React app is rendered, which improves time-to-first-byte and first meaningful paint.

内容的提问来源于stack exchange,提问作者Jack Chen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:53:59