如何在React DOM Server中结合模板引擎使用renderToNodeStream?
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'
renderStreamhandles 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'
safeor art-template'sjson_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

