renderToPipeableStream
renderToPipeableStream, bir react ağacını pipelanabilir Node.js Streamine render eder.
const { pipe, abort } = renderToPipeableStream(reactNode, options?)- Referans
- Kullanım
- React ağaıcını HTML olarak Node.js Streamde render etmek
- Yüklenirken daha fazla içeriği stream etmek
- Shell’e neyin gideceğini belirleme
- Sunucudaki çökmelerini günlüğe kaydetme
- Shell içindeki hatalardan kurtulma
- Shell dışındaki hatardan kurtulma
- Durum kodunu ayarlama
- Farklı hataları farklı yollarla çözme
- Tarayıcılar ve statik oluşturma için tüm içerikleri bekleme
- Sunucu tarafında render işlemini iptal etme
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ıAppbileş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:bootstrapScriptsgibidir, ancak bunun yerine<script type="module">emit eder. -
optional
identifierPrefix: React’inuseIdtarafı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-srcContent-Security-Policy için script’lere izin veren birnoncestring’i. -
optional
onAllReady: Hem shell hem de tüm ek content dahil olmak üzere tüm rendering tamamlandığında tetiklenen bir callback. BunuonShellReadyyerine 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ızcaconsole.errorçağırır. Crash report’ları loglamak için override ederseniz, yine deconsole.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çinpipeç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 neonShellReadyne deonAllReadyç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:
pipeişlemi, HTML’i sağlanan Yazılabilir Node.js Stream’ini çıktı olarak verir. Streami etkinleştirmek istiyorsanızonShellReady’de veya tarayıcılar ve statik oluşturma içinpipe’ı çağırın .abortSunucu 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
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.
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:
- Bu, en yakın
<Suspense>sınırlayıcısı (PostsGlimmer) için yükleme yedeklemesini HTML içinde yayımlayacaktır. - Sunucu içinde
Postsiçeriğini render etmekten “vazgeçecektir.” - 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.