Серверны рэндэрынг праграмы на React 18 з Express і Babel
Пераводзім праект Create React App на серверны рэндэрынг з Express і Babel: renderToString, hydrateRoot, MemoryRouter, а потым — як SSR робяць Remix і Gatsby.
Крыніцы: A Hands-on Guide for a Server-Side Rendering React 18 App
Рэндэрынг з бакульнай стороны для React сур’ёзна змяніўся з’явілася версія React 18. Наведзены выкладкі аднасоўваюцца да React 18, Create React App 5 і React Router 6, і выконваюцца на базе стандартнага праекта Create React App, які пашагоўна ператвараецца. Рэндэрынг павінен выконвацца на серверэ, таму патрэбны працэсы Node. Звычны Node таксама падходзіць, але nodemon ўдобны пад час розработы, таму што ён перазапускае прыложэнне кожны раз, калі змініюецца файл. Глобальная установка выглядае так:
% npm install -g nodemon
CSR проты SSR за калькамі
Пры рэндарынгу з боку кліента (CSR) прызначальнік атрымлеў майжэ порожняю HTML-сторанку, а JavaScript запалняе ўсё кантэнцтва. Першыя завантажэння можу быць повольнымі, але паслячыя навігацыі ведуцца швядка, а на серверэ ўтвараецца мінімальны навантажэння, таму што працу выконваюць бібліятэкі JavaScript. Адыянтам ёст кансалтынацыя для пошуковых сэрвісаў, адтуды што краулер не знаходзіць статычнага кантэнцтва для чытання.
Пры рэндарынгу з боку сервера прызначальнік атрымлеў готовую HTML-сторанку, якую ўжо адобразіў сервер. Гэта дае прынады для SEO, а перша адобразка ведуцца швядка. Адыянтам ёст тое, што паслячыя змены часта выклікаюць павольнае перазавантажэння всей сторанкі, што можа збільшыць навантажэння на сервер.
Калькамі дапамагаюць калькі:
- Прыклад аплікацыі з адной сторанкай (SPA) выкарыстоўвае CSR. У замест на аддачу окрэмых HTML-файлаў па кожнам маршруту, ёна дынамічна адрабатвае кожны маршрут унутры прызначальніка.
SSR насправдзе ўжо існаваў ранейш за CSR. Ён вернуўся у савэцкай форме, пабудованай на універсальным JavaScript, і самэ гэта зараз зазвычай маюць на увазе, калі говораць пра SSR.
Базавая аплікацыя CSR
Створыце новы праект:
% yarn create react-app react-ssr
% cd react-ssr
Пачатакніце з npm start. У вікні прыгледу браузера Elements спінаючыся логаўк і тэкст паказваюцца як HTML, створаны JavaScript.
Гэта класычны прыклад CSR: скрыпты ствараюць тэкстовы кантэнт HTML. Вікно Network паказвае, што на самай працэ ўсунуў сервер.
У тэле знаходзіцца пакет JavaScript, а не рэальны кантэнт, таму для індэксавання у роўеру практычна няма нічы значныя.
Распаўненне версіі для рэальной працы
Для SSR патрэбна версія для рэальной працы, таму першы крок — стварэнне невялікага сервера Express, які распаўняе скомпіляваны выход React. Раней опісаная настаўка React, гатовая да рэальной працы, стварае падставу. Файл сервера бывае двух варыянтаў: адны ёсць у фармаце CommonJS, server/index.js, а другі — у фармаце ES module, server/index.mjs.
Выконайце npm run build, ў рэзультаце чаго будзе створана збірка, а пасля запусціце сервер за дапамою nodemon server або nodemon server/index.mjs. Карточка Network цяпер паказвае адну збірку JavaScript (лінія 12) і порожню маркап-тэкстуру (лінія 17). Яшчо не відбываецца нічога, што б сервер генераваў, таму гэта яшчо ёсць CSR.
Тры крокі для SSR у Express
Ёжы SSR у серверэ Express трэба адбыць за тры крокі.
Шаг 1: гідратацыя на кліенце. ReactDOM.hydrateRoot() падобны ReactDOM.createRoot(), з сігнатурой hydrateRoot(container, element[, options]). Ён прызначаны для кантэйнера, чыё HTML ужэ было створана ReactDOMServer. Паколькі вузел ужэ мае маркап, выроблены серверам, React яго застаўляе і толькі прыўязвае апрацоўшчыкі змаганняў, што спрыяе шырокаму завантажэнню. Выклік знаходзіцца на 7-й лініі src/index.js.
Шаг 2: рэндарыўванне на серверэ. React поставляе об’ект ReactDOMServer, каб ператворыць компоненты на статычны маркап, тады браузер отрымае сторанку, якая ужо заполнена дадзеннямі. Паколькі код React — універсальны JavaScript, тыя ж самыя компоненты працуюць як на кліенце, так і на серверэ.
Шаг 3: адпаведнае рашэнне спецыфічных патрабаванняў сторанкі. Кожны маршрут можа выкарыстоўваць свае сабэйскія патрабавання, як будзе паказана ў наступных раздзелах.
Як параджуць Node разумець JSX
Ўсё большае колька пакетаў можа запусціць SSR. @babel/register ўваходзіць у ліст самых простых. З’явіце яго разам з babel-plugin-transform-assets:
% yarn add -D @babel/register
% yarn add -D babel-plugin-transform-assets
Оба знаходзяцца ў devDependencies:
"devDependencies": {
"@babel/register": "^7.18.9",
"babel-plugin-transform-assets": "^1.0.2"
}
@babel/register скомпілюе файлы на лету, і яго можна налаштаваць для трансформацыі JSX і рэсурсаў. Існуе адна праблема: ён не можа скомпілюваць натыўныя ES-модулі Node.js на лету, адтолькі ўсё яшчэ не існуе стабільнага API для падключэння да працэсу завантажэння ES-модуліў. Самэ гэтая прычына змусiла ў эксперыментах з SSR вжываць файлы CommonJS.
React 18 перэрабіў свае серверныя API і пераказаў іх пад react-dom/server. Функцыя renderToString яшчэ працюе, але ў яё падтрымцы Suspense ёсць абмежэння. Новыя API, у тым часе renderToPipeableStream, абоўсюдна падтрымліваюць Suspense на серверы, а таксама практыку стрымінгу у рамках SSR.
Прагляд сервера SSR
Апошню версію файла server/index.js паспяшна пояснюецца ніжэй па номерамі лініяў.
- Лініі з 1 да 15 налаштовуюць Babel за дапамою хука
require, тады файлы скомпілююцца праз адразу ж ў момент завантажэння.
@babel/preset-env дозволяе коду вікорыстоўваць найновейшы JavaScript без неабяжнасці ручнага выбірання трансформацый сінтаксісу для кожнай мэтавай асяроддзя. @babel/preset-react автаматычна імпортуе функцыі, у якія компілюецца JSX.babel-plugin-transform-assets, які візначае, як ператвараюцца статычныя медыя-файлы. Без яго таг svg спрычынае памылку SyntaxError: Unexpected token '<'.React, у 17-й — ReactDOMServer, а у 18-й — стандартны экспорт з src/App.js.Тут важна парадыгма выканання коду. Каментар app.use() на 47-й лініі павінен выканацца пасля app.get() на лініях 25–45. Якбы ён выканаўся першым, ён бы сэрвірав статычныя файлы, укладзеныя ў index.html, для корневага маршруту, і app.get() ніколі бы не запрацаваў.
На 26-й лініі фіксуецца URL кожнага запіту. Для праекта Create React App спіс выглядае так:
Request URL = /
Request URL = /static/js/main.b92cc87b.js
Request URL = /static/css/main.073c9b0a.css
Request URL = /static/media/logo.06e73328.svg
Request URL = /static/css/main.073c9b0a.css.map
Request URL = /static/js/main.b92cc87b.js.map
Request URL = /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg
Request URL = /favicon.ico
Request URL = /manifest.json
Request URL = /logo192.png
У рэях 27–29 праблема ў тым, што app.get() адрасавае толькі корневы маршрут, а статычныя рэсурсы пераходзяць да наступнага мідлвэра ў рэе 47.
У рэе 30 вызначаецца ReactDOMServer.renderToString(element), каб стварыць HTML на серверэ. Было бы прыродна напісаць ReactDOMServer.renderToString() безпосерэдзя, але тады JSX павінен быў бы знаходзіцца ў іншым файле, чым той, дзе налаштавана функцыя @babel-register, таму і выкарыстоўваецца окрэмы элемент.
У рэе 31 выводзіцца маркап, створаны серверам:
<div class="App"><header class="App-header"><img src="static/media/logo.06e73328.svg" class="App-logo" alt="logo"/><p>Edit <code>src/App.js</code> and save to reload.</p><a class="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">Learn React</a></header></div>
Лінія 33 завантажае файл index.html для рэлізу, а лініі 34–44 чытаюць яго ўмест. Якшто няма памылак, створаны маркап (лінія 42) дадаецца ў коранавы тэг, і готовы index.html стае адказам на першы запіт.
Перакананне ў правямальнасці
Запускайце nodemon server і зноў адзірніце вкладку Network. Завантажаны дакумент цяпер мае маркап, створаны серверам. Лініі 17–27 тэла адпаведаюць цэлай сторанцы, што дае інструментам SEO чаго-то рэальна гледзець.
Для болей строгага тэсту ачыніце chrome://settings/content/javascript і выключыце JavaScript.
Сторанца продовжае атрыбутавацца без JavaScript.
Гэта і ёсь SSR.
Дадаць маршруты
Аплікацыя з адной сторанкай — гэта проста, таму наступны вопыт стосуецца калькі маршрутаў. Пачніце з інсталлявання react-router-dom:
% yarn add react-router-dom
Ён прыяўляецца да dependencies:
"dependencies": {
"react-router-dom": "^6.11.2"
}
Пасля чаго зменіце src/App.js. У 1-й лініі імпортуецца React, які неабяжна для SSR. У 2-й лініі імпортуецца MemoryRouter замест BrowserRouter. Браузерны рутэр выкарыстоўвае HTML5 pushState history API, які Node.js не падтрымляе. MemoryRouter зберагае історыю URL у памяці, не паўтараючы дзеянні ў стрэлцы адрэсы, што падходзіць для тэстаў і сэрвэраў, якія не ўтвараюць браузер. Іншы варыянт — StaticRouter з react-router-dom/server, але ён не падходзіць для складных маршрутаў. У лініях 34-40 выкарыстоўваецца MemoryRouter.
Зноў запусціце працэс будавання за дапамою npm run build, запусціце nodemon server і адвідавайце http://localhost:8080.
SSR працюе, але ёсць адна проблема: URL у стрэлцы адрэсы не адкладзіцца.
Дзе самастайна рэалізаваны SSR стае складным
Рутаванне працюе з гэтым абяканнем, а іншыя проблемы яшчэ чакаюць рашэння, такія як запрашанне дадзейнаў і Redux. Работа на стороне сервера ёсць менш простай, чым на стороне кліента, і кожная старонка можа патрабаваць спецыяльнага адносу.
Няма прычыны рашаць усё гэта вручную, адколі калькі фрэймворкаў і бібліятэкаў уключаюць SSR. Remix — гэта фрэймворк для веб-праектаў усіх рангаў, які выходзіць з фундаменталей веба і працюе да інтэрфейсу корыстніка, уключаючы SSR і іншыя функцыі, без зайвага коду. Код кліента вызывае hydrateRoot на 4-й лініі, а код сервера — renderToPipeableStream на 4-й лініі.
У React 18 была адаптавана функцыя renderToPipeableStream(element, options). Як толькі яна прабуе працаваць з элементам React, яна стварае пачатковы HTML і вяртае стрым. У гэтым стрыме ёсць метод pipe(res) для перадачы рэзультата далей і метод abort() для анулювання запіта. Падтрымка функцыі Suspense ўсёвыключна повная, а HTML з запазджаным контэнтам выдаўаецца у віглядзе стрыма без пазнейшага „прыявлення“ контэнту, так як адразу ж вставляюцца тэгі <script>, якія запалняюць прызначаны прастор.
У вікні Network тэла староні-індэксу Remix ужо мае цэлую сторону, таму гэта ёсць SSR.
Gatsby, Next.js, NuxtJS, Quasar і SvelteKit таксама прыносяць падтрымку SSR і супаўзвязаныя функцыі без дадатковага коду, хоча кожны з іх адначасова падае ўвагу на SSR трохі інакш. На староне-індэксе Gatsby ў вікні Network таксама паказваецца цэлая структура стороны.
Gatsby — гэта быстрый, надзямны генератор статычных сайтаў, які выкарыстоўвае React для атракцыя статычнага кантэнту, і ён дазволяе зменіць статычны HTML пад час яго атракцыя на серверэ.
SSR чы статычная атракцыя
Калі сторана містыць толькі статычныя даны, статычная атракцыя ўсё прыгожэй. Якшча адпаведзь ёсць дынамічная, тады лепшы варант — SSR. Інодзе найлепей падходзіць гібрыдны падход. SSR выконваецца за запатраннем, калі толькі запрашуецца файл, тады як статычная атракцыя выконваецца раз у час пабудовы. І адна, і другая методы ёсць прыемныя для SEO, таму што у обох случаях кантэнт знаходзіцца ў тэле HTML.
Падсумак
Настройка SSR для Create React App складаецца з трох элементаў:
- Атракцыя маркапаў, атракцыйных на серверэ, за дапамогай
ReactDOM.hydrateRoot(). - Атракцыя компанентаў да статычных маркапаў за дапамогай об’екта
ReactDOMServer, чыяі API падтрымліваюць як SSR, так і статычную атракцыю.
Для рэальнага праектуяглыкіе вярны выбор — фрамвэрк або біблекае, які ужо гэта рэгліяе.
Want to Connect?
If you are interested, check out my directory of web development articles.