renderToPipeableStream

renderToPipeableStream, bir react ağacını pipelanabilir Node.js Streamine render eder.

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

Not

Bu API Node.js’e özgüdür. Deno, ve bazı modern edge runtimeları gibi Web Streamleri içeren ortamlar renderToReadableStream’i kullanmalılardır.


Referans

renderToPipeableStream(reactNode, options?)

React ağacınızı Node.js Streamine HTML olarak render etmek istersseniz renderToPipeableStream’i çağırınız.

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

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

İstemcide üzerinde, hydrateRoot’u çağırarak sunucu tarafından oluşturulan HTML’in etkileşimli hale getirebilirsiniz.

Daha fazla örnek için aşağıya bakınız.

Parametreler

  • reactNode: HTML’e render etmek istediğiniz React düğümüdür. Örneğin, <App /> gibi bir JSX bileşeni. Belgenin tamamını temsil etmesi beklediğinden dolayı App bileşeni <html> etiketini render etmelidir.

  • optional options: Streaming options içeren bir object.

    • optional bootstrapScriptContent: Belirtilirse, bu string inline bir <script> tag’i içine yerleştirilir.

    • optional bootstrapScripts: Sayfada emit edilecek <script> tag’leri için string URL’lerden oluşan bir array. hydrateRoot çağıran <script>’i dahil etmek için bunu kullanın. Client tarafında React’i hiç çalıştırmak istemiyorsanız bunu atlayın.

    • optional bootstrapModules: bootstrapScripts gibidir, ancak bunun yerine <script type="module"> emit eder.

    • optional identifierPrefix: React’in useId tarafından oluşturulan ID’ler için kullandığı string prefix. Aynı sayfada birden fazla root kullanırken conflict’leri önlemek için kullanışlıdır. hydrateRoot’a geçirilen prefix ile aynı olmalıdır.

    • optional namespaceURI: Stream için root namespace URI’sini içeren bir string. Default olarak normal HTML’dir. SVG için 'http://www.w3.org/2000/svg', MathML için 'http://www.w3.org/1998/Math/MathML' geçirin.

    • optional nonce: script-src Content-Security-Policy için script’lere izin veren bir nonce string’i.

    • optional onAllReady: Hem shell hem de tüm ek content dahil olmak üzere tüm rendering tamamlandığında tetiklenen bir callback. Bunu onShellReady yerine crawler’lar ve static generation için kullanabilirsiniz. Burada streaming’i başlatırsanız progressive loading elde etmezsiniz. Stream final HTML’i içerir.

    • optional onError: Recoverable olsun ya da olmasın, server error olduğunda tetiklenen bir callback. Default olarak yalnızca console.error çağırır. Crash report’ları loglamak için override ederseniz, yine de console.error çağırdığınızdan emin olun. Shell emit edilmeden önce status code’u ayarlamak için de kullanabilirsiniz.

    • optional onShellReady: Initial shell render edildikten hemen sonra tetiklenen bir callback. Burada status code’u ayarlayabilir ve streaming’i başlatmak için pipe çağırabilirsiniz. React, shell’den sonra ek content’i stream eder ve HTML loading fallback’lerini content ile değiştiren inline <script> tag’lerini de beraberinde gönderir.

    • optional onShellError: Initial shell render edilirken bir hata oluşursa tetiklenen bir callback. Error’ı argüman olarak alır. Stream’den henüz hiç byte emit edilmemiştir ve ne onShellReady ne de onAllReady çağrılır; bu yüzden fallback HTML shell output edebilirsiniz.

    • optional progressiveChunkSize: Bir chunk içindeki byte sayısı. Default heuristic hakkında daha fazla okuyun.

Dönüş Değeri

renderToPipeableStream objeleri iki yöntemle döndürür:

  • pipe işlemi, HTML’i sağlanan Yazılabilir Node.js Stream’ini çıktı olarak verir. Streami etkinleştirmek istiyorsanız onShellReady’de veya tarayıcılar ve statik oluşturma için pipe’ı çağırın .
  • abort Sunucu renderini iptal etmenizi ve geri kalanını istemci üzerinde render etmenizi sağlar.

Kullanım

React ağaıcını HTML olarak Node.js Streamde render etmek

React ağacını HTML olarak Node.js Stream’e render etmek için renderToPipeableStream’i çağırın:

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

// Route handler syntax backend framework'ünüze bağlıdır
app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
});

Root bileşeniniz ile birlikte, başlangıç <script> yolları listesi sağlamanız gerekmektedir. Root bileşeniniz root<html> etiketi olmak üzere tüm belgeyi dönmelidir.

Örneğin, böyle gözükebilir:

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>Benim Uygulamam</title>
</head>
<body>
<Router />
</body>
</html>
);
}

React doctype’ı ve başlangıç <script> etiketlerini sonuç HTML streamine enjekte edecektir.

<!DOCTYPE html>
<html>
<!-- ... Bileşenlerinizden oluşturulan HTML ... -->
</html>
<script src="/main.js" async=""></script>

İstemcide, başlangıç betiğinizin tüm document’ı hydrateRoot çağrısıyla hidrasyon etmesi gerekir:

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

hydrateRoot(document, <App />);

Bu işlem olay yöneticisi dinleyicilerini sunucu tarafından oluşturulan HTML’e bağlar ve interaktif olmasını sağlar.

Derinlemesine İnceleme

CSS ve JS varlık yollarını derleme çıktısından

En son oluşturulan varlık URL’leri (JavaScript ve CSS dosyaları gibi) genellikle derleme sonrasında şifrelenir. Örneğin styles.css yerine styles.123456.css gibi bir sonuç alırsınız. Statik varlık adlarını şifrelemek, aynı varlığın her farklı yapısının farklı bir dosya adı alacağını kesinleştirir. Bu işlem statik varlıklarda güvenle uzun dönem önbelleğe alma işlemini etkinleştirmenizi sağlar: belli bir ada sahip olan bir dosya içeriği asla değiştirmez.

Ancak, varlık URL’lerini derleme sonrasına kadar bilmiyorsanız, onları kaynak kodun içine koymanızın bir yolu yoktur. Örneğin, "/styles.css"’i sabit olarak JSX’e vermek işe yaramayacaktır. Kaynak kodunuzdan uzakta tutmak için root bileşeniniz, prop olarak gönderilen bir mapden gerçek dosya adlarını okuyabilir:

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

Sunucuda, <App assetMap={assetMap} />’i render edin ve varlık URL’leri ile birlikte assetMap’i iletin:

// Bu JSON'ı derleme aracınızdan almanız gerekiyor, ör. derleme çıktısından okuyun
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);
}
});
});

Artık sunucunuz <App assetMap={assetMap} /> bileşenini render ettiğine göre hidrasyon hatalarını engellemek için istemcinizde de assetMap’i render etmeniz gerekiyor. assetMap’i şu şekilde seri hale getirip istemcinize iletebilirsiniz:

// Bu JSON'ı derleme aracınızdan almanız gerekiyor.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};

app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App assetMap={assetMap} />, {
// Dikkat: Veri kullanıcı tarafından oluşturulmadığı için stringfy() yapmanız güvenlidir.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['main.js']],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
});

Yukarıdaki örnekte, bootstrapScriptContent seçeneği istemcide global window.assetMap değişkenini ayarlayan ek bir iç içe <script> etiketi ekler. Bu, istemci kodunun aynı assetMap’i okumasını sağlar:

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

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

Sunucu ve istemci aynı assetMap propuna sahip App bileşenini render ettiği için hidrasyon hatası olmaz.


Yüklenirken daha fazla içeriği stream etmek

Stream işlemi, tüm veriler sunucudan henüz yüklenmemiş olsa bile kullanıcının içeriği görmesini sağlar. Örneğin, bir kapağın, arkadaş ve fotoğrafların bulunduğu kenar çubuğunun ve gönderilerin listelendiği bir profil sayfası düşünün:

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

<Posts /> bileşeni için gereken verilerin yüklenirken zaman aldığını düşünün. Tercihen, kullanıcıya gönderilerin yüklenmesini beklemeden profil sayfasının içeriğini göstermek isterseniz. Bunu yapabilmek için Posts’u <Suspense> sınırıyla sarın:

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

Bu işlem React’a Posts verileri yüklenmeden HTML’i stream etmesini söyler. React HTML’i önce yükleme fallback (PostsGlimmer)‘e gönderir, daha sonra Posts verilerini yüklediğinde, React kalan HRML’i yükleme fallback’inin yerini alması için satır içi <script> etiketiyle gönderir. Kullanıcın açısından, sayfada önce PostsGlimmer görünür daha sonra Posts yerini alır.

Daha ayrıntılı bir yükleme dizisi oluşturmak için <Suspense> sınırlarını iç içe geçirebilirsiniz

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

Bu örnekte, React sayfayı stream etmeye daha da önce başlayabilir. <Suspense> sınırlarıyla sarılmadıkları için sadece ProfileLayout ve ProfileCover render işlemini tamamlamalılardır. Ancak, Sidebar, Friends, ve Photos’un veri yüklemesi gerekiyorsa, React bunun yerine BigSpinner fallbackine HTML’i gönderir. Daha sonra, veriler yüklendikçe, tümü görünür olana kadar içerikler görüntülenmeye başlayacaktır.

Stream işleminin, React’ın tarayıcıya yüklenmesini veya uygulamanızın etkileşimli hale gelmesini beklemesine gerek yoktur. Sunucu tarafından gönderilen HTML içeriği diğer <script> etiketleri yüklenene kadar aşamalı bir şekilde gösterilecektir.

HTML streaminin nasıl çalıştığı hakkında daha fazla bilgi edinin.

Not

Yalnızca use ile okunan bir Promise gibi, Suspense boundary’yi aktive eden bir kaynaktan okunan data rendering sırasında suspend olur. Suspense, bir Effect veya event handler içinde fetch edilen data’yı detect etmez.


Shell’e neyin gideceğini belirleme

Uygulamanızda <Suspense> sınırları dışında kalan parçaya shell adı verilir:

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

Kullanıcı tarafından görülebilecek en erken yükleme durumunu belirler:

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

Tüm uygulamanızı root içinde <Suspense> sınırlarıyla sararsanız, shell sadece o dönme göstergesini içerecektir. Ancak, bu durum kullanıcı deneyimi açısından hoş karşılanmayabilir çünkü ekranda sadece dönme göstergesini görmek sayfanın yavaş olduğunu ve bira bekleyip ekranın gerçek halini görmekten daha can sıkıcı hissettirebilir. Bu yüzden <Suspense> sınırlarını shellin minimal ama tamamlanmış—tüm sayfa düzeninin iskeleti gibi hissedildiği yerlere yerleştirmek isteyeceksiniz.

Bütün shell render edildikten sonra onShellReady callbacki çağırılır. Genelde, stremi bu aşamadan sonra başlatırsınız:

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

onShellReady çağırıldığında, <Suspense> sınırları içinde iç içe geçmiş bileşenlerde veriler hala yükleniyor olabilir.


Sunucudaki çökmelerini günlüğe kaydetme

Varsayılan olarak, sunucudaki tüm hatalar konsolda günlüğe alınır. Bu davranışı çökme raporlarını günlüğe almak için geçersiz kılabilirsiniz:

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

Kişiselleştirilmiş onError olay yöneticisi kullanacaksanız yukarıdaki gösterildiği gibi hataları da günlüğe kaydedin.


Shell içindeki hatalardan kurtulma

Bu örnekte, shell ProfileLayout, ProfileCover ve PostGlimmer bileşenlerini içeriyor:

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

Bu bileşenleri render ederken bir hata oluşursa, React kullanıcıya gönderebileceği anlamlı bir HTML’e sahip olamayacaktır. Son çare olarak, sunucu tarafında oluşturmaya bağlı olmayan yedek bir HTML göndermek için onShellError’u geçersiz kılın:

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

Shell’i oluştururken bir hata oluşursa, hem onError hem de onShellError çağırılacaktır. onError’u hata raporu oluşturmada, onShellError’u da yedek HTML belgesini göndermek için kullanın. Yedek HTML’iniz hata sayfası olmak zorunda değildir. Bunun yerine, uygulamanızı sadece istemci tarafında render eden alternatif bir shell dahişl edebilirsiniz.


Shell dışındaki hatardan kurtulma

Bu örnekte, <Posts /> bileşeni <Suspense> ile sarılmıştır o yüzden shellin bir parçası değildir:

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

Posts bileşeninde veya onun içinde herhangi bir yerde hata oluşursa, React bunu gidermeye çalışacaktır:

  1. Bu, en yakın <Suspense> sınırlayıcısı (PostsGlimmer) için yükleme yedeklemesini HTML içinde yayımlayacaktır.
  2. Sunucu içinde Posts içeriğini render etmekten “vazgeçecektir.”
  3. JavaScript kodu istemci tarafında yüklendiğinde, React Posts’u istemci tarafında render etmeyi deneyecektir.

İstemcide Posts’un yeniden render edilmesi de başarısız olursa, React istemci tarafında hata verecektir. Render sırasında bütün hatalar gönderilince, en yakın üst hata sınırı hatanın kullanıcıya nasıl gösterileceğine karar verir. Pratikte, hatanın giderilemez olduğu kesinleşene kadar kullanıcı yükleme çubuğunu görür.

İstemci tarafında Posts’un yeniden render edilmesi başarılı olursa, sunucu tarafındaki yükleme yedeği istemcideki render çıktısıyla değiştirilir. Bu sayede, kullanıcı sunucuda hata olup olmadığını bilemez. Ancak, sunucunun onError callbacki ve istemcininonRecoverableError callbackleri çalışacağı için bir hata olduğunda haberiniz olacaktır.


Durum kodunu ayarlama

Stream işlemi bir takas sunar. Sayfayı stream etmeye hemen başlamalısınız ki kullanıcı da o kadar erken içeriği görebilsin. Ancak, stream işlemine bir kez başladığınızda, cevabın durum kodunu ayarlayamazsınız.

Uygulamanızı shell’e (tüm <Suspense> sınırlayıcılarının üstünde) ve geri kalan içerik olarak böldüğünüzde bu sorunun bir kısmını çözmüş olursunuz. Shell hata verirse, onShellError callbackiyle hata durum kodunu görebilirsiniz. Diğer türlü, uygulamanız istemci üzerinde hatadan kurtulabilir ve siz de “OK” durum kodunu gönderebilirsiniz.

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>Bir şey yanlış gitti.</h1>');
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});

Bileşen shell’in dışındaysa (ör. <Suspense> sınırlayıcısı içinde) hata gönderilir, React render etmeye devam eder. Bu, onError callbackinin çağırılacağını ancak yine de onShellError yerine onShellReadycallbacki alacağınız anlamına gelir. Bunun sebebi React, istemci tarafında yukarıda açıklandığı gibi hatadan kurtulmaya çalışacaktır.

Ancak isterseniz, bir şeyin hata verdiği gerçeğini kullanarak durum kodunu ayarlayabilirsiniz:

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>Bir şey yanlış gitti.</h1>');
},
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});

Bu, sadece başlangıç shell içeriğini oluştururken oluşan shell dışındaki hataları yakalayacaktır, o yüzden kapsamlı değildir. Bir içerikte hata olup olmadığı bilgisi önemliyse, shell içine taşıyabilirsiniz.


Farklı hataları farklı yollarla çözme

Hangi hatanın verildiğini görmek için kendi onError alt sınıfızı oluştabilirsiniz veya instanceof operatörünü kullanabilirsiniz. Örneğin, kişiselleştirilmiş NotFoundError’u tanımayabilirsiniz ve bileşeninizden çalıştırabilirsiniz. Daha sonra onError, onShellReady ve onShellError callbackleri hata tipine göre farklı işlemler yapabilir:

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>Bir şey yanlış gitti.</h1>');
},
onError(error) {
didError = true;
caughtError = error;
console.error(error);
logServerCrashReport(error);
}
});

Shelli yayımladığınızda ve stream işlemine başladığınızda durum kodunu değiştiremeyeceğinizi unutmayın.


Tarayıcılar ve statik oluşturma için tüm içerikleri bekleme

Stream işlemi daha iyi bi kullanıcı deneyimi sunar çünkü kullanıcı, içerik olur olmaz içeriği görebilir.

Ancak, tarayıcılar sayfanızı ziyaret ederken veya derleme zamanında sayfayı oluşturuyorsanız, aşamalı bir şekilde göstermek yerine içeriğin tamamının önce yüklenmesine izin verip ardından nihai HTML çıktısını oluşturmayı tercih edebilirsiniz.

onAllReady callbackini kullanarak bütün içeriğin yüklenmesini bekleyebilirsiniz:

let didError = false;
let isCrawler = // ...bot tespit stratejinize bağlı

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>Bir şey yanlış gitti.</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);
}
});

Düzenli bir ziyaretçi aşamalı şekilde yüklenen içerik streamine sahip olacaktır. Tarayıcı nihai HTML çıktısını tüm veriler yüklendikten sonra alacaktır. Ancak bu, tarayıcının bütün veriyi beklemesi anlamnına geliyor, bazılarının yüklenmesi yavaş olabilir veya hata olabilir. Uygulamanıza bağlı olarak, shelli de tarayıcınıza göndermeyi tercih edebilirsiniz.


Sunucu tarafında render işlemini iptal etme

Zaman aşımı sonrası sunucunuzun render işleminden “vazgeçmeye” zorlayabilirsiniz:

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

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

React kalan yükleme yedeklerini HTML olarak temizleyecek ve geri kalanını istemcide render etmeyi deneyecektir.