renderToReadableStream
renderToReadableStream γ― React γγͺγΌγθͺγΏεγε―θ½γͺ Web Stream γ«γ¬γ³γγΌγγΎγγ
const stream = await renderToReadableStream(reactNode, options?)- γͺγγ‘γ¬γ³γΉ
- δ½Ώη¨ζ³
- React γγͺγΌγ HTML γ¨γγ¦θͺγΏεγε―θ½γͺ Web Stream γ«γ¬γ³γγΌγγ
- γγΌγγι²γγ«γ€γγ¦γ³γ³γγ³γγγΉγγͺγΌγγ³γ°γγ
- γ·γ§γ«γ«δ½γε«γγγοΏ½?ζοΏ½?οΏ½
- γ΅γΌγδΈγ§οΏ½?γ―γ©γγ·γ₯γγ°οΏ½?θ¨ι²
- γ·γ§γ«ε οΏ½?γ¨γ©γΌγγοΏ½?εΎ©εΈ°
- γ·γ§γ«ε€οΏ½?γ¨γ©γΌγγοΏ½?εΎ©εΈ°
- γΉγγΌγΏγΉγ³γΌγοΏ½?θ¨οΏ½?οΏ½
- γ¨γ©γΌοΏ½?οΏ½?ι‘γ«γγ£γ¦ε¦ηγεγγ
- γ―γγΌγ©γιηηζεγγ«ε ¨γ³γ³γγ³γοΏ½?θͺγΏθΎΌγΏγεΎ ζ©γγ
- γ΅γΌγγ¬γ³γγͺγ³γ°οΏ½?δΈζ’
γͺγγ‘γ¬γ³γΉ
renderToReadableStream(reactNode, options?)
renderToReadableStream γεΌγ³εΊγγ¦γReact γγͺγΌγ HTML γ¨γγ¦θͺγΏεγε―θ½γͺ Web Stream γ«γ¬γ³γγΌγγΎγγ
import { renderToReadableStream } from 'react-dom/server';
async function handler(request) {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}γ―γ©γ€γ’γ³γε΄γ§γ―γγοΏ½?γγγ«γ΅γΌγηζγγγ HTML γζδ½ε―θ½γ«γγγγγ« hydrateRoot γη¨γγΎγγ
εΌζ°
-
reactNode: HTML γΈγ¨γ¬γ³γγΌγγγ React γγΌγγδΎγγ°γ<App />οΏ½?γγγͺ JSX θ¦η΄ γ§γγγγγ―γγγ₯γ‘γ³γε ¨δ½γ葨γγγ¨γζεΎ γγγ¦γγγγγAppγ³γ³γγΌγγ³γγ―<html>γΏγ°γγ¬γ³γγΌγγεΏ θ¦γγγγΎγγ -
ηη₯ε―θ½
options: γΉγγͺγΌγ ι’ι£οΏ½?γͺγγ·γ§γ³γε«γΎγγγͺγγΈγ§γ―γγ- ηη₯ε―θ½
bootstrapScriptContent: ζοΏ½?οΏ½γγγε ΄εγγοΏ½?ζεεγγ€γ³γ©γ€γ³οΏ½?<script>γΏγ°ε γ«ι οΏ½?γγγΎγγ - ηη₯ε―θ½
bootstrapScripts: γγΌγΈδΈγ«εΊεγγ<script>γΏγ°γ«ε―ΎεΏγγ URL ζεεοΏ½?ι εγγγγδ½Ώη¨γγ¦γhydrateRootγεΌγ³εΊγ<script>γε«γγΎγγγ―γ©γ€γ’γ³γγ§ React γγΎγ£γγοΏ½?οΏ½θ‘γγγγͺγε ΄εγ―ηη₯γγΎγγ - ηη₯ε―θ½
bootstrapModules:bootstrapScriptsγ¨εζ§γ§γγγ代γγγ«<script type="module">γεΊεγγΎγγ - ηη₯ε―θ½
identifierPrefix: React γuseIdγ«γγ£γ¦ηζγγ ID γ«δ½Ώη¨γγζεεγγ¬γγ£γγ―γΉγεγγγΌγΈδΈγ«θ€ζ°οΏ½?γ«γΌγγδ½Ώη¨γγιγ«γη«ΆεγιΏγγγγγ«η¨γγΎγγhydrateRootγ«γεγγγ¬γγ£γγ―γΉγζΈ‘γεΏ θ¦γγγγΎγγ - ηη₯ε―θ½
namespaceURI: γοΏ½?γΉγγͺγΌγ οΏ½?γ«γΌγγγΌγ γΉγγΌγΉ URI ζεεγγγγ©γ«γγ§γ―ιεΈΈοΏ½? HTML γ§γγSVG οΏ½?ε ΄εγ―'http://www.w3.org/2000/svg'γMathML οΏ½?ε ΄εγ―'http://www.w3.org/1998/Math/MathML'γζΈ‘γγΎγγ - ηη₯ε―θ½
nonce:script-srcContent-Security-Policy γη¨γγ¦γΉγ―γͺγγγ許ε―γγγγοΏ½?nonceζεεγ - ηη₯ε―θ½
onError: γ΅γΌγγ¨γ©γΌγηΊηγγγγ³γ«ηΊη«γγγ³γΌγ«γγγ―γεΎ©εΈ°ε―θ½γͺγ¨γ©γΌοΏ½?ε ΄εγγγγ§γͺγγ¨γ©γΌοΏ½?ε ΄εγγγγΎγγγγγ©γ«γγ§γ―console.errorοΏ½?γΏγεΌγ³εΊγγΎγγγγγδΈζΈγγγ¦γ―γ©γγ·γ₯γ¬γγΌγγγγ°γ«θ¨ι²γγε ΄εγ§γconsole.errorγεΌγ³εΊγγγγ«γγ¦γγ γγγγΎγγγ·γ§γ«γεΊεγγγεγ«γΉγγΌγΏγΉγ³γΌγγθͺΏζ΄γγγγγ«γδ½Ώη¨γ§γγΎγγ - ηη₯ε―θ½
progressiveChunkSize: γγ£γ³γ―οΏ½?γγ€γζ°γγγγ©γ«γοΏ½?ζ¨θ«ζΉζ³γ«γ€γγ¦γ―γγ‘γγεη §γγ¦γγ γγγ - ηη₯ε―θ½
signal: γ΅γΌγγ§οΏ½?γ¬γ³γγΌγδΈζ’γγ¦γ―γ©γ€γ’γ³γγ§οΏ½?οΏ½γγγ¬γ³γγΌγγγγγ«δ½Ώη¨γ§γγ abort signalγ
- ηη₯ε―θ½
θΏγε€
renderToReadableStream γ― Promise γθΏγγΎγγ
- γ·γ§γ«οΏ½?γ¬γ³γγΌγζεγγε ΄εγPromise γ―θͺγΏεγε―θ½γͺ Web Stream γ«θ§£ζ±Ί (resolve) γγγΎγγ
- γ·γ§γ«οΏ½?γ¬γ³γγΌγε€±ζγγε ΄εγPromise γ―ζε¦ (reject) γγγΎγγγγγδ½Ώη¨γγ¦γγ©γΌγ«γγγ―γ·γ§γ«γεΊεγγΎγγ
θΏγγγγΉγγͺγΌγ γ«γ―δ»₯δΈοΏ½?θΏ½ε οΏ½?γγγγγ£γεε¨γγΎγγ
allReady: γ·γ§γ«γ¨γγΉγ¦οΏ½?θΏ½ε γ³γ³γγ³γοΏ½?δΈ‘ζΉγε«γγγΉγ¦οΏ½?γ¬γ³γγΌγοΏ½?οΏ½δΊγγγ¨γγ«θ§£ζ±Ίγγγ Promiseγγ―γγΌγ©γιηηζεγοΏ½?ε ΄εγγ¬γΉγγ³γΉγθΏγεγ«stream.allReadyγ await γ§γγΎγγγγγθ‘γγ¨γγγ°γ¬γγ·γγͺγγΌγγ£γ³γ°γγͺγγͺγγγΉγγͺγΌγ γ«γ―ζη΅ηγͺ HTML γε«γΎγγγγγ«γͺγγΎγγ
δ½Ώη¨ζ³
React γγͺγΌγ HTML γ¨γγ¦θͺγΏεγε―θ½γͺ Web Stream γ«γ¬γ³γγΌγγ
renderToReadableStream γεΌγ³εΊγγ¦γReact γγͺγΌγ HTML γ¨γγ¦θͺγΏεγε―θ½γͺ Web Stream γ«γ¬γ³γγΌγγΎγγ
import { renderToReadableStream } from 'react-dom/server';
async function handler(request) {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}γ«γΌγγ³γ³γγΌγγ³γ γ¨ γγΌγγΉγγ©γγ <script> γγΉοΏ½?γͺγΉγγζοΏ½?οΏ½γγεΏ
θ¦γγγγΎγγγ«γΌγγ³γ³γγΌγγ³γγ―γγ«γΌγοΏ½? <html> γΏγ°γε«γγ γγγ₯γ‘γ³γε
¨δ½γθΏγγγγ«γγΎγγ
δΎγγ°δ»₯δΈοΏ½?γγγͺε½’γ«γͺγγ§γγγγ
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>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}React γ― doctype γ¨γγͺγγζοΏ½?οΏ½γγγγΌγγΉγγ©γγ <script> γΏγ°γη΅ζοΏ½? HTML γΉγγͺγΌγ γ«ζ³¨ε
₯γγΎγγ
<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>γ―γ©γ€γ’γ³γε΄γ§γ―γγγΌγγΉγγ©γγγΉγ―γͺγγγ― hydrateRoot γεΌγ³εΊγγ¦ document ε
¨δ½οΏ½?γγ€γγ¬γΌγ·γ§γ³γθ‘γεΏ
θ¦γγγγΎγγ
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);γγγ«γγγγ΅γΌγγ§ηζγγγ HTML γ«γ€γγ³γγͺγΉγγθΏ½ε γγγζδ½ε―θ½γ«γͺγγΎγγ
γγγ«ζ·±γη₯γ
γγ«γεΎγ«γζη΅ηγͺγ’γ»γγ URLοΌJavaScript γ CSS γγ‘γ€γ«γͺγ©οΌγ«γ―γγγγγ·γ₯εγθ‘γγγΎγγδΎγγ°γstyles.css γ styles.123456.css γ«γͺγγγ¨γγγγΎγγιηγͺγ’γ»γγοΏ½?γγ‘γ€γ«εγγγγ·γ₯εγγγγ¨γ§γεγγ’γ»γγγγγ«γγγ¨γ«η°γͺγγγ‘γ€γ«εγ«γͺγγγ¨γδΏθ¨ΌγγγΎγγγγγζη¨γͺοΏ½?γ―γγγηΉοΏ½?οΏ½οΏ½?εεγζγ€γγ‘γ€γ«οΏ½?ε
οΏ½?οΏ½γδΈε€γ«γͺγγιηγͺγ’γ»γγοΏ½?ι·ζηγͺγγ£γγ·γ³γ°γοΏ½?οΏ½ε
¨γ«θ‘γγγγγ«γͺγγγγ§γγ
γγγγγγ«γεΎγΎγ§γ’γ»γγ URL γεγγγͺγε ΄εγγγγγγ½γΌγΉγ³γΌγγ«ε«γγγγ¨γγ§γγΎγγγδΎγγ°γε
γ»γ©οΏ½?γγγ« JSX γ« "/styles.css" γγγΌγγ³γΌγγ£γ³γ°γγζΉζ³γ―εδ½γγΎγγγγ½γΌγΉγ³γΌγγ«γγγγε«γγͺγγγγ«γγγγγγ«γΌγγ³γ³γγΌγγ³γγγprops η΅η±γ§ζΈ‘γγγγγγγγοΏ½?οΏ½ιοΏ½?γγ‘γ€γ«εγθͺγΏεγγγγ«γγγγ¨γγ§γγΎγγ
export default function App({ assetMap }) {
return (
<html>
<head>
<title>My app</title>
<link rel="stylesheet" href={assetMap['styles.css']}></link>
</head>
...
</html>
);
}γ΅γΌγδΈγ§γ―γ<App assetMap={assetMap} /> οΏ½?γγγ«γ¬γ³γγΌγγγ’γ»γγ URL γε«γ assetMap γζΈ‘γγΎγγ
// You'd need to get this JSON from your build tooling, e.g. read it from the build output.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
async function handler(request) {
const stream = await renderToReadableStream(<App assetMap={assetMap} />, {
bootstrapScripts: [assetMap['/main.js']]
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}γ΅γΌγγ§ <App assetMap={assetMap} /> οΏ½?γγγ«γ¬γ³γγΌγγ¦γγοΏ½?γ§γγ―γ©γ€γ’γ³γγ§γ assetMap γδ½Ώγ£γ¦γ¬γ³γγΌγγ¦γγ€γγ¬γΌγ·γ§γ³γ¨γ©γΌγιΏγγεΏ
θ¦γγγγΎγγγοΏ½?γγγ«γ―δ»₯δΈοΏ½?γγγ« assetMap γγ·γͺγ’γ©γ€γΊγγ¦γ―γ©γ€γ’γ³γγ«ζΈ‘γγΎγγ
// You'd need to get this JSON from your build tooling.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
async function handler(request) {
const stream = await renderToReadableStream(<App assetMap={assetMap} />, {
// Careful: It's safe to stringify() this because this data isn't user-generated.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['/main.js']],
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}δΈθ¨οΏ½?δΎγ§γ―γbootstrapScriptContent γͺγγ·γ§γ³γδ½Ώγ£γ¦<script> γΏγ°γθΏ½ε γγ¦γγ―γ©γ€γ’γ³γδΈγ§γ°γγΌγγ« window.assetMap ε€ζ°γγ»γγγγ¦γγΎγγγγγ«γγγγ―γ©γ€γ’γ³γοΏ½?γ³γΌγγεγ assetMap γθͺγΏεγγγγγ«γͺγγΎγγ
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App assetMap={window.assetMap} />);γ―γ©γ€γ’γ³γγ¨γ΅γΌγοΏ½?δΈ‘ζΉγ props γ¨γγ¦εγ assetMap γδ½Ώγ£γ¦ App γγ¬γ³γγΌγγγγγγγ€γγ¬γΌγ·γ§γ³γ¨γ©γΌγ―ηΊηγγΎγγγ
γγΌγγι²γγ«γ€γγ¦γ³γ³γγ³γγγΉγγͺγΌγγ³γ°γγ
γΉγγͺγΌγγ³γ°γ«γγγγ΅γΌγδΈγ§γγΉγ¦οΏ½?γγΌγΏγγγΌγγγγεγ«γγ¦γΌγΆγγ³γ³γγ³γγθ¦ε§γγγγγγγ«γγγγ¨γγ§γγΎγγδΎγγ°δ»₯δΈοΏ½?γγγͺγγγγ£γΌγ«γγΌγΈγγγγγ«γγΌγγγ¬γ³γγ»εηγε«γΎγγγ΅γ€γγγΌγζη¨ΏοΏ½?γͺγΉγγ葨瀺γγ¦γγγ¨γγγθγγΎγγγγ
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Posts />
</ProfileLayout>
);
}γγγ§γ<Posts /> οΏ½?γγΌγΏγθͺγΏθΎΌγοΏ½?γ«ζιγγγγγ¨γγΎγγγγηζ³ηγ«γ―γζη¨ΏοΏ½?θͺγΏθΎΌγΏγεΎ
γ€γγ¨γͺγγγγγγ£γΌγ«γγΌγΈοΏ½?οΏ½?οΏ½γοΏ½?γ³γ³γγ³γγγ¦γΌγΆγ«θ‘¨η€Ίγγγγ§γγγγγγγοΏ½?οΏ½ηΎγγγ«γ―γPosts γ <Suspense> γγ¦γ³γγͺγ§ε²γΏγΎγγ
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}γγγ«γγ React γ«γPosts οΏ½?γγΌγΏγθͺγΏθΎΌγΎγγεγ« HTML γγΉγγͺγΌγγ³γ°ιε§γγγγζη€ΊγγΎγγReact γ―γΎγγγγΌγγ£γ³γ°γγ©γΌγ«γγγ― (PostsGlimmer) οΏ½? HTML γιδΏ‘γγΎγγ欑㫠Posts οΏ½?γγΌγΏθͺγΏθΎΌγΏγοΏ½?οΏ½δΊγγγγοΏ½?οΏ½γοΏ½? HTML γ¨γγγΌγγ£γ³γ°γγ©γΌγ«γγγ―γγγγ§οΏ½?ζγγγγοΏ½?γ€γ³γ©γ€γ³ <script> γΏγ°γιδΏ‘γγΎγγγ¦γΌγΆγγθ¦γγ¨γγγΌγΈγ«γ―γΎγ PostsGlimmer γ葨瀺γγγεΎγγγγγ Posts γ«οΏ½?γζγγγγ¨γ«γͺγγΎγγ
γγγ«γγγη΄°γγθͺγΏθΎΌγΏγ·γΌγ±γ³γΉγεΆεΎ‘γγγγγ«<Suspense> γγ¦γ³γγͺγγγΉγγγγγγ¨γγ§γγΎγγ
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}γοΏ½?δΎγ§γ―γReact γ―γγΌγΈοΏ½?γΉγγͺγΌγγ³γ°γγγγ«η΄ ζ©γιε§γ§γγΎγγζεγ«γ¬γ³γγΌγοΏ½?οΏ½δΊγγ¦γγεΏ
θ¦γγγοΏ½?γ―γ<Suspense> γγ¦γ³γγͺγ§ε²γΎγγ¦γγͺγ ProfileLayout γ¨ ProfileCover γ γγ§γγSidebarγFriendsγPhotos γγγΌγΏγθͺγΏθΎΌγεΏ
θ¦γγγε ΄εγReact γ― BigSpinner οΏ½?γγ©γΌγ«γγγ― HTML γ代γγγ«ιδΏ‘γγΎγγγοΏ½?εΎγγγε€γοΏ½?γγΌγΏγε©η¨ε―θ½γ«γͺγγ«γ€γγγγε€γοΏ½?γ³γ³γγ³γγ葨瀺γγγ¦γγγζη΅ηγ«γγΉγ¦γ葨瀺γγγΎγγ
γΉγγͺγΌγγ³γ°γ§γ―γγγ©γ¦γΆγ§ React θͺδ½γθͺγΏθΎΌγΎγγοΏ½?γεΎ
γ€εΏ
θ¦γγγ’γγͺγζδ½ε―θ½γ«γͺγοΏ½?γεΎ
γ€εΏ
θ¦γγγγΎγγγγ΅γΌγγγοΏ½? HTML γ³γ³γγ³γγ―γγγγγ <script> γΏγ°γθͺγΏθΎΌγΎγγεγ«γγγ°γ¬γγ·γγ«θ‘¨η€ΊγγγΎγγ
HTML γΉγγͺγΌγγ³γ°οΏ½?εδ½γ«γ€γγ¦θ©³γγθͺγ
γ·γ§γ«γ«δ½γε«γγγοΏ½?ζοΏ½?οΏ½
γ’γγͺοΏ½?ε
¨ <Suspense> γγ¦γ³γγͺγγε€γ«γγι¨εοΏ½?γγ¨γγ·γ§γ« (shell) γ¨εΌγ³γΎγγ
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}γγγγγ¦γΌγΆγ«θ¦γγζεοΏ½?γγΌγγ£γ³γ°δΈηΆζ γζ±ΊοΏ½?οΏ½γγΎγγ
<ProfileLayout>
<ProfileCover />
<BigSpinner />
</ProfileLayout>γγγ«γΌγι¨εγ§γ’γγͺε
¨δ½γ <Suspense> γγ¦γ³γγͺγ§γ©γγγγ¦γγΎγγ¨γγ·γ§γ«γ¨γγ¦γ―γοΏ½?γΉγγγ γγε«γΎγγγγ¨γ«γͺγγΎγγγγγγγγ―γγΎγεΏ«ι©γͺγ¦γΌγΆδ½ι¨γ«γ―γͺγγΎγγγε€§γγͺγΉγγγη»ι’γ«θ‘¨η€Ίγγγγγ¨γ―γγγε°γγ γεΎ
γ£γ¦γγοΏ½?οΏ½ιοΏ½?γ¬γ€γ’γ¦γγ葨瀺γγγγ¨γγγι
γδΈεΏ«γ«ζγγγγγγγ§γγγγγγ£γ¦γ<Suspense> ε’ηγ―ι©εγ«ι
οΏ½?γγ¦γγ·γ§γ«γγγγγ«γγ€οΏ½?οΏ½ε
¨γ«ζγγγγγγγ«εΏ
θ¦γγγγ§γγγγδΎγγ°γγΌγΈγ¬γ€γ’γ¦γε
¨δ½οΏ½?γΉγ±γ«γγ³οΏ½?γγγͺγοΏ½?γ§γγ
ιεζοΏ½? renderToReadableStream εΌγ³εΊγγ stream γ«θ§£ζ±ΊγγγοΏ½?γ―γγ·γ§γ«ε
¨δ½οΏ½?γ¬γ³γγΌγη΅δΊγγη΄εΎγ§γγιεΈΈγγοΏ½? stream γδ½Ώγ£γ¦γ¬γΉγγ³γΉγδ½ζγγ¦θΏγγγ¨γ§γγΉγγͺγΌγγ³γ°γιε§γγΎγγ
async function handler(request) {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}γοΏ½? stream γθΏγγγζηΉγ§γ―γγγΉγγγγ <Suspense> γγ¦γ³γγͺε
οΏ½?γ³γ³γγΌγγ³γγ―γΎγ γγΌγΏγγγΌγδΈγγγγγΎγγγ
γ΅γΌγδΈγ§οΏ½?γ―γ©γγ·γ₯γγ°οΏ½?θ¨ι²
γγγ©γ«γγ§γ―γγ΅γΌγδΈοΏ½?γγΉγ¦οΏ½?γ¨γ©γΌγ―γ³γ³γ½γΌγ«γ«γγ°γ¨γγ¦θ¨ι²γγγΎγγγοΏ½?ζεγγͺγΌγγΌγ©γ€γγγ¦γγ―γ©γγ·γ₯γ¬γγΌγγγγ°γ¨γγ¦θ¨ι²γγγγ¨γγ§γγΎγγ
async function handler(request) {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}γ«γΉγΏγ οΏ½? onError οΏ½?οΏ½θ£
γζδΎγγε ΄εγδΈθ¨οΏ½?γγγ«γ¨γ©γΌγγ³γ³γ½γΌγ«γ«γγγ°γ¨γγ¦θ¨ι²γγγγ¨γεΏγγͺγγ§γγ γγγ
γ·γ§γ«ε οΏ½?γ¨γ©γΌγγοΏ½?εΎ©εΈ°
γοΏ½?δΎγ§γ―γγ·γ§γ«γ¨γγ¦ ProfileLayoutγProfileCoverγγγγ³ PostsGlimmer γε«γΎγγ¦γγΎγγ
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}γγγοΏ½?γ³γ³γγΌγγ³γγγ¬γ³γγΌγγιγ«γ¨γ©γΌγηΊηγγε ΄εγReact γ―γ―γ©γ€γ’γ³γγ«ιδΏ‘γ§γγζε³οΏ½?γγ HTML γζδΎγ§γγΎγγγζη΅ζοΏ½?οΏ½γ¨γγ¦γrenderToReadableStream εΌγ³εΊγγ try...catch γ§γ©γγγγ¦γγ΅γΌγγ¬γ³γγͺγ³γ°γ«δΎεγγͺγγγ©γΌγ«γγγ― HTML γιδΏ‘γγΎγγγγ
async function handler(request) {
try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}γ·γ§γ«οΏ½?ηζδΈγ«γ¨γ©γΌγηΊηγγε ΄εγonError γ¨ catch γγγγ―οΏ½?δΈ‘ζΉγηΊη«γγΎγγγ¨γ©γΌγ¬γγΌγγ«γ― onError γδ½Ώη¨γγγγ©γΌγ«γγγ―οΏ½? HTML γγγ₯γ‘γ³γγιδΏ‘γγγγγ«γ― catch γγγγ―γδ½Ώη¨γγΎγγγγ©γΌγ«γγγ― HTML γ―γ¨γ©γΌγγΌγΈγ§γγεΏ
θ¦γ―γγγΎγγγ代γγγ«γγ―γ©γ€γ’γ³γοΏ½?γΏγ§γ’γγͺγγ¬γ³γγΌγγγγοΏ½?代ζΏγ·γ§γ«γε«γγγγ¨γε―θ½γ§γγ
γ·γ§γ«ε€οΏ½?γ¨γ©γΌγγοΏ½?εΎ©εΈ°
γοΏ½?δΎγ§γ―γ<Posts /> γ³γ³γγΌγγ³γγ― <Suspense> γ§γ©γγγγγ¦γγγγγγ·γ§γ«οΏ½?δΈι¨γ§γ―γγγΎγγγ
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}Posts γ³γ³γγΌγγ³γγΎγγ―γοΏ½?ε
ι¨οΏ½?γ©γγγ§γ¨γ©γΌγηΊηγγε ΄εγReact γ―γγγγοΏ½?εΎ©εΈ°γ試γΏγΎγγ
- ζγθΏγ
<Suspense>γγ¦γ³γγͺ (PostsGlimmer) οΏ½?γγΌγγ£γ³γ°γγ©γΌγ«γγγ―γ HTML γ¨γγ¦εΊεγγΎγγ - γ΅γΌγδΈγ§
PostsοΏ½?γ³γ³γγ³γγγ¬γ³γγΌγγγγ¨γγοΏ½?γ諦γγΎγγ - JavaScript γ³γΌγγγ―γ©γ€γ’γ³γδΈγ§γγΌγγγγγ¨γReact γ―γ―γ©γ€γ’γ³γδΈγ§
PostsοΏ½?γ¬γ³γγΌγε試θ‘γγΎγγ
γ―γ©γ€γ’γ³γδΈγ§ Posts οΏ½?γ¬γ³γγΌγε試θ‘γγ¦εεΊ¦ε€±ζγγε ΄εγReact γ―γ―γ©γ€γ’γ³γδΈγ§γ¨γ©γΌγγΉγγΌγγΎγγγ¬γ³γγΌδΈγ«γΉγγΌγγγδ»οΏ½?γγΉγ¦οΏ½?γ¨γ©γΌγ¨εζ§γ«γζγθΏγθ¦ͺοΏ½?γ¨γ©γΌγγ¦γ³γγͺγγ¦γΌγΆγ«γ¨γ©γΌγγ©οΏ½?γγγ«ζη€Ίγγγγζ±ΊοΏ½?οΏ½γγΎγγγ€γΎγγγ¨γ©γΌγεΎ©εΈ°δΈθ½γ§γγγγ¨γη’ΊοΏ½?οΏ½γγγΎγ§γγ¦γΌγΆγ«γ―γγΌγγ£γ³γ°γ€γ³γΈγ±γΌγΏγθ¦γγγγ¨γ«γͺγγΎγγ
γ―γ©γ€γ’γ³γδΈγ§οΏ½? Posts οΏ½?γ¬γ³γγΌε試θ‘γζεγγε ΄εγγ΅γΌγγγοΏ½?γγΌγγ£γ³γ°γγ©γΌγ«γγγ―γ―γ―γ©γ€γ’γ³γγ§οΏ½?γ¬γ³γγΌεΊεγ§οΏ½?γζγγγγΎγγγ¦γΌγΆγ«γ―γ΅γΌγγ¨γ©γΌγηΊηγγγγ¨γ―εγγγΎγγγγγ γγγ΅γΌγοΏ½? onError γ³γΌγ«γγγ―γ¨γ―γ©γ€γ’γ³γοΏ½? onRecoverableError γ³γΌγ«γγγ―γηΊη«γγγγγγ¨γ©γΌγ«γ€γγ¦ιη₯γεγεγγγ¨γγ§γγΎγγ
γΉγγΌγΏγΉγ³γΌγοΏ½?θ¨οΏ½?οΏ½
γΉγγͺγΌγγ³γ°γ«γ―γγ¬γΌγγͺγγεε¨γγΎγγγ¦γΌγΆγγ³γ³γγ³γγζ©γθ¦γγγ¨γγ§γγγγγ«γγ§γγγ γζ©γγγΌγΈοΏ½?γΉγγͺγΌγγ³γ°γιε§γγγγ§γγγγδΈζΉγ§γγΉγγͺγΌγγ³γ°γιε§γγγ¨γγ¬γΉγγ³γΉοΏ½?γΉγγΌγΏγΉγ³γΌγγθ¨οΏ½?οΏ½γγγγ¨γγ§γγͺγγͺγγΎγγ
γ·γ§γ«οΌγγΉγ¦οΏ½? <Suspense> γγ¦γ³γγͺγγδΈοΏ½?ι¨εοΌγ¨γγδ»₯ε€οΏ½?γ³γ³γγ³γγ«γ’γγͺγεε²γγγγ¨γ§γγοΏ½?ει‘γ―γγ§γ«ι¨εηγ«θ§£ζ±Ίγγγ¦γγΎγγγ·γ§γ«γ§γ¨γ©γΌγηΊηγγε ΄εγcatch γγγγ―γοΏ½?οΏ½θ‘γγγγ¨γ©γΌοΏ½?γΉγγΌγΏγΉγ³γΌγγγ»γγγγγγ¨γγ§γγΎγγγγδ»₯ε€οΏ½?ε ΄εγ―γγ’γγͺγγ―γ©γ€γ’γ³γδΈγ§εΎ©εΈ°γ§γγε―θ½ζ§γγγγγγβOKβ γιδΏ‘γ§γγοΏ½?γ§γγ
async function handler(request) {
try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}γ·γ§γ«οΏ½?ε€ε΄οΌγ€γΎγ <Suspense> γγ¦γ³γγͺοΏ½?ε
ε΄οΌοΏ½?γ³γ³γγΌγγ³γγ§γ¨γ©γΌγηΊηγγε ΄εγReact γ―γ¬γ³γγΌγεζ’γγΎγγγγγγ―γonError γ³γΌγ«γγγ―γηΊη«γγγοΏ½?οΏ½?γγ³γΌγγ― catch γγγγ―γ«ε
₯γγγ«οΏ½?οΏ½θ‘γηΆγγγγ¨γζε³γγΎγγγγγ―δΈθ¨γ§θͺ¬ζγγγγγ«γReact γγοΏ½?γ¨γ©γΌγγ―γ©γ€γ’γ³γδΈγ§εΎ©εΈ°γγγγ¨γγγγγ§γγ
γγ γγζγΏγ§γγγ°γδ½γγοΏ½?γ¨γ©γΌγθ΅·γγγ¨γγδΊοΏ½?οΏ½γ«εΊγ₯γγγΉγγΌγΏγΉγ³γΌγγθ¨οΏ½?οΏ½γγγγ¨γγ§γγΎγγ
async function handler(request) {
try {
let didError = false;
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: didError ? 500 : 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}γγγ―γεζοΏ½?γ·γ§γ«γ³γ³γγ³γοΏ½?ηζδΈγ«ζ’γ«γ·γ§γ«οΏ½?ε€ε΄γ§ηΊηγγγ¨γ©γΌγζζγ§γγγ γγͺοΏ½?γ§γοΏ½?οΏ½ε ¨γ§γ―γγγΎγγγγγγ³γ³γγ³γγ§γ¨γ©γΌγηΊηγγγγ©γγγη₯γγγ¨γιθ¦γ§γγγ°γγγγγ·γ§γ«γ«η§»εγγγγγ¨γγ§γγΎγγ
γ¨γ©γΌοΏ½?οΏ½?ι‘γ«γγ£γ¦ε¦ηγεγγ
γ«γΉγΏγ οΏ½? Error γ΅γγ―γ©γΉγδ½ζγγinstanceof ζΌοΏ½?οΏ½εγδ½Ώη¨γγ¦γ©γγͺγ¨γ©γΌγγΉγγΌγγγγγγγ§γγ―γγγγ¨γγ§γγΎγγδΎγγ°γγ«γΉγΏγ οΏ½? NotFoundError γοΏ½?οΏ½ηΎ©γγγ³γ³γγΌγγ³γγγγγγγΉγγΌγγγγ¨γγ§γγΎγγγοΏ½?εΎγonError γ§γ¨γ©γΌγδΏεγγ¦γγγγ¨γ©γΌοΏ½?οΏ½?ι‘γ«εΏγγ¦γ¬γΉγγ³γΉγθΏγεγ«δ½γη°γͺγε¦ηγθ‘γγγ¨γγ§γγΎγγ
async function handler(request) {
let didError = false;
let caughtError = null;
function getStatusCode() {
if (didError) {
if (caughtError instanceof NotFoundError) {
return 404;
} else {
return 500;
}
} else {
return 200;
}
}
try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
caughtError = error;
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: getStatusCode(),
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: getStatusCode(),
headers: { 'content-type': 'text/html' },
});
}
}γγγγ·γ§γ«γεΊεγγ¦γΉγγͺγΌγγ³γ°γιε§γγ¦γγΎγγ¨γγΉγγΌγΏγΉγ³γΌγγε€ζ΄γ§γγͺγγͺγγΎγοΏ½?γ§ζ³¨ζγγ¦γγ γγγ
γ―γγΌγ©γιηηζεγγ«ε ¨γ³γ³γγ³γοΏ½?θͺγΏθΎΌγΏγεΎ ζ©γγ
γΉγγͺγΌγγ³γ°γ«γγγε©η¨ε―θ½γ«γͺγ£γι γ§γ³γ³γγ³γγγ¦γΌγΆγθ¦γγγγγ«γͺγγγγγ¦γΌγΆδ½ι¨γεδΈγγΎγγ
γγγγγ―γγΌγ©γγγΌγΈγθ¨ͺγγε ΄εγγγγ«γζγ«γγΌγΈγηζγγ¦γγε ΄εγ«γ―γγ³γ³γγ³γγεΎγ γ«θ‘¨η€ΊγγοΏ½?γ§γ―γͺγγγγΉγ¦οΏ½?γ³γ³γγ³γγζεγ«γγΌγγγ¦γγζη΅ηγͺ HTML εΊεγηζγγγγ§γγγγ
Promise γ§γγ stream.allReady γ await γγγγ¨γ§γγγΉγ¦οΏ½?γ³γ³γγ³γγθͺγΏθΎΌγΎγγγΎγ§εΎ
ζ©γθ‘γγγ¨γγ§γγΎγγ
async function handler(request) {
try {
let didError = false;
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
let isCrawler = // ... depends on your bot detection strategy ...
if (isCrawler) {
await stream.allReady;
}
return new Response(stream, {
status: didError ? 500 : 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}ιεΈΈοΏ½?γ¦γΌγΆγ―γγΉγγͺγΌγ γ§θͺγΏθΎΌγΎγγγ³γ³γγ³γγοΏ½?οΏ½ιηγ«εγεγγΎγγγ―γγΌγ©γ―γε ¨γγΌγΏγθͺγΏθΎΌγΎγγεΎοΏ½?ζη΅ηγͺ HTML εΊεγεγεγγΎγγγγγγγγγ―γ―γγΌγ©γγγΉγ¦οΏ½?γγΌγΏγεΎ γ€εΏ θ¦γγγγγ¨γζε³γγγοΏ½?δΈγ«γ―θͺγΏθΎΌγΏγι γγοΏ½?γγ¨γ©γΌγηΊηγγγοΏ½?γε«γΎγγγγγγγΎγγγγ’γγͺγ±γΌγ·γ§γ³γ«γγ£γ¦γ―γγ―γγΌγ©γ«γγ·γ§γ«γιδΏ‘γγγγ¨γιΈζγγ¦γζ§γγΎγγγ
γ΅γΌγγ¬γ³γγͺγ³γ°οΏ½?δΈζ’
γΏγ€γ γ’γ¦γεΎγ«γ΅γΌγγ¬γ³γγͺγ³γ°γγ諦γγγγγγ«εΌ·εΆγγγγ¨γγ§γγΎγγ
async function handler(request) {
try {
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, 10000);
const stream = await renderToReadableStream(<App />, {
signal: controller.signal,
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
// ...React γ―γοΏ½?οΏ½γοΏ½?γγΌγγ£γ³γ°δΈγγ©γΌγ«γγγ―γ HTML γ¨γγ¦η΄γ‘γ«εΊεγγγ―γ©γ€γ’γ³γδΈγ§οΏ½?οΏ½γγγ¬γ³γγΌγγγγ¨θ©¦γΏγΎγγ