| title | prerenderToNodeStream |
|---|
prerenderToNodeStream์ Node.js Stream์ ์ฌ์ฉํ์ฌ React ํธ๋ฆฌ๋ฅผ ์ ์ HTML ๋ฌธ์์ด๋ก ๋ ๋๋งํฉ๋๋ค.
const {prelude} = await prerenderToNodeStream(reactNode, options?)์ด API๋ Node.js ์ ์ฉ์
๋๋ค. Deno๋ ์ต์ ์ฃ์ง ๋ฐํ์์ฒ๋ผ Web Streams๋ฅผ ์ง์ํ๋ ํ๊ฒฝ์์๋ prerender๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
prerenderToNodeStream์ ํธ์ถํด ์ฑ์ ์ ์ HTML๋ก ๋ ๋๋งํฉ๋๋ค.
import { prerenderToNodeStream } from 'react-dom/static';
// ๋ผ์ฐํธ ํธ๋ค๋ฌ ๋ฌธ๋ฒ์ ์ฌ์ฉํ๋ ๋ฐฑ์๋ ํ๋ ์์ํฌ์ ๋ฐ๋ผ ๋ค๋ฆ
๋๋ค.
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});
response.setHeader('Content-Type', 'text/plain');
prelude.pipe(response);
});ํด๋ผ์ด์ธํธ์์ hydrateRoot๋ฅผ ํธ์ถํด ์๋ฒ์์ ์์ฑ๋ HTML์ ์ธํฐ๋ํฐ๋ธํ๊ฒ ๋ง๋ญ๋๋ค.
์๋์์ ๋ ๋ง์ ์์๋ฅผ ํ์ธํ์ธ์.
-
reactNode: HTML๋ก ๋ ๋๋งํ React ๋ ธ๋์ ๋๋ค. ์๋ฅผ ๋ค์ด<App />๊ณผ ๊ฐ์ JSX ๋ ธ๋๊ฐ ํด๋น๋ฉ๋๋ค. ์ ์ฒด ๋ฌธ์๋ฅผ ๋ํ๋ด์ผ ํ๋ฏ๋ก, App ์ปดํฌ๋ํธ๋<html>ํ๊ทธ๋ฅผ ๋ ๋๋งํด์ผ ํฉ๋๋ค. -
optional
options: ์ ์ ์์ฑ ์ต์ ์ ๊ฐ์ง ๊ฐ์ฒด์ ๋๋ค.- optional
bootstrapScriptContent: ์ง์ ๋ ๊ฒฝ์ฐ, ์ด ๋ฌธ์์ด์ด ์ธ๋ผ์ธ<script>ํ๊ทธ์ ์ฝ์ ๋ฉ๋๋ค. - optional
bootstrapScripts: ํ์ด์ง์ ์ถ๋ ฅํ<script>ํ๊ทธ์ ๋ฌธ์์ด URL ๋ฐฐ์ด์ ๋๋ค.hydrateRoot๋ฅผ ํธ์ถํ๋<script>๋ฅผ ํฌํจํ๋ ค๋ฉด ์ด ์ต์ ์ ์ฌ์ฉํ์ธ์. ํด๋ผ์ด์ธํธ์์ React๋ฅผ ์ ํ ์คํํ์ง ์์ผ๋ ค๋ฉด ์๋ตํ์ธ์. - optional
bootstrapModules:bootstrapScripts์ ๊ฐ์ง๋ง,<script type="module">์ ์ถ๋ ฅํฉ๋๋ค. - optional
identifierPrefix:useId๋ก ์์ฑ๋ ID์ React๊ฐ ์ฌ์ฉํ๋ ๋ฌธ์์ด ์ ๋์ฌ์ ๋๋ค. ํ ํ์ด์ง์์ ์ฌ๋ฌ ๊ฐ์ ๋ฃจํธ๋ฅผ ์ฌ์ฉํ ๋ ์ถฉ๋์ ํผํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.hydrateRoot์ ์ ๋ฌํ ์ ๋์ฌ์ ๋์ผํด์ผ ํฉ๋๋ค. - optional
namespaceURI: ์คํธ๋ฆผ์ ๋ฃจํธ namespace URI๋ฅผ ๋ด์ ๋ฌธ์์ด์ ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ ์ผ๋ฐ HTML์ ๋๋ค. SVG์ ๊ฒฝ์ฐ'http://www.w3.org/2000/svg', MathML์ ๊ฒฝ์ฐ'http://www.w3.org/1998/Math/MathML'์ ์ ๋ฌํ์ธ์. - optional
onError: ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๋๋ง๋ค, ๋ณต๊ตฌ ๊ฐ๋ฅ ๋ถ๊ฐ๋ฅ ์ฌ๋ถ์ ๊ด๊ณ์์ด ํธ์ถ๋๋ ์ฝ๋ฐฑ์ ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋กconsole.error๋ง ํธ์ถํฉ๋๋ค. ์ถฉ๋ ๋ณด๊ณ ๋ฅผ ๊ธฐ๋กํ๋๋ก ์ฌ์ ์ํ๋ ๊ฒฝ์ฐ์๋ ๋ฐ๋์console.error๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค. ์ ธ์ด ์ถ๋ ฅ๋๊ธฐ ์ ์ ์ํ ์ฝ๋๋ฅผ ์ค์ ํ๋ ๋ฐ์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. - optional
progressiveChunkSize: ์ฒญํฌ์ ๋ฐ์ดํธ ์์ ๋๋ค. ๊ธฐ๋ณธ ํด๋ฆฌ์คํฑ์ ๋ํด ๋ ์ฝ์ด๋ณด์ธ์. - optional
signal: ํ๋ฆฌ๋ ๋๋ง์ ์ค๋จํ๊ณ ๋๋จธ์ง๋ฅผ ํด๋ผ์ด์ธํธ์์ ๋ ๋๋งํ ์ ์๊ฒ ํ๋ ์ค๋จ ์ ํธ์ ๋๋ค.
- optional
prerenderToNodeStream ๋ Promise๋ฅผ ๋ฐํํฉ๋๋ค.
- ๋ ๋๋ง์ด ์ฑ๊ณตํ๋ฉด Promise๋ ๋ค์์ ํฌํจํ๋ ๊ฐ์ฒด๋ก ํด๊ฒฐ๋ฉ๋๋ค.
prelude: HTML์ Node.js Stream์ ๋๋ค. ์ด ์คํธ๋ฆผ์ ์ฌ์ฉํด ์๋ต์ ์ฒญํฌ ๋จ์๋ก ์ ์กํ๊ฑฐ๋, ์ ์ฒด ์คํธ๋ฆผ์ ๋ฌธ์์ด๋ก ์ฝ์ ์ ์์ต๋๋ค.
- ๋ ๋๋ง์ ์คํจํ๋ฉด, Promise๊ฐ ์คํจํฉ๋๋ค. ์ด๋ฅผ ์ฌ์ฉํด ํด๋ฐฑ ์ ธFallback Shell์ ์ถ๋ ฅํ์ธ์.
ํ๋ฆฌ๋ ๋๋ง ์ nonce ์ต์
์ ์ฌ์ฉํ ์ ์์ต๋๋ค. Nonce๋ ์์ฒญ๋ง๋ค ๊ณ ์ ํด์ผ ํ๋ฉฐ, CSP๋ก ์ ํ๋ฆฌ์ผ์ด์
์ ๋ณดํธํ ๋ Nonce ๊ฐ์ ํ๋ฆฌ๋ ๋๋ง ๊ฒฐ๊ณผ์ ํฌํจํ๋ ๊ฒ์ ๋ถ์ ์ ํ๊ณ ์์ ํ์ง ์์ต๋๋ค.
์ ์ prerenderToNodeStream API๋ ์ ์ ์๋ฒ ์ฌ์ด๋ ์์ฑ(SSG)์ ์ฌ์ฉํฉ๋๋ค. renderToString๊ณผ ๋ฌ๋ฆฌ, prerenderToNodeStream์ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ํ์ ์ฑ๊ณตํฉ๋๋ค. ์ด๋ Suspense๋ฅผ ์ฌ์ฉํด ๊ฐ์ ธ์์ผ ํ๋ ๋ฐ์ดํฐ๋ฅผ ํฌํจํด, ์ ์ฒด ํ์ด์ง์ ์ ์ HTML์ ์์ฑํ๋ ๋ฐ ์ ํฉํฉ๋๋ค. ์ฝํ
์ธ ๊ฐ ๋ก๋๋๋ ๋์ ์คํธ๋ฆฌ๋ฐํ๋ ค๋ฉด, renderToReadableStream๊ณผ ๊ฐ์ ์คํธ๋ฆฌ๋ฐ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR) API๋ฅผ ์ฌ์ฉํ์ธ์.
React ํธ๋ฆฌ๋ฅผ ์ ์ HTML ์คํธ๋ฆผ์ผ๋ก ๋ ๋๋งํ๊ธฐ {/rendering-a-react-tree-to-a-stream-of-static-html/}
prerenderToNodeStream๋ฅผ ํธ์ถํด React ํธ๋ฆฌ๋ฅผ ์ ์ HTML๋ก ๋ ๋๋งํ๊ณ , ์ด๋ฅผ Node.js Stream์ ์ถ๋ ฅํฉ๋๋ค.
import { prerenderToNodeStream } from 'react-dom/static';
// ๋ผ์ฐํฐ ํธ๋ค๋ฌ ๋ฌธ๋ฒ์ ์ฌ์ฉํ๋ ๋ฐฑ์๋ ํ๋ ์์ํฌ์ ๋ฐ๋ผ ๋ค๋ฆ
๋๋ค.
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});
response.setHeader('Content-Type', 'text/plain');
prelude.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 ... -->
</html>
<script src="/main.js" async=""></script>ํด๋ผ์ด์ธํธ์์ ๋ถํธ์คํธ๋ฉ ์คํฌ๋ฆฝํธ๋ hydrateRoot๋ฅผ ํธ์ถํด document ์ ์ฒด๋ฅผ hydrateํด์ผ ํฉ๋๋ค.
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);์ด๋ ์ ์ ์๋ฒ ์์ฑ HTML์ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ฐ๊ฒฐํด ์ธํฐ๋ํฐ๋ธํ๊ฒ ๋ง๋ญ๋๋ค.
๋น๋ ์ถ๋ ฅ์์ CSS ๋ฐ JS ์์ ๊ฒฝ๋ก ์ฝ๊ธฐ {/reading-css-and-js-asset-paths-from-the-build-output/}
์ต์ข
์์
URL(์: JavaScript์ CSS ํ์ผ)์ ๋น๋ ํ ์ข
์ข
ํด์Hash๊ฐ ์ถ๊ฐ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด styles.css ๋์ styles.123456.css์ ๊ฐ์ ํ์ผ๋ช
์ด ๋ ์ ์์ต๋๋ค. ์ ์ ์์
ํ์ผ๋ช
์ ํด์๋ฅผ ์ถ๊ฐํ๋ฉด ๋์ผํ ์์
์ด๋ผ๋ ๋น๋๋ง๋ค ๋ค๋ฅธ ํ์ผ๋ช
์ ๊ฐ๊ฒ ๋ฉ๋๋ค. ์ด๋ ์ ์ ์์
์ ๋ํด ์ฅ๊ธฐ ์บ์ฑ์ ์์ ํ๊ฒ ํ์ฑํํ ์ ์๊ฒ ํด์ค๋๋ค. ํน์ ์ด๋ฆ์ ๊ฐ์ง ํ์ผ์ ๊ทธ ๋ด์ฉ์ด ์ ๋ ๋ณ๊ฒฝ๋์ง ์๊ธฐ ๋๋ฌธ์
๋๋ค.
๊ทธ๋ฌ๋ ๋น๋๊ฐ ๋๋ ํ์์ผ ์์
URL์ ์ ์ ์๋ค๋ฉด, ์ด๋ฅผ ์์ค ์ฝ๋์ ๋ฃ์ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์์์ฒ๋ผ JSX์ "/styles.css"๋ฅผ ํ๋์ฝ๋ฉํ๋ฉด ๋์ํ์ง ์์ต๋๋ค. ์์ค ์ฝ๋์ ์์
๊ฒฝ๋ก๋ฅผ ๋ฃ์ง ์์ผ๋ ค๋ฉด, ๋ฃจํธ ์ปดํฌ๋ํธ๊ฐ Prop์ผ๋ก ์ ๋ฌ๋ ๋งต์์ ์ค์ ํ์ผ๋ช
์ ์ฝ์ด์ฌ ์ ์์ต๋๋ค.
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์ ์ ๋ฌํฉ๋๋ค.
// ์ด JSON์ ๋น๋ ๋๊ตฌ์์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋น๋ ์ถ๋ ฅ์์ ์ฝ์ด์ฌ ์ ์์ต๋๋ค.
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);
});์๋ฒ์์ <App assetMap={assetMap} />์ ๋ ๋๋งํ๊ณ ์์ผ๋ฏ๋ก, Hydration ์ค๋ฅ๋ฅผ ํผํ๋ ค๋ฉด ํด๋ผ์ด์ธํธ์์๋ assetMap๊ณผ ํจ๊ป ๋ ๋๋งํด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด assetMap์ ์ง๋ ฌํํด ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌํ ์ ์์ต๋๋ค.
// ์ด JSON์ ๋น๋ ๋๊ตฌ์์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
// ์ฃผ์: ์ด ๋ฐ์ดํฐ๋ ์ฌ์ฉ์๊ฐ ์์ฑํ ๊ฒ์ด ์๋๋ฏ๋ก `stringify()`ํด๋ ์์ ํฉ๋๋ค.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['/main.js']],
});
response.setHeader('Content-Type', 'text/html');
prelude.pipe(response);
});์ ์์์์ bootstrapScriptContent ์ต์
์ ํด๋ผ์ด์ธํธ์์ ์ ์ญ ๋ณ์ window.assetMap์ ์ค์ ํ๋ ์ธ๋ผ์ธ <script> ํ๊ทธ๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํด๋ผ์ด์ธํธ ์ฝ๋์์ ๋์ผํ assetMap์ ์ฝ์ ์ ์์ต๋๋ค.
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App assetMap={window.assetMap} />);ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋ App์ ๋์ผํ assetMap Prop์ผ๋ก ๋ ๋๋งํ๋ฏ๋ก ํ์ด๋๋ ์ด์
์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
React ํธ๋ฆฌ๋ฅผ ์ ์ HTML ๋ฌธ์์ด๋ก ๋ ๋๋งํ๊ธฐ {/rendering-a-react-tree-to-a-string-of-static-html/}
prerenderToNodeStream์ ํธ์ถํด ์ฑ์ ์ ์ HTML ๋ฌธ์์ด๋ก ๋ ๋๋งํฉ๋๋ค.
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);
});
}์ด๋ ๊ฒ ํ๋ฉด React ์ปดํฌ๋ํธ์ ์ด๊ธฐ ์ํธ์์ฉํ์ง ์์ HTML ์ถ๋ ฅ์ด ์์ฑ๋ฉ๋๋ค. ํด๋ผ์ด์ธํธ์์๋ hydrateRoot๋ฅผ ํธ์ถํ์ฌ ์๋ฒ์์ ์์ฑ๋ HTML์ Hydrateํ๊ณ ์ํธ์์ฉํ๊ฒ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
prerenderToNodeStream๋ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ๋ค ์ ์ HTML ์์ฑ์ ์๋ฃํ๊ณ Promise๋ฅผ ํด๊ฒฐํฉ๋๋ค. ์๋ฅผ ๋ค์ด ํ์ง ์ด๋ฏธ์ง, ์น๊ตฌ์ ์ฌ์ง์ด ํฌํจ๋ ์ฌ์ด๋๋ฐ, ๊ฒ์๋ฌผ ๋ชฉ๋ก์ ํ์ํ๋ ํ๋กํ ํ์ด์ง๋ฅผ ์๊ฐํด ๋ณด๊ฒ ์ต๋๋ค.
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}์๋ฅผ ๋ค์ด <Posts />๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํด์ผ ํ๊ณ , ์ด ๊ณผ์ ์ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ์ด ๊ฒฝ์ฐ, ์ด์์ ์ผ๋ก๋ ๊ฒ์๋ฌผ ๋ฐ์ดํฐ๊ฐ ๋ชจ๋ ๋ก๋๋ ๋ค HTML์ ํฌํจ๋๊ธฐ๋ฅผ ์ํ ๊ฒ์
๋๋ค. ์ด๋ฅผ ์ํด Suspense๋ฅผ ์ฌ์ฉํด ๋ฐ์ดํฐ ๋ก๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๋ ๋๋ง์ ์ผ์ ์ค๋จํ ์ ์์ผ๋ฉฐ, prerenderToNodeStream๋ ํด๋น ์ค๋จ๋ ์ฝํ
์ธ ๊ฐ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ํ ์ ์ HTML๋ก ๋ณํ์ ์๋ฃํฉ๋๋ค.
Suspense๋ฅผ ์ง์ํ๋ ๋ฐ์ดํฐ ์์ค๋ง์ด Suspense ์ปดํฌ๋ํธ๋ฅผ ํ์ฑํํฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค.
- Relay ํน์ Next.js ์ฒ๋ผ Suspense๋ฅผ ์ง์ํ๋ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ.
lazy๋ฅผ ์ฌ์ฉํ ์ปดํฌ๋ํธ ์ฝ๋์ ์ง์ฐ๋ก๋ฉ.use๋ฅผ ์ฌ์ฉํด Promise์ ๊ฐ์ ์ฝ๊ธฐ.
Suspense๋ Effect๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ด๋ถ์์ ๋ฐ์ดํฐ๊ฐ ํจ์นญ๋ ๋ ์ด๋ฅผ ๊ฐ์งํ์ง ์์ต๋๋ค.
์ ์์์ Posts ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ํ๋ ์์ํฌ์ ๋ฐ๋ผ ๋ค๋ฆ
๋๋ค. Suspense๋ฅผ ์ง์ํ๋ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ํด๋น ํ๋ ์์ํฌ์ ๋ฐ์ดํฐ ํจ์นญ ๋ฌธ์์์ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.
ํน์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ง ์๋ Suspense ์ง์ ๋ฐ์ดํฐ ํจ์นญ์ ์์ง ์ง์๋์ง ์์ต๋๋ค. Suspense๋ฅผ ์ง์ํ๋ ๋ฐ์ดํฐ ์์ค๋ฅผ ๊ตฌํํ๊ธฐ ์ํ ์๊ตฌ ์ฌํญ์ ํ์ฌ ๋ถ์์ ํ๊ณ ๋ฌธ์ํ๋์ด ์์ง ์์ต๋๋ค. ๋ฐ์ดํฐ ์์ค๋ฅผ Suspense์ ํตํฉํ๊ธฐ ์ํ ๊ณต์ API๋ React์ ํฅํ ๋ฒ์ ์์ ์ ๊ณต๋ ์์ ์ ๋๋ค.
ํ์์์ ์ดํ ์ฌ์ ๋ ๋๋ง์ "ํฌ๊ธฐ"ํ๋๋ก ๊ฐ์ ํ ์ ์์ต๋๋ค.
async function renderToString() {
const controller = new AbortController();
setTimeout(() => {
controller.abort()
}, 10000);
try {
// Prelude์๋ ์ปจํธ๋กค๋ฌ๊ฐ ์ค๋จํ๊ธฐ ์ ์
// ์ฌ์ ๋ ๋๋ง๋ ๋ชจ๋ HTML์ด ํฌํจ๋ฉ๋๋ค.
const {prelude} = await prerenderToNodeStream(<App />, {
signal: controller.signal,
});
//...๋ถ์์ ํ ์์์ ๊ฐ์ง ๋ชจ๋ Suspense ๊ฒฝ๊ณ๋ ํ๋ฐฑ ์ํ๋ก Prelude์ ํฌํจ๋ฉ๋๋ค.
์ ์ฒด ์ฑ์ด ๋ ๋๋ง๋ ๋๊น์ง ์คํธ๋ฆผ์ด ์์๋์ง ์์์ต๋๋ค. {/my-stream-doesnt-start-until-the-entire-app-is-rendered/}
prerenderToNodeStream ์๋ต์ ๋ชจ๋ Suspense ๊ฒฝ๊ณ๊ฐ ํด๊ฒฐ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ ํฌํจํ์ฌ ์ ์ฒด ์ฑ์ด ๋ ๋๋ง์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ํ ์๋ฃ๋ฉ๋๋ค. ์ด API๋ ์ ์ ์ฌ์ดํธ ์์ฑ(SSG)์ ์ํด ์ค๊ณ๋์์ผ๋ฉฐ ์ฝํ
์ธ ๊ฐ ๋ก๋๋๋ฉด์ ๋ ๋ง์ ์ฝํ
์ธ ๋ฅผ ์คํธ๋ฆฌ๋ฐํ๋ ๊ฒ์ ์ง์ํ์ง ์์ต๋๋ค.
์ฝํ
์ธ ๊ฐ ๋ก๋๋๋ฉด์ ์คํธ๋ฆฌ๋ฐํ๋ ค๋ฉด renderToPipeableStream๊ณผ ๊ฐ์ ์คํธ๋ฆฌ๋ฐ ์๋ฒ ๋ ๋๋ง API๋ฅผ ์ฌ์ฉํ์ธ์.