renderToPipeableStream
renderToPipeableStream
একটি React tree কে একটি pipeable Node.js Stream. এ রেন্ডার করে।
const { pipe, abort } = renderToPipeableStream(reactNode, options?)
- রেফারেন্স
- ব্যবহার
- React tree কে Node.js Stream এ HTML হিসেবে render করা
- Load হতে হতে আরো content stream করা
- Shell এ কী যাবে তা নির্দিষ্ট করা
- Logging crashes on the server
- Recovering from errors inside the shell
- Recovering from errors outside the shell
- Setting the status code
- Handling different errors in different ways
- Waiting for all content to load for crawlers and static generation
- Aborting server rendering
রেফারেন্স
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 সম্পর্কে আরো পড়ুন।
- optional
রিটার্নস
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 করে তুলবে।
গভীরভাবে জানুন
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 কিভাবে কাজ করে সে সম্পর্কে আরো পড়ুন।
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:
- It will emit the loading fallback for the closest
<Suspense>
boundary (PostsGlimmer
) into the HTML. - It will “give up” on trying to render the
Posts
content on the server anymore. - 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.