renderToPipeableStream
renderToPipeableStream
㯠React ããªãŒããã€ãå¯èœãª Node.js ã¹ããªãŒã ã«ã¬ã³ããŒããŸãã
const { pipe, abort } = renderToPipeableStream(reactNode, options?)
- ãªãã¡ã¬ã³ã¹
- 䜿çšæ³
- React ããªãŒã HTML ãšã㊠Node.js ã¹ããªãŒã ã«ã¬ã³ããŒãã
- ããŒããé²ãã«ã€ããŠã³ã³ãã³ããã¹ããªãŒãã³ã°ãã
- ã·ã§ã«ã«äœãå«ãããã®æå®
- ãµãŒãäžã§ã®ã¯ã©ãã·ã¥ãã°ã®èšé²
- ã·ã§ã«å ã®ãšã©ãŒããã®åŸ©åž°
- ã·ã§ã«å€ã®ãšã©ãŒããã®åŸ©åž°
- ã¹ããŒã¿ã¹ã³ãŒãã®èšå®
- ãšã©ãŒã®çš®é¡ã«ãã£ãŠåŠçãåãã
- ã¯ããŒã©ãéççæåãã«å šã³ã³ãã³ãã®èªã¿èŸŒã¿ãåŸ æ©ãã
- ãµãŒãã¬ã³ããªã³ã°ã®äžæ¢
ãªãã¡ã¬ã³ã¹
renderToPipeableStream(reactNode, options?)
renderToPipeableStream
ãåŒã³åºããŠãReact ããªãŒã HTML ãšã㊠Node.js ã¹ããªãŒã ã«ã¬ã³ããŒããŸãã
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
ã¯ã©ã€ã¢ã³ãåŽã§ã¯ããã®ããã«ãµãŒãçæããã 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-src
Content-Security-Policy ãçšããŠã¹ã¯ãªãããèš±å¯ããããã®nonce
æååã - çç¥å¯èœ
onAllReady
: ã·ã§ã«ãšãã¹ãŠã®è¿œå ã³ã³ãã³ãã®äž¡æ¹ãå«ããã¹ãŠã®ã¬ã³ããŒãå®äºãããšãã«åŒã³åºãããã³ãŒã«ããã¯ãã¯ããŒã©ãéççæåãã®å Žåã«ãonShellReady
ã®ä»£ããã«å©çšã§ããŸããããããã¹ããªãŒãã³ã°ãéå§ããå Žåãããã°ã¬ãã·ããªããŒãã£ã³ã°ããªããªããã¹ããªãŒã ã«ã¯æçµç㪠HTML ãå«ãŸããããã«ãªããŸãã - çç¥å¯èœ
onError
: ãµãŒããšã©ãŒãçºçãããã³ã«çºç«ããã³ãŒã«ããã¯ã埩垰å¯èœãªãšã©ãŒã®å Žåãããã§ãªããšã©ãŒã®å ŽåããããŸããããã©ã«ãã§ã¯console.error
ã®ã¿ãåŒã³åºããŸãããããäžæžãããŠã¯ã©ãã·ã¥ã¬ããŒãããã°ã«èšé²ããå Žåã§ãconsole.error
ãåŒã³åºãããã«ããŠãã ããããŸããã·ã§ã«ãåºåãããåã«ã¹ããŒã¿ã¹ã³ãŒãã調æŽããããã«ã䜿çšã§ããŸãã - çç¥å¯èœ
onShellReady
: åæã·ã§ã«ãã¬ã³ããŒãããçŽåŸã«åŒã³åºãããã³ãŒã«ããã¯ãããã§ã¹ããŒã¿ã¹ã³ãŒãã®ã»ãããè¡ããpipe
ãã³ãŒã«ããŠã¹ããªãŒãã³ã°ãéå§ã§ããŸããReact ã¯ã·ã§ã«ãéä¿¡ããåŸã«ãè¿œå ã³ã³ãã³ããšãããŒãã£ã³ã°ãã©ãŒã«ããã¯ãããã§çœ®æããããã®ã€ã³ã©ã€ã³<script>
ã¿ã°ãã¹ããªãŒãã³ã°ããŸãã - çç¥å¯èœ
onShellError
: åæã·ã§ã«ã®ã¬ã³ããŒäžã«ãšã©ãŒãçºçãããšåŒã³åºãããã³ãŒã«ããã¯ãåŒæ°ãšããŠãšã©ãŒãåãåããŸããã¹ããªãŒã ããã®ãã€ãåéä¿¡ã¯ãŸã èµ·ããŠããããonShellReady
ãonAllReady
ã¯ã³ãŒã«ãããªããªãããããã©ãŒã«ããã¯çšã® HTML ã·ã§ã«ãåºåããããšãã§ããŸãã - çç¥å¯èœ
progressiveChunkSize
: ãã£ã³ã¯ã®ãã€ãæ°ãããã©ã«ãã®æšè«æ¹æ³ã«ã€ããŠã¯ãã¡ããåç §ããŠãã ããã
- çç¥å¯èœ
è¿ãå€
renderToPipeableStream
ã¯ä»¥äžã® 2 ã€ã®ã¡ãœãããå«ãã ãªããžã§ã¯ããè¿ããŸãã
pipe
: HTML ã Node.js ã® Writable ã¹ããªãŒã ã«åºåããŸããpipe
ã®åŒã³åºãã¯ãã¹ããªãŒãã³ã°ãæå¹ã«ãããå Žåã¯onShellReady
ã§ãã¯ããŒã©ãéççæåãã®åºåãè¡ãããå Žåã¯onAllReady
ã§è¡ã£ãŠãã ãããabort
: ãµãŒãã§ã®ã¬ã³ããŒãäžæ¢ããŠã¯ã©ã€ã¢ã³ãã§æ®ããã¬ã³ããŒããããã«äœ¿çšããŸãã
䜿çšæ³
React ããªãŒã HTML ãšã㊠Node.js ã¹ããªãŒã ã«ã¬ã³ããŒãã
renderToPipeableStream
ãåŒã³åºããŠãReact ããªãŒã HTML ãšã㊠Node.js ã¹ããªãŒã ã«ã¬ã³ããŒããŸãã
import { renderToPipeableStream } from 'react-dom/server';
// The route handler syntax depends on your backend framework
app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
});
ã«ãŒãã³ã³ããŒãã³ã ãš ããŒãã¹ãã©ãã <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>
...
<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'
};
app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App assetMap={assetMap} />, {
bootstrapScripts: [assetMap['main.js']],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
});
ãµãŒã㧠<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'
};
app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<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']],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
});
äžèšã®äŸã§ã¯ã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>
å¢çã¯é©åã«é
眮ããŠãã·ã§ã«ããããã«ãã€å®å
šã«æããããããã«å¿
èŠãããã§ããããäŸãã°ããŒãžã¬ã€ã¢ãŠãå
šäœã®ã¹ã±ã«ãã³ã®ãããªãã®ã§ãã
ã·ã§ã«å
šäœã®ã¬ã³ããŒãå®äºãããšãã« onShellReady
ã³ãŒã«ããã¯ãåŒã³åºãããŸããéåžžãããã§ã¹ããªãŒãã³ã°ãéå§ããŸãã
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
onShellReady
ãåŒã³åºãããæç¹ã§ã¯ããã¹ãããã <Suspense>
ããŠã³ããªå
ã®ã³ã³ããŒãã³ãã¯ãŸã ããŒã¿ãããŒãäžãããããŸããã
ãµãŒãäžã§ã®ã¯ã©ãã·ã¥ãã°ã®èšé²
ããã©ã«ãã§ã¯ããµãŒãäžã®ãã¹ãŠã®ãšã©ãŒã¯ã³ã³ãœãŒã«ã«ãã°ãšããŠèšé²ãããŸãããã®æåããªãŒããŒã©ã€ãããŠãã¯ã©ãã·ã¥ã¬ããŒãããã°ãšããŠèšé²ããããšãã§ããŸãã
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
ã«ã¹ã¿ã ã® onError
å®è£
ãæäŸããå Žåãäžèšã®ããã«ãšã©ãŒãã³ã³ãœãŒã«ã«ããã°ãšããŠèšé²ããããšãå¿ããªãã§ãã ããã
ã·ã§ã«å ã®ãšã©ãŒããã®åŸ©åž°
ãã®äŸã§ã¯ãã·ã§ã«ãšã㊠ProfileLayout
ãProfileCover
ãããã³ PostsGlimmer
ãå«ãŸããŠããŸãã
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
ãããã®ã³ã³ããŒãã³ããã¬ã³ããŒããéã«ãšã©ãŒãçºçããå ŽåãReact ã¯ã¯ã©ã€ã¢ã³ãã«éä¿¡ã§ããæå³ã®ãã HTML ãæäŸã§ããŸãããæçµæ段ãšããŠãonShellError
ããªãŒããŒã©ã€ãããŠããµãŒãã¬ã³ããªã³ã°ã«äŸåããªããã©ãŒã«ãã㯠HTML ãéä¿¡ããŸãããã
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
ã·ã§ã«ã®çæäžã«ãšã©ãŒãçºçããå ŽåãonError
ãš onShellError
ã®äž¡æ¹ãçºç«ããŸãããšã©ãŒã¬ããŒãã«ã¯ onError
ã䜿çšãããã©ãŒã«ããã¯ã® HTML ããã¥ã¡ã³ããéä¿¡ããããã«ã¯ onShellError
ã䜿çšããŸãããã©ãŒã«ãã㯠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>
ããŠã³ããªããäžã®éšåïŒãšãã以å€ã®ã³ã³ãã³ãã«ã¢ããªãåå²ããããšã§ããã®åé¡ã¯ãã§ã«éšåçã«è§£æ±ºãããŠããŸããã·ã§ã«ã§ãšã©ãŒãçºçããå ŽåãonShellError
ã³ãŒã«ããã¯ãåŒã³åºããããšã©ãŒã®ã¹ããŒã¿ã¹ã³ãŒããã»ããããããšãã§ããŸãããã以å€ã®å Žåã¯ãã¢ããªãã¯ã©ã€ã¢ã³ãäžã§åŸ©åž°ã§ããå¯èœæ§ããããããâOKâ ãéä¿¡ã§ããã®ã§ãã
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = 200;
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
ã·ã§ã«ã®å€åŽïŒã€ãŸã <Suspense>
ããŠã³ããªã®å
åŽïŒã®ã³ã³ããŒãã³ãã§ãšã©ãŒãçºçããå ŽåãReact ã¯ã¬ã³ããŒãåæ¢ããŸãããããã¯ãonError
ã³ãŒã«ããã¯ã¯çºç«ãããã®ã®ããã®åŸ onShellError
ã§ã¯ãªã onShellReady
ãçºç«ããããšãæå³ããŸããããã¯äžèšã§èª¬æããããã«ãReact ããã®ãšã©ãŒãã¯ã©ã€ã¢ã³ãäžã§åŸ©åž°ããããšããããã§ãã
ãã ããæã¿ã§ããã°ãäœããã®ãšã©ãŒãèµ·ãããšããäºå®ã«åºã¥ããã¹ããŒã¿ã¹ã³ãŒããèšå®ããããšãã§ããŸãã
let didError = false;
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
ããã¯ãåæã®ã·ã§ã«ã³ã³ãã³ãã®çæäžã«æ¢ã«ã·ã§ã«ã®å€åŽã§çºçãããšã©ãŒãææã§ããã ããªã®ã§ãå®å šã§ã¯ãããŸãããããã³ã³ãã³ãã§ãšã©ãŒãçºçãããã©ãããç¥ãããšãéèŠã§ããã°ããããã·ã§ã«ã«ç§»åãããããšãã§ããŸãã
ãšã©ãŒã®çš®é¡ã«ãã£ãŠåŠçãåãã
ã«ã¹ã¿ã ã® Error
ãµãã¯ã©ã¹ãäœæããinstanceof
æŒç®åã䜿çšããŠã©ããªãšã©ãŒãã¹ããŒããããããã§ãã¯ããããšãã§ããŸããäŸãã°ãã«ã¹ã¿ã ã® NotFoundError
ãå®çŸ©ããã³ã³ããŒãã³ããããããã¹ããŒããããšãã§ããŸãããã®åŸãonError
ãonShellReady
, onShellError
ã®ã³ãŒã«ããã¯äžã§ãšã©ãŒã®çš®é¡ã«å¿ããŠç°ãªãåŠçãè¡ãããšãã§ããŸãã
let didError = false;
let caughtError = null;
function getStatusCode() {
if (didError) {
if (caughtError instanceof NotFoundError) {
return 404;
} else {
return 500;
}
} else {
return 200;
}
}
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = getStatusCode();
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = getStatusCode();
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
didError = true;
caughtError = error;
console.error(error);
logServerCrashReport(error);
}
});
ãããã·ã§ã«ãåºåããŠã¹ããªãŒãã³ã°ãéå§ããŠããŸããšãã¹ããŒã¿ã¹ã³ãŒããå€æŽã§ããªããªããŸãã®ã§æ³šæããŠãã ããã
ã¯ããŒã©ãéççæåãã«å šã³ã³ãã³ãã®èªã¿èŸŒã¿ãåŸ æ©ãã
ã¹ããªãŒãã³ã°ã«ãããå©çšå¯èœã«ãªã£ãé ã§ã³ã³ãã³ãããŠãŒã¶ãèŠããããã«ãªãããããŠãŒã¶äœéšãåäžããŸãã
ããããã¯ããŒã©ãããŒãžã蚪ããå Žåãããã«ãæã«ããŒãžãçæããŠããå Žåã«ã¯ãã³ã³ãã³ããåŸã ã«è¡šç€ºããã®ã§ã¯ãªããå šãŠã®ã³ã³ãã³ããæåã«ããŒãããŠããæçµç㪠HTML åºåãçæãããã§ãããã
onAllReady
ã³ãŒã«ããã¯ãçšããããšã§ããã¹ãŠã®ã³ã³ãã³ããèªã¿èŸŒãŸãããŸã§åŸ
æ©ãè¡ãããšãã§ããŸãã
let didError = false;
let isCrawler = // ... depends on your bot detection strategy ...
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
if (!isCrawler) {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
}
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onAllReady() {
if (isCrawler) {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
}
},
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
éåžžã®ãŠãŒã¶ã¯ãã¹ããªãŒã ã§èªã¿èŸŒãŸããã³ã³ãã³ãã段éçã«åãåããŸããã¯ããŒã©ã¯ãå šããŒã¿ãèªã¿èŸŒãŸããåŸã®æçµç㪠HTML åºåãåãåããŸããããããããã¯ã¯ããŒã©ããã¹ãŠã®ããŒã¿ãåŸ ã€å¿ èŠãããããšãæå³ãããã®äžã«ã¯èªã¿èŸŒã¿ãé ããã®ããšã©ãŒãçºçãããã®ãå«ãŸãããããããŸãããã¢ããªã±ãŒã·ã§ã³ã«ãã£ãŠã¯ãã¯ããŒã©ã«ãã·ã§ã«ãéä¿¡ããããšãéžæããŠãæ§ããŸããã
ãµãŒãã¬ã³ããªã³ã°ã®äžæ¢
ã¿ã€ã ã¢ãŠãåŸã«ãµãŒãã§ã®ã¬ã³ããŒããè«Šãããããã«åŒ·å¶ããããšãå¯èœã§ãã
const { pipe, abort } = renderToPipeableStream(<App />, {
// ...
});
setTimeout(() => {
abort();
}, 10000);
React ã¯ãæ®ãã®ããŒãã£ã³ã°äžãã©ãŒã«ããã¯ã HTML ãšããŠçŽã¡ã«åºåããã¯ã©ã€ã¢ã³ãäžã§æ®ããã¬ã³ããŒããããšè©Šã¿ãŸãã