prerenderToNodeStream
prerenderToNodeStream
kết xuất một cây React thành một chuỗi HTML tĩnh bằng cách sử dụng Node.js Stream..
const {prelude} = await prerenderToNodeStream(reactNode, options?)
Tham khảo
prerenderToNodeStream(reactNode, options?)
Gọi prerenderToNodeStream
để kết xuất ứng dụng của bạn thành HTML tĩnh.
import { prerenderToNodeStream } from 'react-dom/static';
// Cú pháp trình xử lý route phụ thuộc vào framework backend của bạn
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});
response.setHeader('Content-Type', 'text/plain');
prelude.pipe(response);
});
Trên client, gọi hydrateRoot
để làm cho HTML được tạo từ server trở nên tương tác.
Tham số
-
reactNode
: Một node React mà bạn muốn kết xuất thành HTML. Ví dụ: một node JSX như<App />
. Nó được mong đợi đại diện cho toàn bộ tài liệu, vì vậy component App sẽ kết xuất thẻ<html>
. -
optional
options
: Một đối tượng với các tùy chọn tạo tĩnh.- optional
bootstrapScriptContent
: Nếu được chỉ định, chuỗi này sẽ được đặt trong một thẻ<script>
nội tuyến. - optional
bootstrapScripts
: Một mảng các URL chuỗi cho các thẻ<script>
để phát ra trên trang. Sử dụng cái này để bao gồm<script>
gọihydrateRoot
. Bỏ qua nếu bạn không muốn chạy React trên client. - optional
bootstrapModules
: Giống nhưbootstrapScripts
, nhưng phát ra<script type="module">
thay thế. - optional
identifierPrefix
: Một tiền tố chuỗi mà React sử dụng cho các ID được tạo bởiuseId
. Hữu ích để tránh xung đột khi sử dụng nhiều root trên cùng một trang. Phải là cùng một tiền tố như được truyền chohydrateRoot
. - optional
namespaceURI
: Một chuỗi với namespace URI gốc cho stream. Mặc định là HTML thông thường. Truyền'http://www.w3.org/2000/svg'
cho SVG hoặc'http://www.w3.org/1998/Math/MathML'
cho MathML. - optional
onError
: Một callback kích hoạt bất cứ khi nào có lỗi server, cho dù có thể khôi phục được hay không. Theo mặc định, nó chỉ gọiconsole.error
. Nếu bạn ghi đè nó để ghi lại các báo cáo sự cố, hãy đảm bảo rằng bạn vẫn gọiconsole.error
. Bạn cũng có thể sử dụng nó để điều chỉnh mã trạng thái trước khi shell được phát ra. - optional
progressiveChunkSize
: Số byte trong một chunk. Đọc thêm về heuristic mặc định. - optional
signal
: Một tín hiệu abort cho phép bạn hủy bỏ quá trình kết xuất server và kết xuất phần còn lại trên client.
- optional
Trả về
prerenderToNodeStream
trả về một Promise:
- Nếu kết xuất thành công, Promise sẽ resolve thành một đối tượng chứa:
prelude
: một Node.js Stream của HTML. Bạn có thể sử dụng stream này để gửi phản hồi theo từng chunk hoặc bạn có thể đọc toàn bộ stream vào một chuỗi.
- Nếu kết xuất không thành công, Promise sẽ bị reject. Sử dụng cái này để xuất shell dự phòng.
Cách sử dụng
Kết xuất một cây React thành một stream HTML tĩnh
Gọi prerenderToNodeStream
để kết xuất cây React của bạn thành HTML tĩnh vào một Node.js Stream.:
import { prerenderToNodeStream } from 'react-dom/static';
// Cú pháp trình xử lý route phụ thuộc vào framework backend của bạn
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});
response.setHeader('Content-Type', 'text/plain');
prelude.pipe(response);
});
Cùng với component root, bạn cần cung cấp một danh sách các đường dẫn <script>
bootstrap. Component root của bạn sẽ trả về toàn bộ tài liệu bao gồm thẻ <html>
gốc.
Ví dụ: nó có thể trông như thế này:
export default function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>Ứng dụng của tôi</title>
</head>
<body>
<Router />
</body>
</html>
);
}
React sẽ inject doctype và thẻ <script>
bootstrap của bạn vào stream HTML kết quả:
<!DOCTYPE html>
<html>
<!-- ... HTML từ các component của bạn ... -->
</html>
<script src="/main.js" async=""></script>
Trên client, script bootstrap của bạn sẽ hydrate toàn bộ document
bằng một lệnh gọi đến hydrateRoot
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
Điều này sẽ đính kèm các trình xử lý sự kiện vào HTML tĩnh được tạo từ server và làm cho nó có tính tương tác.
Tìm hiểu sâu
Các URL asset cuối cùng (như các tệp JavaScript và CSS) thường được băm sau bản dựng. Ví dụ: thay vì styles.css
, bạn có thể kết thúc với styles.123456.css
. Băm tên tệp asset tĩnh đảm bảo rằng mọi bản dựng riêng biệt của cùng một asset sẽ có một tên tệp khác nhau. Điều này rất hữu ích vì nó cho phép bạn bật bộ nhớ cache dài hạn một cách an toàn cho các asset tĩnh: một tệp có tên nhất định sẽ không bao giờ thay đổi nội dung.
Tuy nhiên, nếu bạn không biết URL asset cho đến sau bản dựng, bạn không có cách nào để đưa chúng vào mã nguồn. Ví dụ: việc mã hóa cứng "/styles.css"
vào JSX như trước đây sẽ không hoạt động. Để giữ chúng bên ngoài mã nguồn của bạn, component root của bạn có thể đọc tên tệp thực từ một map được truyền dưới dạng một prop:
export default function App({ assetMap }) {
return (
<html>
<head>
<title>Ứng dụng của tôi</title>
<link rel="stylesheet" href={assetMap['styles.css']}></link>
</head>
...
</html>
);
}
Trên server, kết xuất <App assetMap={assetMap} />
và truyền assetMap
của bạn với các URL asset:
// Bạn cần lấy JSON này từ công cụ bản dựng của bạn, ví dụ: đọc nó từ đầu ra bản dựng.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: [assetMap['/main.js']]
});
response.setHeader('Content-Type', 'text/html');
prelude.pipe(response);
});
Vì server của bạn hiện đang kết xuất <App assetMap={assetMap} />
, bạn cần kết xuất nó với assetMap
trên client để tránh các lỗi hydration. Bạn có thể serialize và truyền assetMap
cho client như sau:
// Bạn cần lấy JSON này từ công cụ bản dựng của bạn.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
// Cẩn thận: An toàn để stringify() cái này vì dữ liệu này không phải do người dùng tạo.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['/main.js']],
});
response.setHeader('Content-Type', 'text/html');
prelude.pipe(response);
});
Trong ví dụ trên, tùy chọn bootstrapScriptContent
thêm một thẻ <script>
nội tuyến bổ sung đặt biến window.assetMap
toàn cục trên client. Điều này cho phép mã client đọc cùng một assetMap
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App assetMap={window.assetMap} />);
Cả client và server đều kết xuất App
với cùng một prop assetMap
, vì vậy không có lỗi hydration.
Kết xuất một cây React thành một chuỗi HTML tĩnh
Gọi prerenderToNodeStream
để kết xuất ứng dụng của bạn thành một chuỗi HTML tĩnh:
import { prerenderToNodeStream } from 'react-dom/static';
async function renderToString() {
const {prelude} = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js']
});
return new Promise((resolve, reject) => {
let data = '';
prelude.on('data', chunk => {
data += chunk;
});
prelude.on('end', () => resolve(data));
prelude.on('error', reject);
});
}
Điều này sẽ tạo ra đầu ra HTML không tương tác ban đầu của các component React của bạn. Trên client, bạn sẽ cần gọi hydrateRoot
để hydrate HTML được tạo từ server đó và làm cho nó có tính tương tác.
Chờ tất cả dữ liệu được tải
prerenderToNodeStream
đợi tất cả dữ liệu được tải trước khi hoàn thành việc tạo HTML tĩnh và resolve. Ví dụ: hãy xem xét một trang hồ sơ hiển thị ảnh bìa, một sidebar với bạn bè và ảnh và một danh sách các bài đăng:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
Hãy tưởng tượng rằng <Posts />
cần tải một số dữ liệu, điều này mất một chút thời gian. Lý tưởng nhất là bạn muốn đợi các bài đăng hoàn thành để nó được đưa vào HTML. Để thực hiện việc này, bạn có thể sử dụng Suspense để tạm dừng dữ liệu và prerenderToNodeStream
sẽ đợi nội dung bị tạm dừng hoàn thành trước khi resolve thành HTML tĩnh.
Khắc phục sự cố
Stream của tôi không bắt đầu cho đến khi toàn bộ ứng dụng được kết xuất
Phản hồi prerenderToNodeStream
đợi cho đến khi toàn bộ ứng dụng kết thúc quá trình kết xuất, bao gồm cả việc chờ tất cả các ranh giới Suspense được resolve, trước khi resolve. Nó được thiết kế để tạo trang web tĩnh (SSG) trước thời hạn và không hỗ trợ truyền trực tuyến thêm nội dung khi nó tải.
Để truyền trực tuyến nội dung khi nó tải, hãy sử dụng API kết xuất server phát trực tuyến như renderToPipeableStream.