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ıApp
bileşeni<html>
etiketini render etmelidir. -
isteğe bağlı
options
: Stream seçenekleri olan bir nesne.- isteğe bağlı
bootstrapScriptContent
: Eğer belirtilmişse, bu string satır içi<script>
etiketinin içine yerleştirilecektir. - isteğe bağlı
bootstrapScripts
:<script>
etiketlerinin sayfada yayınlayacağı string tipindeki URL’lerin dizisi. Kullanmak için,hydrateRoot
’u çağıran<script>
etiketiketine dahil edin. React’ı istemci üzerinde çalıştırmak istemiyorsanız atlayınız. - isteğe bağlı
bootstrapModules
:bootstrapScripts
gibidir, ancak<script type="module">
çıktısı verir. - isteğe bağlı
identifierPrefix
: React tarafındanuseId
ile üretilen ID’ler için kullanılan ön ektir. Aynı sayfada birden fazla kök kullanılıyorsa karışıklıkları önlemek için uygundur.hydrateRoot
’a gönderilen ön ek ile aynı olmalıdır. - isteğe bağlı
namespaceURI
: Stream için kök namespace URI olan bir stringtir. Standart HTML’de varsayılandır. SVG için'http://www.w3.org/2000/svg'
ya da MathML için'http://www.w3.org/1998/Math/MathML'
iletin. - isteğe bağlı
nonce
:nonce
stringi,script-src
Content-Security-Policy scriptlerine izin vermek için kullanılır. - isteğe bağlı
onAllReady
: shell ve tüm ek content’ler de dahil olmak üzerebütün render işlemi tamamlandığında çağırılan callbacktir.onShellReady
tarayıcılar ve statik oluşturma için yerine kullanabilirsiniz. Streami buradan başlatırsanız, herhangi bir aşamalı yükleme almayacaksınız. Stream, en son HTML’i içerecektir. - isteğe bağlı
onError
: Kurtarılabilir veya kurtarılamaz sunucu hatalarında çağırılan callbacktir. Varsayılan olarak sadececonsole.error
’u çağırır. Günlük kitlenme raporularına olarak geçersiz kılsanız bileconsole.error
’u çağırdığınızdan emin olun. Ayrıca shell yayınlanmadan önce durum kodunu ayarlamak için de kullanabilirsiniz. - isteğe bağlı
onShellReady
: Başlangıç shell’i render edildikten hemen sonra çağırılan callbacktir. Durum kodunu ayarlamak ve streami başlatmak içinpipe
’ı çağırabilirsiniz. React shellden sonra ek contentleri stream edecektir ve bu contentleri HTML yükleme fallbacklerini değiştiren<script>
etiketleriyle birlikte stream edecektir. - isteğe bağlı
onShellError
: Başlangıç shelli render edilirken bir hata varsa çağırılan callbacktir. Hatayı argüman olarak alır. Stream için herhangi bir byte yayınlanmaz veonShellReady
ya daonAllReady
çağırılmaz. Böylece bir HTML shellinin fallback çıktısını alabilirsiniz. - isteğe bağlı
progressiveChunkSize
: Yığındaki byte sayısıdır. Varsayılan buluşsal yöntem hakkında daha fazlasını okuyun.
- isteğe bağlı
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ızonShellReady
’de veya tarayıcılar ve statik oluşturma içinpipe
’ı ç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 çatınıza 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
Posts
iç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 onShellReady
callbacki 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 ters 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.