renderToPipeableStream
renderToPipeableStream нь React модыг дамжуулах боломжтой Node.js Stream болгон дүрсэлнэ.
const { pipe, abort } = renderToPipeableStream(reactNode, options?)- Лавлагаа
- Ашиглалт
- React модыг HTML болгон Node.js Stream рүү дүрслэх
- Контент ачаалагдах тусам урсгалаар дамжуулах
- Shell-д юу орохыг тодорхойлох
- Серверийн уналтыг бүртгэх
- Shell доторх алдаанаас сэргээх
- Shell-ийн гаднах алдаанаас сэргээх
- Status code тохируулах
- Өөр өөр алдааг ялгаатай аргаар боловсруулах
- Crawler болон статик үүсгэлтэд бүх контент ачаалагдахыг хүлээх
- Серверийн дүрслэлийг зогсоох
Лавлагаа
renderToPipeableStream(reactNode, options?)
React модыг HTML болгон Node.js Stream рүү дүрслэхийн тулд renderToPipeableStream-ийг дуудна.
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});Серверийн үүсгэсэн HTML-ийг интерактив болгохын тулд клиент талд hydrateRoot-ийг дуудна.
Параметрүүд
-
reactNode: HTML болгон дүрслэх React node. Жишээлбэл,<App />шиг JSX элемент. Энэ нь баримт бичгийг бүхэлд нь төлөөлөх ёстой тулAppкомпонент<html>tag-ийг дүрслэх хэрэгтэй. -
optional
options: Урсгалын тохиргоонуудыг агуулсан объект.- optional
bootstrapScriptContent: Заасан тохиолдолд энэ тэмдэгт мөрийг inline<script>tag дотор байрлуулна. - optional
bootstrapScripts: Хуудаст гаргах<script>tag-уудын URL тэмдэгт мөрийн массив.hydrateRoot-ийг дууддаг<script>-ийг оруулахдаа ашиглана. Клиент талд React огт ажиллуулахгүй бол үүнийг орхино. - optional
bootstrapModules:bootstrapScripts-тэй адил боловч оронд нь<script type="module">гаргана. - optional
identifierPrefix:useId-ийн үүсгэсэн ID-д React-ийн ашиглах тэмдэгт мөрийн угтвар. Нэг хуудсанд олон root ашиглах үед зөрчил үүсэхээс сэргийлнэ.hydrateRoot-д дамжуулсан угтвартай ижил байх ёстой. - optional
namespaceURI: Урсгалын root namespace URI-г агуулсан тэмдэгт мөр. Анхдагч нь ердийн HTML. SVG-д'http://www.w3.org/2000/svg', MathML-д'http://www.w3.org/1998/Math/MathML'дамжуулна. - optional
nonce:script-srcContent-Security-Policy-д script зөвшөөрөхnonceтэмдэгт мөр. - optional
onAllReady: Shell болон бүх нэмэлт контент дүрслэгдэж дуусахад ажиллах callback. Crawler болон статик үүсгэлтэдonShellReady-ийн оронд ашиглаж болно. Эндээс урсгалыг эхлүүлбэл шат дараалсан ачаалалт байхгүй бөгөөд урсгал эцсийн HTML-ийг агуулна. - Canary only optional
onBrowserBailout:browser()-оос сэргээхдээ браузераар солих Suspense fallback үлдээх үед React-ийн дууддаг callback. Энэ нь зөвхөн браузерт дүрслэх үйлдлийг тайлбарласанErrorболонcomponentStackагуулсанerrorInfoобъектыг хүлээн авна.browser-т шалтгаан дамжуулсан болerror.cause-аас авна. Анхдагчаар React юу ч хийхгүй. Зөвхөн браузерт дүрслэх үйлдлийг хэрхэн мэдээлэхийг үзнэ үү. - optional
onError: Серверт сэргээж болох эсвэл болохгүй алдаа гарах бүрд ажиллах callback. Анхдагчаар зөвхөнconsole.error-ийг дуудна. Үүнийг уналтын тайлан бүртгэхээр өөрчилсөн чconsole.error-ийг үргэлжлүүлэн дуудаарай. Мөн shell гарахаас өмнө status code тохируулахад ашиглаж болно. - optional
onShellReady: Анхны shell дүрслэгдмэгц ажиллах callback. Энд status code тохируулж,pipe-ийг дуудан урсгалыг эхлүүлж болно. Дараа нь React shell-ийн араас нэмэлт контентыг урсгалаар дамжуулах бөгөөд inline<script>tag-ууд HTML-ийн ачаалж буй fallback-ийг бэлэн контентоор солино. - optional
onShellError: Анхны shell-ийг дүрслэхэд алдаа гарвал ажиллах callback. Алдааг аргумент болгон хүлээн авна. Урсгал руу хараахан byte гараагүй,onShellReadyболонonAllReadyдуудагдахгүй тул fallback HTML shell гаргаж болно. - optional
progressiveChunkSize: Нэг chunk дэх byte-ийн тоо. Анхдагч heuristic-ийн талаар дэлгэрэнгүй уншина уу.
- optional
Буцаах утга
renderToPipeableStream нь хоёр method-той объект буцаана:
pipeнь HTML-ийг өгсөн Writable Node.js Stream рүү гаргана. Урсгалыг идэвхжүүлэх болonShellReadyдотор, crawler болон статик үүсгэлтэдonAllReadyдоторpipe-ийг дуудна.abortнь серверийн дүрслэлийг зогсоож, үлдсэн хэсгийг клиент талд дүрслэх боломж олгоно.
Ашиглалт
React модыг HTML болгон Node.js Stream рүү дүрслэх
React модыг HTML болгон Node.js Stream рүү дүрслэхийн тулд 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 компонент-той хамт bootstrap <script> замуудын жагсаалтыг өгөх хэрэгтэй. Root компонент нь root <html> tag-ийг багтаасан баримт бичгийг бүхэлд нь буцаах ёстой.
Жишээлбэл, дараах байдлаар харагдаж болно:
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 нь үүссэн HTML урсгалд doctype болон таны bootstrap <script> tag-уудыг оруулна:
<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>Клиент талд bootstrap script нь hydrateRoot-ийг дуудаж document-ийг бүхэлд нь hydrate хийх ёстой:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);Ингэснээр серверийн үүсгэсэн HTML-д event listener-үүд холбогдож, интерактив болно.
Гүнзгий үзэцгээе
Эцсийн asset URL-уудыг (JavaScript болон CSS файл гэх мэт) build-ийн дараа ихэвчлэн hash-ладаг. Жишээлбэл, styles.css-ийн оронд styles.123456.css үүсэж болно. Статик asset-ийн файлын нэрийг hash-ласнаар нэг asset-ийн build бүр өөр нэртэй байна. Ингэснээр тодорхой нэртэй файлын агуулга хэзээ ч өөрчлөгдөхгүй тул статик asset-д урт хугацааны cache-ийг аюулгүй идэвхжүүлж болно.
Гэвч asset URL-уудыг build дууссаны дараа л мэдэх боломжтой бол source code-д урьдчилан бичиж чадахгүй. Жишээлбэл, өмнөх шиг "/styles.css"-ийг JSX-д hardcode хийх нь ажиллахгүй. Эдгээрийг source code-оос тусгаарлахын тулд root компонент prop-оор дамжуулсан map-аас жинхэнэ файлын нэрийг уншиж болно:
export default function App({ assetMap }) {
return (
<html>
<head>
...
<link rel="stylesheet" href={assetMap['styles.css']}></link>
...
</head>
...
</html>
);
}Сервер талд <App assetMap={assetMap} />-ийг дүрсэлж, 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);
}
});
});Сервер одоо <App assetMap={assetMap} />-ийг дүрсэлж байгаа тул hydration алдаанаас сэргийлэхийн тулд клиент талд мөн assetMap-тай дүрслэх хэрэгтэй. assetMap-ийг serialize хийж клиент рүү дараах байдлаар дамжуулна:
// 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 тохиргоо нь клиент талын global window.assetMap хувьсагчийг оноох нэмэлт inline <script> tag оруулна. Ингэснээр клиент код ижил assetMap-ийг уншиж чадна:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App assetMap={window.assetMap} />);Клиент болон сервер хоёулаа App-ийг ижил assetMap prop-той дүрслэх тул hydration алдаа гарахгүй.
Контент ачаалагдах тусам урсгалаар дамжуулах
Урсгалаар дамжуулснаар сервер бүх өгөгдлийг ачаалж дуусахаас өмнө хэрэглэгч контентыг харж эхэлнэ. Жишээлбэл, нүүр зураг, найзууд болон зургуудтай хажуугийн хэсэг, нийтлэлүүдийн жагсаалт харуулдаг профайл хуудсыг авч үзье:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Posts />
</ProfileLayout>
);
}<Posts />-ийн өгөгдлийг ачаалахад хугацаа ордог гэж төсөөлье. Нийтлэлүүдийг хүлээлгүй профайл хуудасны бусад контентыг хэрэглэгчид харуулах нь зохимжтой. Үүний тулд Posts-ийг <Suspense> boundary дотор байрлуулна:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}Ингэснээр Posts өгөгдлөө ачаалахаас өмнө HTML-ийг урсгалаар дамжуулж эхлэхийг React-д заана. React эхлээд ачаалалтын fallback (PostsGlimmer)-ийн HTML-ийг илгээнэ. Posts өгөгдлөө ачаалж дуусмагц үлдсэн HTML болон ачаалалтын fallback-ийг уг HTML-ээр солих inline <script> tag-ийг илгээнэ. Хэрэглэгч эхлээд PostsGlimmer-ийг харах бөгөөд дараа нь Posts-оор солигдоно.
Ачааллын дарааллыг илүү нарийн удирдахын тулд <Suspense> boundary-нуудыг давхарлан байрлуулж болно:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}Энэ жишээнд React хуудсыг бүр ч эрт урсгалаар дамжуулж эхэлнэ. ProfileLayout болон ProfileCover ямар нэг <Suspense> boundary-д ороогүй тул зөвхөн тэд эхлээд дүрслэгдэж дуусах ёстой. Харин Sidebar, Friends, эсвэл Photos өгөгдөл ачаалах шаардлагатай бол React оронд нь BigSpinner fallback-ийн HTML-ийг илгээнэ. Өгөгдөл бэлэн болохын хэрээр бүх контент харагдаж дуустал дараалан гарч ирнэ.
Урсгалаар дамжуулахын тулд React браузерт ачаалагдах эсвэл апп интерактив болохыг хүлээх шаардлагагүй. Серверийн HTML контент ямар нэг <script> tag ачаалагдахаас өмнө шат дараалан харагдана.
HTML-ийг урсгалаар дамжуулах ажиллагааны талаар дэлгэрэнгүй уншина уу.
Shell-д юу орохыг тодорхойлох
Аппын ямар ч <Suspense> boundary-гийн гадна байрлах хэсгийг бүрхүүл (shell) гэнэ:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}Энэ нь хэрэглэгчийн хамгийн түрүүнд харах боломжтой ачааллын төлөвийг тодорхойлно:
<ProfileLayout>
<ProfileCover />
<BigSpinner />
</ProfileLayout>Хэрэв аппыг бүхэлд нь root дээрх <Suspense> boundary дотор байрлуулбал shell зөвхөн spinner агуулна. Гэвч дэлгэц дээр том spinner харах нь бага зэрэг удаан хүлээгээд жинхэнэ layout харахаас ч удаан, таагүй санагдаж болох тул хэрэглэгчийн туршлага муудна. Тиймээс <Suspense> boundary-нуудыг shell нь хуудасны бүх layout-ийн араг яс мэт хамгийн бага боловч бүрэн байхаар байрлуулах нь зүйтэй.
Shell бүхэлдээ дүрслэгдэж дуусахад onShellReady callback ажиллана. Урсгалыг ихэвчлэн энэ үед эхлүүлнэ:
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});onShellReady ажиллах үед давхарласан <Suspense> boundary доторх компонентууд өгөгдлөө ачаалсаар байж болно.
Серверийн уналтыг бүртгэх
Анхдагчаар серверийн бүх алдааг console-д бүртгэнэ. Уналтын тайлан бүртгэхийн тулд энэ үйлдлийг өөрчилж болно:
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});onError-ийн өөрийн хэрэгжүүлэлтийг өгсөн бол дээрхийн адил алдааг console-д мөн бүртгэхээ бүү мартаарай.
Shell доторх алдаанаас сэргээх
Энэ жишээнд shell нь ProfileLayout, ProfileCover, PostsGlimmer-ийг агуулна:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}Эдгээр компонентыг дүрслэх үед алдаа гарвал React-д клиент рүү илгээх утга бүхий HTML байхгүй. Серверийн дүрслэлээс хамаарахгүй fallback HTML-ийг эцсийн арга болгон илгээхийн тулд onShellError-ийг өөрчилнө:
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 үүсгэх үед алдаа гарвал onError болон onShellError хоёулаа ажиллана. Алдааг мэдээлэхэд onError, fallback HTML баримт бичиг илгээхэд onShellError-ийг ашиглана. Fallback HTML заавал алдааны хуудас байх албагүй. Оронд нь аппыг зөвхөн клиент талд дүрслэх өөр shell оруулж болно.
Shell-ийн гаднах алдаанаас сэргээх
Энэ жишээнд <Posts /> компонент <Suspense> дотор байгаа тул shell-ийн хэсэг биш:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}Posts компонент эсвэл түүний дотор алдаа гарвал React алдаанаас сэргээхийг оролдоно:
- Хамгийн ойрын
<Suspense>boundary-гийн ачаалалтын fallback (PostsGlimmer)-ийг HTML рүү гаргана. Postsконтентыг сервер дээр дүрслэх оролдлогоо зогсооно.- JavaScript код клиент талд ачаалагдахад React
Posts-ийг клиент дээр дүрслэхээр дахин оролдоно.
Posts-ийг клиент талд дахин дүрслэх оролдлого мөн бүтэлгүйтвэл React алдааг клиент дээр шиднэ. Дүрслэх үед шидэгдсэн бүх алдааны адил хамгийн ойрын эцэг error boundary алдааг хэрэглэгчид хэрхэн харуулахыг тодорхойлно. Практикт алдаанаас сэргээх боломжгүй нь тодорхой болох хүртэл хэрэглэгч ачаалалтын заагч харна гэсэн үг.
Posts-ийг клиент талд дахин дүрслэх оролдлого амжилттай бол серверийн ачаалалтын fallback клиент дүрслэлийн үр дүнгээр солигдоно. Хэрэглэгч серверийн алдаа гарсныг мэдэхгүй. Гэхдээ алдааны талаар мэдэгдэхийн тулд серверийн onError болон клиентийн onRecoverableError callback-ууд ажиллана.
Status code тохируулах
Урсгалаар дамжуулахад харилцан буулт бий. Хэрэглэгч контентыг аль болох эрт харахын тулд хуудсыг хурдан дамжуулж эхлэхийг хүснэ. Гэвч урсгал эхэлсний дараа response-ийн status code-ийг өөрчлөх боломжгүй.
Аппыг shell (бүх <Suspense> boundary-гийн гаднах хэсэг) болон үлдсэн контент гэж хувааснаар энэ асуудлын нэг хэсгийг шийдсэн. Shell-д алдаа гарвал алдааны status code тохируулах боломжтой onShellError callback ирнэ. Үгүй бол апп клиент талд сэргээх боломжтой тул “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);
}
});Shell-ийн гаднах (<Suspense> boundary доторх) компонент алдаа шидвэл React дүрслэхээ зогсоохгүй. onError callback ажиллах боловч onShellError-ийн оронд onShellReady ирнэ. Учир нь React дээр тайлбарласны дагуу алдаанаас клиент талд сэргээхийг оролдоно.
Гэхдээ хүсвэл алдаа гарсныг ашиглан 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);
}
});Энэ нь зөвхөн анхны shell контентыг үүсгэх үед shell-ийн гадна гарсан алдааг барих тул бүх алдааг хамрахгүй. Тодорхой контентод алдаа гарсан эсэхийг мэдэх нь зайлшгүй бол уг контентыг shell рүү шилжүүлж болно.
Өөр өөр алдааг ялгаатай аргаар боловсруулах
Та өөрийн Error дэд классуудыг үүсгэж, ямар алдаа шидэгдсэнийг instanceof operator-оор шалгаж болно. Жишээлбэл, өөрийн NotFoundError-ийг тодорхойлоод компонентоос шиднэ. Дараа нь onError, onShellReady, onShellError callback-ууд алдааны төрлөөс хамааран өөр үйлдэл хийж чадна:
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);
}
});Shell-ийг гаргаж урсгалыг эхлүүлсний дараа status code-ийг өөрчлөх боломжгүйг санаарай.
Crawler болон статик үүсгэлтэд бүх контент ачаалагдахыг хүлээх
Контент бэлэн болохын хэрээр хэрэглэгч харж чаддаг тул урсгалаар дамжуулах нь илүү сайн хэрэглэгчийн туршлага өгнө.
Гэхдээ crawler хуудсанд зочлох үед эсвэл build хийхдээ хуудас үүсгэж байгаа бол контентыг шат дараалан харуулахын оронд эхлээд бүгдийг нь ачаалаад эцсийн HTML үр дүнг гаргахыг хүсэж болно.
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);
}
});Энгийн зочин шат дараалан ачаалагдах контентын урсгалыг авна. Crawler бүх өгөгдөл ачаалагдсаны дараа эцсийн HTML үр дүнг хүлээн авна. Гэхдээ crawler ачаалахад удаан эсвэл алдаа гарч болох өгөгдлийг оролцуулан бүх өгөгдлийг хүлээх шаардлагатай болно. Аппаасаа хамааран crawler-т мөн shell илгээхээр сонгож болно.
Серверийн дүрслэлийг зогсоох
Тодорхой хугацааны дараа серверийн дүрслэлийг хүчээр зогсоож болно:
const { pipe, abort } = renderToPipeableStream(<App />, {
// ...
});
setTimeout(() => {
abort();
}, 10000);React үлдсэн ачаалалтын fallback-уудыг HTML болгон гаргаж, бусад хэсгийг клиент талд дүрслэхийг оролдоно.