renderToPipeableStream

renderToPipeableStream একটি React tree কে একটি pipeable Node.js Stream. এ রেন্ডার করে।

const { pipe, abort } = renderToPipeableStream(reactNode, options?)

খেয়াল করুন

এই API শুধুমাত্র Node.js এর জন্য। Web Streams, এর মতো environment, যেমন Deno এবং আধুনিক Edge রানটাইমে, renderToReadableStream ব্যবহার করা উচিত।


রেফারেন্স

renderToPipeableStream(reactNode, options?)

আপনার React tree কে একটি Node.js Stream এ HTML হিসেবে রেন্ডার করতে renderToPipeableStream কল করুন।

import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});

Client-side এ, server-generated HTML কে ইন্টার‍্যাক্টিভ করতে hydrateRoot কল করুন।

নিচে আরও উদাহরণ দেখুন।

প্যারামিটারস

  • reactNode: একটি React node যেটিকে আপনি HTML এ রেন্ডার করতে চান। উদাহরণস্বরূপ, <App /> এর মতো একটি JSX element । এটা এক্সপেক্টেড যে, এটি পুরো document কে ধারণ করবে, তাই App কম্পোনেটটির <html> ট্যাগ রেন্ডার করার কথা।

  • optional options: একটি object যাতে streaming options থাকবে।

    • optional bootstrapScriptContent: যদি প্রদান করা হয়, তাহলে এই string টি একটি inline <script> tag এ থাকবে।
    • optional bootstrapScripts: page এ emit করার জন্য <script> tag এর string URL গুলোর একটি array। hydrateRoot কল করে এমন <script> include করতে এটি ব্যবহার করুন। যদি আপনি client এ একেবারেই React রান করতে না চান তাহলে এটি বাদ দিন।
    • optional bootstrapModules: bootstrapScripts এর মতো, কিন্তু এর পরিবর্তে <script type="module"> emit করে।
    • optional identifierPrefix: React এ useId দ্বারা তৈরি ID এর জন্য ব্যবহৃত একটি string prefix। একই page এ একাধিক root ব্যবহার করার সময় conflict এড়াতে কাজে আসে। hydrateRoot এ পাস করা prefix এর অনুরূপ হতে হবে।
    • optional namespaceURI: stream এর জন্য root namespace URI এর একটি string । ডিফল্টভাবে এটি regular HTML । SVG এর জন্য 'http://www.w3.org/2000/svg' অথবা MathML এর জন্য 'http://www.w3.org/1998/Math/MathML' পাস করুন।
    • optional nonce: script-src Content-Security-Policy এর জন্য script গুলোকে allow করতে একটি nonce string।
    • optional onAllReady: একটি callback যেটি সকল rendering কমপ্লিট হওয়ার পর ফায়ার হয়, shell এবং সকল অতিরিক্ত content সহ। crawler এবং static generation এর জন্য আপনি onShellReady এর পরিবর্তে এটি ব্যবহার করতে পারেন। যদি আপনি এখানে streaming শুরু করেন, তাহলে আপনি কোনো progressive loading পাবেন না। stream টিতে final HTML থাকবে।
    • optional onError: একটি callback যেটি যেকোনো server error হলে ফায়ার হয়, recoverable হোক বা না হোক। By default, এটি শুধু console.error কল করে। যদি আপনি crash report log করার জন্য এটি override করেন, তাহলে নিশ্চিত করুন যে আপনি তখনো console.error কল করতে পারছেন। এছাড়াও shell emit হওয়ার আগে status code adjust করতে এটি ব্যবহার করতে পারেন।
    • optional onShellReady: একটি callback যেটি initial shell render হওয়ার ঠিক পরেই ফায়ার হয়। আপনি এখানে status code set করতে এবং streaming শুরু করতে pipe কল করতে পারেন। React shell এর পর অতিরিক্ত content stream করবে inline <script> tag সহ যেগুলো HTML loading fallback গুলোকে content দিয়ে replace করে।
    • optional onShellError: একটি callback যেটি initial shell render করতে error হলে ফায়ার হয়। এটি error কে argument হিসেবে receive করে। এখনো stream থেকে কোনো byte emit হয়নি, এবং onShellReady বা onAllReady কোনোটিই call হবে না, তাই আপনি একটি fallback HTML shell output করতে পারেন
    • optional progressiveChunkSize: একটি chunk এ byte এর সংখ্যা। default heuristic সম্পর্কে আরো পড়ুন।

রিটার্নস

renderToPipeableStream দুইটি method ওয়ালা একটি object return করেঃ

  • pipe প্রদত্ত Writable Node.js Stream এ HTML output করে। streaming enable করতে চাইলে onShellReady তে pipe কল করুন, অথবা crawler এবং static generation এর জন্য onAllReady তে কল করুন।
  • abort আপনাকে server rendering abort করতে এবং বাকিটা client এ render করতে দেয়।

ব্যবহার

React tree কে Node.js Stream এ HTML হিসেবে render করা

আপনার React tree কে একটি Node.js Stream এ HTML হিসেবে render করতে renderToPipeableStream কল করুনঃ

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);
}
});
});

root component এর সাথে, আপনাকে bootstrap <script> path গুলোর একটি list প্রদান করতে হবে। আপনার root component টি যেন root <html> tag সহ পুরো document return করে।

উদাহরণস্বরূপ, এটি এরকম দেখতে হতে পারেঃ

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 এবং আপনার bootstrap <script> tag গুলো HTML stream এ inject করবেঃ

<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>

Client এ, আপনার bootstrap script যেন hydrateRoot কল করে পুরো document hydrate করেঃ

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, <App />);

এটি server-generated HTML এ event listener attach করবে এবং এটিকে interactive করে তুলবে।

গভীরভাবে জানুন

Build output থেকে CSS এবং JS asset path read করা

Final asset URL গুলো (যেমন JavaScript এবং CSS file) প্রায়ই build এর পর hash করা হয়। উদাহরণস্বরূপ, styles.css এর পরিবর্তে আপনি styles.123456.css পেতে পারেন। Static asset filename hash করা নিশ্চিত করে যে একই asset এর প্রতিটি আলাদা build এর আলাদা filename থাকবে। এটি উপকারী কারণ এটি আপনাকে static asset এর জন্য safely long-term caching enable করতে দেয়: একটি নির্দিষ্ট নামের file এর content কখনো পরিবর্তন হবে না।

তবে, build এর পর পর্যন্ত যদি আপনি asset URL গুলো না জানেন, তাহলে source code এ সেগুলো রাখার কোনো উপায় নেই। উদাহরণস্বরূপ, আগের মতো JSX এ "/styles.css" hardcode করা কাজ করবে না। এগুলো আপনার source code থেকে দূরে রাখতে, আপনার root component একটি prop হিসেবে পাস করা map থেকে আসল filename গুলো read করতে পারে:

export default function App({ assetMap }) {
return (
<html>
<head>
...
<link rel="stylesheet" href={assetMap['styles.css']}></link>
...
</head>
...
</html>
);
}

Server এ, <App assetMap={assetMap} /> render করুন এবং asset 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);
}
});
});

যেহেতু আপনার server এখন <App assetMap={assetMap} /> render করছে, hydration error এড়াতে client এও assetMap সহ এটি render করতে হবে। আপনি এইভাবে assetMap serialize করে client এ পাস করতে পারেন:

// 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 option একটি অতিরিক্ত inline <script> tag যোগ করে যেটি client এ global window.assetMap variable সেট করে। এটি client code কে একই assetMap read করতে দেয়:

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, <App assetMap={window.assetMap} />);

Client এবং server উভয়েই একই assetMap prop সহ App render করে, তাই কোনো hydration error থাকবে না।


Load হতে হতে আরো content stream করা

Streaming ইউজারকে সকল ডাটা সার্ভারে লোড হওয়ার আগেই কন্টেন্ট দেখতে দেয়। উদাহরণস্বরূপ, একটি profile page এর কথা ভাবুন যেটি একটি cover, friend এবং photo সহ একটি sidebar, এবং post এর একটি লিস্ট দেখায়ঃ

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Posts />
</ProfileLayout>
);
}

ধরুন যে <Posts /> এর জন্য data লোড হতে কিছু সময় লাগে। স্বাভাবিকভাবে, আপনি post এর জন্য অপেক্ষা না করে user কে profile page এর বাকি content দেখাতে চাইবেন। এটি করতে, Posts কে একটি <Suspense> boundary তে wrap করুনঃ

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}

এটি React কে Posts এর data লোড হওয়ার আগেই HTML streaming শুরু করতে বলে। React প্রথমে loading fallback (PostsGlimmer) এর জন্য HTML পাঠাবে, এবং তারপর, যখন Posts এর data লোডিং শেষ হবে, React বাকি HTML পাঠাবে একটি inline <script> tag সহ যেটি লোডিং fallback কে সেই HTML দিয়ে replace করবে। ইউজারের দৃষ্টিকোণ থেকে, page প্রথমে PostsGlimmer নিয়ে দেখাবে, পরে Posts দিয়ে রিপ্লেস হবে।

আরও granular loading sequence তৈরি করতে আপনি আরও nested <Suspense> boundary যোগ করতে পারেনঃ

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}

এই উদাহরণে, React আরো আগে page streaming শুরু করতে পারে। শুধুমাত্র ProfileLayout এবং ProfileCover এর প্রথমে rendering শেষ করতে হবে কারণ তারা কোনো <Suspense> boundary তে wrap করা নেই। তবে, যদি Sidebar, Friends, অথবা Photos এর কিছু data load করতে হয়, React তার পরিবর্তে BigSpinner fallback এর HTML পাঠাবে। তারপর, যত বেশি data লোড হবে, ততবেশি content প্রকাশিত হতে থাকবে যতক্ষণ না সবকিছু দৃশ্যমান হয়।

Streaming এর React নিজে browser এ load হওয়ার জন্য অথবা আপনার app interactive হওয়ার জন্য অপেক্ষা করার প্রয়োজন নেই। Server থেকে HTML content কোনো <script> tag load হওয়ার আগেই একে একে প্রকাশিত হতে থাকবে।

HTML Streaming কিভাবে কাজ করে সে সম্পর্কে আরো পড়ুন।

খেয়াল করুন

শুধুমাত্র Suspense-enabled ডাটা সোর্সগুলো Suspense component কে activate করবে। এগুলোর মধ্যে রয়েছে:

  • Relay এবং Next.js এর মতো Suspense-enabled ফ্রেমওয়ার্ক দিয়ে ডাটা ফেচিং
  • lazy দিয়ে কম্পোনেন্ট কোড lazy-load করা
  • use দিয়ে Promise এর value read করা

যদি ডাটা একটি Effect অথবা event handler এর ভিতরে fetch করা হয় তবে Suspense তা ডিটেক্ট করবে না

উপরের Posts component এ আপনি ঠিক কিভাবে data load করবেন তা আপনার framework এর উপর নির্ভর করে। যদি আপনি একটি Suspense-enabled framework ব্যবহার করেন, তাহলে আপনি তার data fetching documentation এ বিস্তারিত তথ্য পাবেন।

Opinionated framework ব্যবহার ছাড়া Suspense-enabled ডাটা ফেচিং এখনো সাপোর্টেড না। Suspense-enabled ডাটা সোর্স implement করার requirement গুলো unstable এবং undocumented। Suspense এর সাথে ডাটা সোর্স integrate করার জন্য একটি official API রিয়েক্টের ভবিষ্যৎ ভার্সনে রিলিজ হবে।


Shell এ কী যাবে তা নির্দিষ্ট করা

আপনার app এর <Suspense> boundary এর বাইরের যেকোনো অংশকে বলা হয় shell:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}

এটা ঠিক করে দেয় যে ইউজার প্রথমে কোন loading state টি দেখতে পারেঃ

<ProfileLayout>
<ProfileCover />
<BigSpinner />
</ProfileLayout>

যদি আপনি root এ পুরো app কে একটি <Suspense> boundary তে wrap করেন, shell এ শুধু সেই spinner থাকবে। তবে, এটি একটি সুন্দর user experience না কারণ screen এ একটি বড় spinner দেখা আরো slow এবং বিরক্তিকর মনে হতে পারে একটু বেশি অপেক্ষা করে আসল layout দেখার চেয়ে। এই কারণেই সাধারণত আপনি <Suspense> boundary গুলো এমনভাবে রাখবেন যাতে shell নুন্যতম কিন্তু সম্পূর্ণ মনে হয়—পুরো page layout এর একটি skeleton এর মতো।

onShellReady callback পুরো shell render হওয়ার পর ফায়ার হয়। সাধারণত, আপনি তখনই streaming শুরু করবেনঃ

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});

onShellReady ফায়ার হওয়ার সময়ে, নেস্টেড <Suspense> boundary এর component গুলো তখনো data load করতে থাকতে পারে।


Logging crashes on the server

By default, all errors on the server are logged to console. You can override this behavior to log crash reports:

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});

If you provide a custom onError implementation, don’t forget to also log errors to the console like above.


Recovering from errors inside the shell

In this example, the shell contains ProfileLayout, ProfileCover, and PostsGlimmer:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}

If an error occurs while rendering those components, React won’t have any meaningful HTML to send to the client. Override onShellError to send a fallback HTML that doesn’t rely on server rendering as the last resort:

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);
}
});

If there is an error while generating the shell, both onError and onShellError will fire. Use onError for error reporting and use onShellError to send the fallback HTML document. Your fallback HTML does not have to be an error page. Instead, you may include an alternative shell that renders your app on the client only.


Recovering from errors outside the shell

In this example, the <Posts /> component is wrapped in <Suspense> so it is not a part of the shell:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}

If an error happens in the Posts component or somewhere inside it, React will try to recover from it:

  1. It will emit the loading fallback for the closest <Suspense> boundary (PostsGlimmer) into the HTML.
  2. It will “give up” on trying to render the Posts content on the server anymore.
  3. When the JavaScript code loads on the client, React will retry rendering Posts on the client.

If retrying rendering Posts on the client also fails, React will throw the error on the client. As with all the errors thrown during rendering, the closest parent error boundary determines how to present the error to the user. In practice, this means that the user will see a loading indicator until it is certain that the error is not recoverable.

If retrying rendering Posts on the client succeeds, the loading fallback from the server will be replaced with the client rendering output. The user will not know that there was a server error. However, the server onError callback and the client onRecoverableError callbacks will fire so that you can get notified about the error.


Setting the status code

Streaming introduces a tradeoff. You want to start streaming the page as early as possible so that the user can see the content sooner. However, once you start streaming, you can no longer set the response status code.

By dividing your app into the shell (above all <Suspense> boundaries) and the rest of the content, you’ve already solved a part of this problem. If the shell errors, you’ll get the onShellError callback which lets you set the error status code. Otherwise, you know that the app may recover on the client, so you can send “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);
}
});

If a component outside the shell (i.e. inside a <Suspense> boundary) throws an error, React will not stop rendering. This means that the onError callback will fire, but you will still get onShellReady instead of onShellError. This is because React will try to recover from that error on the client, as described above.

However, if you’d like, you can use the fact that something has errored to set the status code:

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);
}
});

This will only catch errors outside the shell that happened while generating the initial shell content, so it’s not exhaustive. If knowing whether an error occurred for some content is critical, you can move it up into the shell.


Handling different errors in different ways

You can create your own Error subclasses and use the instanceof operator to check which error is thrown. For example, you can define a custom NotFoundError and throw it from your component. Then your onError, onShellReady, and onShellError callbacks can do something different depending on the error type:

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);
}
});

Keep in mind that once you emit the shell and start streaming, you can’t change the status code.


Waiting for all content to load for crawlers and static generation

Streaming offers a better user experience because the user can see the content as it becomes available.

However, when a crawler visits your page, or if you’re generating the pages at the build time, you might want to let all of the content load first and then produce the final HTML output instead of revealing it progressively.

You can wait for all the content to load using the onAllReady callback:

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);
}
});

A regular visitor will get a stream of progressively loaded content. A crawler will receive the final HTML output after all the data loads. However, this also means that the crawler will have to wait for all data, some of which might be slow to load or error. Depending on your app, you could choose to send the shell to the crawlers too.


Aborting server rendering

You can force the server rendering to “give up” after a timeout:

const { pipe, abort } = renderToPipeableStream(<App />, {
// ...
});

setTimeout(() => {
abort();
}, 10000);

React will flush the remaining loading fallbacks as HTML, and will attempt to render the rest on the client.