Адкрыцьце ресурса, а не ўсуненне перадачы: пазначанне додаткаў на React через HTTP/3
Дазвольце дазнаць, чаму HTTP/3 прыводзіць да таго, што пазнейшая адкрыцьце ресурса становіцца справжнім бутылочным горлам у додатках на React, і як функциі preloadModule, preinit, Early Hints і chunking гэта рашуюць.
Апгрэйд сервера да HTTP/3 спрыя шырокаму перадачы данных, пры тым чым багато застосоў на базе React практычна не відчуваюць прышвартавання. Зазвычай прычына крыўдзіцца не самай перадачай, а моментам, калі браузер упершыне даведаецца пра наявнасць ресурсу. У гэтым кансалтаты раздзеляюць этыя два проблемы, паказваюць, дзе QUIC асалідна дапамагае, і рассказваюць пра засобы, якія прышвартаваюць процес аднаходжэння ресурсу раней: API-і ресурсаў у React 19, падготавка модуляў на адповіднасць намерам, сигналы 103 Early Hints, стрімінгаванне SSR і стратэгія частакавання, падходжыцьяя для мультіплексаванага транспорту.
Процес, які выглядае нормальна, але застаўся медленным
Уявіце команду, яка аналізуе панель статыстык працуючы через з’ўязак 4G. Кожны важлівы фрагмент вже завантажаны, структура сітевага транспорту выглядае аднародна, пры тым час до інтерактыўнасці ўпорна застаецца нижэй, чым планавалося. Кожны фрагмент швядка завершае завантажэнне як толькі пачынаецца, таму HTTP/3 явныя спрыяе цэму.
Якщо паглядзець адказальней, модель змінюецца. Завантажэння ведуцься швядка, але запыткі пачынаюцца пазнела. React трэба завантажыць, запусціць, працаваць і атрыбутаваць дадзеныя, пакуль не будзе досягнута межа „лазіваго“ завантажэння, і толькі тады браузер даведаецца, што патрэбны файл Dashboard.js. Пройшлі сотні мілісекунд, прычаму ўжо не патрабаваліся першыя байты гэтага фрагмента. Задзейсткі выкалываюцца ў прадзеўні сіті, у кроку, які большасць чартаў па эфектывнасці ніколі не выдзеляе окрема: адкрыцце рэсурсаў, на вядмежанні ад їх доставкі.
Доставка і адкрыцце — гэта два разныя прыбліжэння
Дапамагае дзеліцьце „завантажэння рычага“ на два пытань:
- Скорасць перадачы: калі ўжо зроблена запит, насколькі шыбка пакеты дадзеных перавозяцца з сервера да прыгледчыка?
- Час выканання пошуку: у які момент прыгледчык узагалі разумее, што яму патрэбны гэтыя пакеты дадзеных?
Веб-шрыфт, які апыляецца з файла стайл-шіт, чыніць разліку явной. Ланцюг выглядае так:
HTML → CSS → @font-face rule → font request
Незалежна адзінай колькісты супрацоўкі з’яноўкі, запчынка на атрыманне шрыфту не можа быць выданая, пакуль не будзе атрыманы HTML, не будзе запрашана і аналізавана CSS, і не будзе знайдзена правіла @font-face і не будзе адпаведна яго застосавана да атрыманага тексту. Гэта ўсёлякі затрымка адкрыцья. Тэг <link rel="preload"> не прышвартавае завантажэнне шрыфту нават на адзін мілісэкунду; ён проста дазволяе браузеру выдаты запчынку раней. Практычна всё, што напісана ў рэште гіду, являе сабою варыяцыю гэтай ідеі.
Калькі інструменты адпаведзяюць на калькі запытанні
Кожная з тэхналогій, прыгаданых нижэй, нацелена на разны этап процесу завантажэння:
preconnect: з калькіх джэрел браузер павінен запачаткуваць супрацоўку перш чым будзе выдана якая-небудзь запчынка?preload: калькі конкрэтнага файла браузер знайдзе запоўніць пазней сам?
preloadModule і modulepreload: які часткі ES-модуляў трэба завантажыць і скомпілаваць заздалегідь перед ўжыткам?preinit і preinitModule: які ресурсы не толькі трэба завантажыць, але і застосаваць чы ўжыць раней?prefetch: што, верагатна, падася для наступнай навігацыі, але з низкай прыоритэтам?Толькі апошні пункт стосуецца транспорту. Усё іншае — гэта аднароджэнне, таймінг чы планаванне, і гэтыя аспекты даюць чыстае адображэнне таго, дзе зазвычай ляжуць застойныя моменты.
Дзе HTTP/2 multiplexing не паспел
Па протакце HTTP/1.1 браузеры досягалі паралелізма пацярпаннем кальколькоў TCP-з’язоў на адного хоста, зазвычай не больш як шасця, пры чым кожны з’язок перадаваў адну рэсурс у час. HTTP/2 заменіў гэта адним з’язком, який перамешвае многа стрымоў:
HTTP/1.1 HTTP/2
──────────────── ────────────────────
TCP conn 1 → JS One connection
TCP conn 2 → CSS ├── Stream A: JS
TCP conn 3 → Font ├── Stream B: CSS
TCP conn 4 → Image ├── Stream C: Font
└── Stream D: Image
Гэта былі справжнія крокі ваўперад, але HTTP/2 яшчэ застаўся на базе TCP, а TCP гарантуе строга арранжаваную даставу адного потоку байтоў. Ён не ведае, што якія байты належаюць да стрыму JavaScript, а іншыя — да стрыму шрыфтаў. Якщо згубляецца адна пакета, TCP затрымвае ўсё, што следуе за яю, пакуль не прыйдзе перадача занова, нават калі згублены пакет быў часткай Стрыму C, а трохі іншыя стрымы не мелі з яным нічога спакойнага.
Гэта ёсць блакаванне TCP-галоўнай лініі (HOL). На стабільных сетях гэта рэдкая проблема; на нестабільных мобільных каналах гэта ўсёга главная прычына таго, чаму мультіплексаванне HTTP/2 так і не выпало ўсунуць усія яго обманы.
Які змяны ў QUIC пад HTTP/3
HTTP/3 заменяе TCP на QUIC, які працюе на UDP і сам адбывае шифраванне:
HTTP/2 HTTP/3
──────── ────────
HTTP/2 HTTP/3
↓ ↓
TCP QUIC
↓ ↓
TLS UDP
↓ ↓
IP IP
Незалежныя стрымы усуваюць блаканне на рэвэе транспорту
Паколькі QUIC керуе стрымамі внутрь протакола транспорту, кожны стрым відновляецца незалежна. Загублены пакет толькі зупіняе стрым, да якога ён належыць:
Stream A ──────────────────── ✓
Stream B ──────────────────── ✓
Stream C ──────── X ─ retry
Stream D ──────────────────── ✓
Стрэмы A, B і D продовжаюць працаваць, пакуль C чакае на свою пераправку. Значныя эфекты спостарэліся саме там, дзе TCP падаў найбольш. Адна з даследжэнняяў, опублікованых у ліпені 2025 года і адбыўшыхся ў шасці краінах, паказала, што на лініях з высокым рывкам пакетаў срэдні час досягнення першага байта зменіўся на 41,8%. Внутршні тэсты ў Wix паказалі, што налажванне з’ѐеднання вядзецца на 33% быстрэй, а показнік p75 LCP паспяліў на 20%. На стабільных шырокаспектрых лініях прырост у пораўнанні з HTTP/2 скарацваецца да прыблізна 5%. Гэта асиметрыя сама па сабе є інформатыўной: прыросты спостарэліся там, дзе раней паўтарылася проблема блакування через HOL. Спрыймайце гэтыя цифры як карточкі з тых даследжэнняяў, а не як гарантіі для вашага трафіку, і апыліцэйна пераканайцеся ў результатах для своіх корыстувачаў.
Менш раунд-трыпов да першага байта
За дапамою HTTP/2 пры TCP новая з’язок паказвае косты рукапрыкладу TCP і пасля — адзінокай даговорэнней пра TLS, што дае два раунд-трыпы перш чым пачнуцца перадачы данных аплікацыі. QUIC адзьеднвае абмянку TLS 1.3 з процэсам стварэння з’язку і завершае яе ў адны раунд-трып. Для вярнуўшыхся адвіцэйнаў 0-RTT рэанімацыя дазволяе заштыфрованым данным запитаў перавозіцца разам з пакетам апускання. На межыконтинентальным канэле дужою шырокасяговасцю 150 мс гэта дапамагае заўсёды заадно 150–300 мс на кожны новы з’язок. Хаця трэба памятаць, што данные 0-RTT могу быть перадрукаваны, таму серверы зазвычай прыменяюць іх толькі для ідэмпатных запитаў, такіх як загрузка статычных ресурсаў.
З’язкі, якія вытрымаюць змену сетевага пераключальніка
TCP выклікае з’яўленне з’язку за дапамою сукупнасці IP-адрэс і портаваў выходнага та праблемнага канцэрта. Калі телефон пераходзіць з сеті Wi-Fi на мобайльную, яго адрэса змінюецца і з’язак TCP перыходзіць у стан завершэння. У свою чаргу QUIC викорыстоўвае незрозумелы ідэнтыфікатор з’язку, таму сесія можа перайсці на новы шлях. Фрагмент маршруту, які запрашаецца заздалегідь, не павінен пачынаць занова чераз тое, што пасажирскі поўзд выйшаў з станцыі.
Чы робіць HTTP/3 запрашэння заздалегідь непатрэбным?
Нята, і розумеўце гэтага ёсць сутністю всіяго тэмы. HTTP/3 оптымаўзуе спосаб перадачі ресурсаў; запрашэння заздалегідь оптымаўзуе спосаб ўсё ранейшага запрашэння іх. Гэтыя два методы дзейнуюць у разных точках ланцуга:
Browser
│
│ ← "I don't know I need this yet"
↓
Resource discovery ← preload operates here
│
↓
Request
│
↓
QUIC transport ← HTTP/3 operates here
│
↓
Server
Транспортны ўгода не можа запрашваць тое, чаго ніхто ўсё яшчэ не прасіў. На самай працы, быстрейшы транспорт робіць запазнелае адкрыцце ўсё болей выразным. Падазроўваючы, што час перадачы фрагмента зменіўся з 300 мс на 80 мс, запазнеласць адкрыцця у 400 мс, якая раней часткова была схованая ў загальны час, тепер станавіць большую частку з яго. «Вузкія месцы» проста перасунуліся, але не зніклі.
Чаму React сховвае залежнасці ад браузера
Звычныя HTML-ресурсы, такія як кантэнты <img> і шаблоны стылю <link>, знаходзяцца рана, таму што парсер браузера (і яго сканер прадзвычайнага запрашэння) бачыць іх пад час чытання документа. React, які адрасаваецца на кліянтскай стороне, стварае значна дыяўольшую ланцюгаватасць, пры якой дзеяныя залежнасці станавяцца виднымі:
HTML → main.js → React executes → render → lazy() → discover Dashboard.js → download → render
За дапамою React.lazy() імпорт выкананы пасля запуску JavaScript. Браузер не можа з’ясаваць, што існуе Dashboard.js, пакуль галоўны бандл не будзе завантажаны, апрацаваны, скомпільваны і запусканы, а React не выкарасіць достатню частку коду, каб дася да лендж-компонента. Пры першым запуске з повольнага тэлефона гэта можа значыць калькі секунд, пры якіх не пачынаецца запит на частку коду.
const Dashboard = lazy(() => import("./Dashboard"));
// The browser has no idea Dashboard.js exists
// until this renders. And it only renders after
// React has fully bootstrapped.
Suspense павышае качэнне, а не прыглед
Частая хыбна думка — што абгорткаванне компонента ў Suspense рашае проблему. Це не змінюе момент, калі запускаецца запит на частку коду:
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
Suspense забезпечвае коордынацію: паколькі ленвы компонент яшчэ не адгрупаваны, React паказвае запасны варіант заместо таго, каб заблокаваць усю структуру. Гэта мае значэнне для спрыткі якасці, але гэта не механізм для прагнозавання ресурсаў. Запрос все равно пачынаецца у той жа пазні момент. HTTP/3 будзе эфектываўна перадаваць даны пасля гэтага, пры тым як у ёго немае вліяння на тое, сколькі часу падышло, каб яны дасталіся.
API-явы для ресурсаў у React 19 і тое, калі саме яны выконваюць свае задачы
У React 19 ў react-dom прыносится сяродзе праблем, якія дазволяюць компонентам задаваць падказкі пра ресурсы ў планавальнік браузера самэ тады, калі патрабаванне стае вядомым падчас адрасавання. Яны — не проста тонкія обгорткі навакол HTML-тагоў: React адмахвае іх, а падчас серверавага адрасавання можа выдаць іх у частку дакумента, каб браузер пазнаў іх раней.
preconnect: падготовка дааспраўкі да аднаго рэспансу
Іспользуйце preconnect, калі запытка між рознымі рэспансамі неабяжна будзе адправлена ў скоры час. Ён заздалегідь пачынае рашучанне DNS, стварэнне з’яўлення і процес TLS handshake.
import { preconnect } from "react-dom";
// Call this when you know a cross-origin
// request is coming - not just "might be coming."
preconnect("https://cdn.example.com");
Заставіце яго для тых рэспансоў, з якімі вы неабяжна будете весты саўязь. Кожная падготавленая дааспраўка выклікае працу як у кліенте, так і на серверы, а невыкарыстаная проста збрасваецца.
preload: раннее завантажэнне конкрэтнага файла
preload прыказвае браузеру пачаць завантажэнне вядомага ресурсу без яго выконання чы аплікацыі. Шрыфты ўсё чащэ ёсць класычным прыкладам, таму што інакш яны залишаюцца схованымі за процэсам парсінгу CSS:
import { preload } from "react-dom";
// Font hidden behind CSS - the browser won't
// find this until it processes @font-face.
// Preload surfaces it earlier.
preload("/fonts/inter.woff2", {
as: "font",
crossOrigin: "anonymous",
});
Зверніце ўвагу на опцыю crossOrigin: "anonymous". Шрыфты завжды запрашваюцца у режыме CORS, таму preload шрыфта без яе стварае запытку, якая не падходзіць да рэальнай, і браузер у падсумку завантажвае файл два разы.
preloadModule: запрашоўвае і скомпілюе ES-модуль
preloadModule выражае тую ж мету для ES-модуляў, але ідзе даўжэй: модуль завантажаецца, парсуецца і скомпілюецца, пасля чаго захаваецца ў його картоце модуляў, так што яго можна будзе выкарыстаць як толькі import() папрасі пра ўжыцеўленне.
import { preloadModule } from "react-dom";
// Use this for lazy route chunks you know
// are likely to be needed soon.
preloadModule("/assets/Dashboard-abc123.js");
Гэта ідальна аптавалюцыя для частак маршрутаў, якія, верагодна, будуць патрэбныя за некалькі хвілін.
preinit і preinitModule: запрашоўвае і прыменяе
preinit і preinitModule — гэта болей сильныя варыянты. Яны запрашоўваюць ресурс і таксама прыменяють яго: шаблон стылю дадаецца і застосоўваецца, а скрыпт выкананы як толькі ён прыбывае.
import { preinit } from "react-dom";
// You don't just want this downloaded -
// you want it applied before render.
preinit("/styles/app.css", { as: "style" });
Для CSS найважлівейшым ёст размац межа двух семейств стайл-шіт. Запрацоўваны шаблон стайла завантажваецца, але не застаўляецца. Якщо такі шаблон стайла неабходны ўжо до першага атрыбутавання элемента, трэба запускать завантажэнне раней, але відрасункаванне все равно чакае, пакуль ён фактычна не будзе дадзены. preinit пакрывае оба этапы. Для скрыптаў дзейсніцца працоўнае правіло: можна выкарыстоўваць толькі preinit-код, які можна без апасцярожнасці запускаць негаю.
Актывацыя preloadModule на адпаведнасць намеру пользователя
Выкліканне preloadModule для кожнага маршруту пад час запуску вядзе да марнавання пропускной здатнасі. Найлепшы момент — калі намер пользователя стае відчутным, што зазвычай означае прыходжанне курсора або фокусу клавіатуры на поўязку навігацыі безпосереднья ў празрыўку да кліка.
Наведэны выкладком элемент паўтарае гэта з’яеднанне. Ён адрасавае звычайныя акраны, таму лінк застаецца працэспрасным без JavaScript; вызывае preloadModule як пад onMouseEnter, так і пад onFocus, ўбліжчаючы корыстувачоў клавіатуры, і перадае фактычную навігацыю на navigate з React Router:
import { preloadModule } from "react-dom";
import { useNavigate } from "react-router-dom";
function NavLink({ to, chunkPath, children }) {
const navigate = useNavigate();
return (
<a
href={to}
onMouseEnter={() => preloadModule(chunkPath)}
onFocus={() => preloadModule(chunkPath)}
onClick={(e) => {
e.preventDefault();
navigate(to);
}}
>
{children}
</a>
);
}
// Usage
<NavLink to="/dashboard" chunkPath="/assets/Dashboard-abc123.js">
Dashboard
</NavLink>
Час між наведэнням і клікам зазвычай становіць ад 100 да 400 мс. Праз HTTP/3 средняя павелічына дадзеных часто можа буць обробленая за гэты перыяд: пры швале 10 Мбіт/с 150 КБ заўсёды займае або каля 120 мс. Калі ўжо вырашыцца клікнуць, модуль вэсна ўжо скомпіляваны ў карце модулей, проблема з запоўненням дадзеных відразу ж рашаецца, і альтэрнатыва Suspense ніколі не з’яўляецца.
Тое, чаго дасягае працэўнік onMouseEnter, — гэта тое, чаго не можа зрабіць жанчынскі протакол: ён ператварае сигнал намеру на інформацыю пра рэсурс яшчэ до таго, як будзе запрасжана навігацыя. HTTP/3 пасля чаго эфектыўна керуе перадачай. Кожны слой выканае свою задачу.
Две практычныя прыметкі. Першая — chunkPath павінен быть імёнам файлу з хэшам, якое насправды стварыў ваш бандлер, таму ў рэальным проекте яго трэба браць з маніфесту будовы, а не вводзіць вручную. Другая — прыстроі на базе сенсораў не маюць функцыі hover, таму якщо для вас важна мобайльная навігацыя, разважыце варыянты на базе onTouchStart або трыгераў, заснованых на відобразэнні экрана.
Працэўнае запрашанне на роўні бандлера
Для масовага, з нізкай прыоритэтнасцю запрашання пад час вільнага часу, webpack падтрымляе спецыяльны коментар унутрь дынамічнага імпорту:
const Dashboard = lazy(
() => import(/* webpackPrefetch: true */ "./Dashboard")
);
Зважайце, што webpackPrefetch стварае таг <link rel="prefetch">, які браузер спрыяе як задачу низкага прыорітэта, якая будзе выканана праз час прыпоўнення, для можлівай будучай навігацыі. эта не той сигнал, як preload чыстаў modulepreload з высокім прыорітэтом, якія викорыстоўваюцца для ресурсаў, якія патрэбны зараз.
Vite выкарыстоўвае болей автаматычны падход і сам стварае тагі modulepreload. У webpack для гэтага трэбуецца спецыяльны коментар чыста плагін. Натыўная HTML-форма такога сигналу выглядае так:
<!-- Vite generates these for lazy chunks automatically -->
<link rel="modulepreload" href="/assets/Dashboard-abc123.js">
<link rel="modulepreload" href="/assets/vendor-react-def456.js">
Строга кажучы, HTML, які генеруе Vite, містить тэгі modulepreload для вхіднага чанка та яго статычных імпортаў. Для дынамічна імпортаваных чанкаў функцыя Vite на час выконання import() дадае тэгі preload для яхных залежнасцей, так што чанк та яго імпорты завантажуюцца паралельна, а не па порядку. Для точнага разумення прыемаў пераканайцеся ў дасяглівных матэрыялах вашай версіі Vite.
Паўстаноўна да звычайнага rel="preload", modulepreload дазволяе браузеру параскладваць та скомпіляваць модуль як толькі ён прыбывае, замест таго каб чакаць момент выканання. Праз HTTP/3 калькі такіх паведамленняў пераходзяць па незалежных потоках QUIC, таму втрата пакета ў чанку з контентам не спрачынае затрымкі чанка з панеллю керування.
Ад Server Push да 103 Early Hints
HTTP/2 прагнаў рашыць проблему адкрыціі ресурсаў на стороне сервера за дапамогою методу Server Push: сервер надсілалі ресурсы, якія браузер ўсё ўжо не запрашаў. Мета перанесення ціяго процэсу на ранейшы этап была правильная, але ў ягоя рэалізацыі былі проблемы. У сервера не было надзеянага спосабу выявіць, чы ў браузера вже ёсць кэшаваны ресурс, таму часта надсілаліся дублікаты, витрачаўся пропускна спакет, а таксама выступала борба за працэсную магчымасць з ресурсамі, якія сам браузер счыляў болей важлівымі. У канцэ на Chrome прыпыніў падтрымку методу Server Push.
Модэль, які заменіў яго, болей розумна распадзяляе адпаведальнасць: сервер пасылает информацыю, а браузер вяршыць выбор, калі і які ресурсы запрашаць.
103 Early Hints дапамагае ўпрабавіць гэта на практыцы. Калі сервер яшчэ складае главны адказ, ён надае тымчасовы статус 103 з заголовкамі Link. Браузер можа негайна пачаць загрузку гэтых ресурсоў, і калі прыйдзе фінальны адказ 200 OK з HTML, дзеяныя з іх можа ўжо быць загружаныя.
Browser Server
│ │
│──── GET / ─────────────→ │
│ │ (generating HTML...)
│ ←─── 103 Early Hints ─── │
│ Link: </assets/main.js>; rel=modulepreload
│ Link: </assets/vendor.js>; rel=modulepreload
│ │
│ (fetching chunks now...) │ (still generating...)
│ │
│ ←─── 200 OK + HTML ───── │
│ (chunks already downloading or done)
На момент напісання NGINX прадае вбудованую падтрымку Early Hints з выданнем 1.29.0 у чэрвені 2025 года, а Cloudflare выклікае яе як настройку у сваёй панелі керування. У Node.js об’ект адказа прадае метод writeEarlyHints(), які можна выкліканы ў спецыяльным серверы чы роутеры прычыну надсылання асалівскага адказа:
// In a custom server or middleware
res.writeEarlyHints({
link: [
"</assets/main.js>; rel=modulepreload; as=script",
"</assets/vendor.js>; rel=modulepreload; as=script",
"</assets/Dashboard.js>; rel=modulepreload; as=script",
],
});
// Then proceed with normal response
res.status(200).send(html);
У гэтым фрагменте для выдання заканчальнай адпаведзі ўжоўцяеся метод res.status().send() у стылі Express. У звычным сервере Node.js http у такіх случаях викорыстоўваюцца res.writeHead() і res.end(). Функцыя Early Hints ўжоўчываецца толькі тады, калі ёсць рэальны час обробкі на сервере, напрыклад падчас запытоў да базы данных або генеравання кантэнта, калі інакш браузер застаўся бы без дзеяння.
Звёрстаныя даны з corewebvitals.io, якіе атрыманы за дапамогай метражу часу з Chrome DevTools, паказалі, што размешчэнне критычнага файла CSS у рошце Early Hints прыводзіць да таго, што элемент LCP паказваецца прыблізна на 35% раней, чым у разе звычнага падготавлення кантэнта ў самым HTML. Для аплікацый на React такая перадача дапамагае завантажваць галоўны частаку кантэнта, пакуль сервер яшчэ працюе, а не пасля таго, як HTML вярнуўся.
Спалучэнне стрімінгавага SSR, Early Hints і HTTP/3
Функцыя сервернай рэндараціі з адпраўкай даных у формате стріму ў React даўае яшчэ адну можлівасць. Уместо таго, каб сервер чакаў з рэспансам да тым часу, пакалі будзе готава ўся сторанка, ён адправляе HTML пасьпаральна:
HTML shell → Suspense fallback → more HTML → resolved content → hydration
Пад час рэндараціі сервер ведае, якія элементы Suspense будуць адрэндараваны і на якія часткі ўжо створанага контэнту яны залежнае. Гэтую інфармацыю можна перадаць браузеру через Early Hints яшчэ до таго, калі пачнёцца стрім HTML. Спрасаваная часовая шкала:
0ms ── Browser sends request
── Server starts rendering
1ms ── Server knows Dashboard boundary will render
── Server sends 103 Early Hints: Dashboard.js
── Browser starts fetching Dashboard.js
50ms ── Server streams HTML shell
── Browser starts parsing
120ms── Server streams Dashboard content
── Dashboard.js already downloaded
── Hydration starts immediately
Пораўняйце тую ж аплікацыю без Early Hints, дзе процес адкрыцья кантэнту чакае на кліента:
0ms ── Browser sends request
50ms ── Server streams HTML shell
── Browser starts parsing
── Browser discovers <script> tags
── main.js starts downloading
180ms── React executes
── Hits Dashboard lazy boundary
── Dashboard.js request starts (now)
300ms── Dashboard.js downloads
── Hydration starts
HTTP/3 скарацьва кожны фрагмент у обох таймлайнах. Тое, што зменяюць Early Hints, — это момент пачатку запиту да Dashboard, што являецца іншым і частаючымся спосабам заўтрошчання ресурсаў. Якщо процэс атрыбутавання фреймворку вже практыкуецца за дапамою іншых прымітіў, стаття на тэму прымітіў SSR у React 19.2, такіх як Activity і частковая прадзеявка дэталізавае, як ствараюцца межы стрімування.
Перэсцяжчанне падходу да розміру частак для мультіплексавання дадзеных
Давняя рэкамендацыя па мінімізаванні колькасці HTTP-запыткаў выйшла з ліміту шасця канектацый у HTTP/1.1 і з таго, што мультіплексаванне ў HTTP/2 ёсць толькі часткова паралельным у межах аднаго TCP-стрэму. Заводзячы незалежныя QUIC-стрэмы, колькасць запыткаў перестае быть галоўным факторам, якім была раней, таму можна агрэсыўней дзеліць кантэнт, не платячы той самы штраф за кожны запыт.
Розумнае стандартнае дзеленне выглядае так:
- React і ReactDOM: адзін спецыяльны, стабільны чанк вэндара. Ён рэдка зменяецца, таму можа мець дужа дзейнае час кэшавання.
- Бібліятэкі Router: свой сабе чанк, з таго жа паказу стабільнасці.
- Масівныя бібліятэкі трохіх сторон, такія як інструменты для стварэння чартоў або редактары: по аднаму чанку на кожную бібліятэку, тады апдэйт аднай не зробіць недзейнаю іншыя.
React.lazy(), тады навігацыя завантажае толькі тое, што ёй трэба.Рэзультатам яе вжыцца тачна кешаванне. Рэдагаванне Dashboard.tsx должна знішчыць кеш для таго фрагмента маршрута, тады как бандл вэндора застаецца ў кешы, і гэта можліва толькі за дапамогою дзеяржовага раздзелення. Через HTTP/3 8-15 фрагментаў завантажаюцца па незалежных стрымоў без блакання HOL між ямі.
Vite справляецца з большайшаю часткаю такіх задач без налагоджэння. У Webpack параметр splitChunks.cacheGroups выражае тую ж самую палітрыку. Наведзена нижэй налагоджэння стварае чак для бібліятак React, чак для рутэра і чак толькі для асінхронных дыяграм; значэння priority вялікіяе, якая група будзе выбрана, калі модуль падходзіць пад аднасць критэрыяў, а параметр chunks: "async" не дазволяе коду для дыяграм быць завантажаным пачаткова:
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
reactVendor: {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: "vendor-react",
chunks: "all",
priority: 40,
},
routerVendor: {
test: /[\\/]node_modules[\\/](react-router|react-router-dom)[\\/]/,
name: "vendor-router",
chunks: "all",
priority: 30,
},
chartsVendor: {
test: /[\\/]node_modules[\\/](recharts|d3)[\\/]/,
name: "vendor-charts",
chunks: "async",
priority: 20,
},
},
},
},
};
Є адзін недагод. Чырвоныя часткі спрычынаюць дадзейнавальны накладкі па кожным файлам пад час парсавання і компілявання ў браузеры. Кроме таго, не кожны візітар выкарыстоўвае HTTP/3: корпоратывныя фаервалы, якія блакуе UDP на порту 443, є распашчастымі, і такія корыстувальнікі пераходзяць на HTTP/2, дзе частка накладак, зв’язаных з колькасцю запыткаў, вяртаецца. Перад тым, як дзеліць контэнт на ўсё мельчэ, трэба здзійсніць меры. Зазвычай правильным розмěрам дзелення є па маршруту і па важкім бібліятэках; па складовых — зазвычай няўмогучна. Якщо ваша платформа — Next.js, матэрыял на контроле частак у Turbopack охопляе адпаведныя настройкі там.
Чаму прадзейнаванне всього контэнту все равно мае негатыўны эфект
Мультіплексаванне дозволяе колькам стрымам выкарыстоўваць адну з’ёডнанне, але ён не дае кожнаму з іх неабмежанай пропускной спроможнасці і не робіць іх аднакова важлівымі. Двадцать падказкаў modulepreload яшчэ выкарыстоўваюць адну трубку; конкурэнція проста распадаецца межы большым колькам стрымаў.
Таму корыстным пытаннем не ёсьць „што можна запрацаваць заздалегідь?“, а „які важлівы ресурс браузер зазначыць толькі пасляпэўна?“. Як вачак, рассмотріце:
- Критычны шрыфт:
preload. - Критычны файл стылю:
preload, абоpreinit, калі яго трэба застосаваць раней, чым будзе атрыбут paint. - Критычны модуль JavaScript:
preloadModule. - Важлівыя ресурсы з іншага кантэксту або API:
preconnect.
preload з параметрам fetchpriority="high".preloadModule, які запускаецца па жаданню пользователя.prefetch з низкім прыорітэтом.Кожны пункт трэба спрыятаць як „варыянт“, а не як правілу. Не існуе універсальнага списку прадзеўжання, толькі ресурсы, якія ўодночас важлівы і адкрываюцца пазней у вашай конкрэтной аплікацыі.
Той жа правілася дапраўляецца да fetchpriority. Браузеры вялікую частку ресурсаў прадаюць за дапамою складных гіурыстык, і пазначэнне всього як high еквівалярна таму, калі нічога не пазначаецца. Зменяйце стандартны значэння толькі тады, калі памеры пакажуць, што браузер робіць помылку.
Пяць запитанняў для адзінаковага разгляду стратэгіі завантажэння
Працюючы над аудытам таго, як прыложэнне на React завантажае своія ресурсы, разгляньце следуючыя запитання па порядку:
- У які момент адкрываецца гэты ресурс? Якшы адказ — "пасля запуску JavaScript" або "пасля адрасавання React", значыць, верагатываецца, што можна яго адкрыць раней.
- Калі ён патрэбны корыстніку? Часамі ўсё можа быць важлівым, але не патрэбным негаўна. Гэта разлікаванне вялічыць межу між
preload(зараз) іprefetch(у вільны час).
preconnect, пасля preload або preloadModule, пасля preinit, пасля 103 ранніх падказакоў, пасля стрімавага SSR з падказкамі, якія сервер атрымлеў на аднойчынку з таго, што ён атрымлеў.Як складаюцца гэтыя слоі
Якща розглядаць увесь процес, запуск сучаснага застосунку на базе React выкалікаеся з шасцяў слоёў, кожны з якіх мае свой дыячы спектар:
Application intent (React knows which routes and components are needed)
↓
Resource APIs (preconnect / preload / preloadModule / preinit)
↓
Server-side surfacing (103 Early Hints / streaming SSR)
↓
Browser resource scheduler (priority, cache, bandwidth estimation)
↓
HTTP/3 / QUIC transport (independent streams, 0-RTT, connection migration)
↓
Network
Старэйшы падход заклікаў перадаваць рэсурсы на кліент як магчыма адразу: Server Push, падготавка всіх элементаў заздалегідь і аб’еднанне пакетаў для зменшэння колькасці запытань. Сучасны падход — перадаваць браузеру болей разнарадныя сігналы на кожным слою і даваць можлівасць яму самаму выконваць календараўзні запытання. HTTP/3 забезпечвае транспорт, які можа эфектыўна дзейсці на адповедныя рашэнні. Ранніе падказкі перадаюць інформацыю з сервера браузеру ўсё раней, чым існуе HTML. API-і для керавання рэсурсамі у React дазваляюць застосунку выказваць свае намеры ў момент, калі компонент готавы.
Няхайшы слой не можа заменіць іншы. HTTP/3 не можа запрашваць тое, што ўжо не было адкрыта. Ранніе падказкі бяруць сэнс толькі тады, калі сервер знае, якія маршруты ўжо обробляюцца. А preloadModule не можа вывяліць ситуацыю з монолітным пакетам размерам 2 МБ, якому для компіляцыі на тэле середньага класу трэба 800 мс.
Ключовыя выводы
Найбольшы эфект HTTP/3 на прадзейнаванне ў прамежчыку часу не ў тым, што перадачы сталі быстрей; аднойчы быстрыя перадачы зробілі пазнейшае адкрыцця інфармацыі столькі ж дорожчым. Калі час перадачы з 400 мс зменшыўся да 80 мс, затрымка адкрыцця, якая раней была ўсередзіні яго, стала галоўным факторам вартасці, і стратэгія, налаштаваная пад стары бутлнэк, тепер не дапамагае.
- Прыявляйце рэсурсы заздалегідь, таму што інакша прыгледач знайдзе іх запоўніцу пазней, а не толькі таму, што гэта важна. Важныя рэсурсы, якія знаходзяцца ў час, не патрабуюць жадных паведамленняў, а неважныя, якія знаходзяцца пазней, не заслуговуюць на іх.
Suspenseпавышае якасць таго, што бачыць корыстнік у час чакання; ён не прыводзіць да шырэйшага завантажэння частак.- Выберыце сама слабейшая эфектыўная падказка:
preconnectдля выхідных адресаў,preloadдля файлоў,preloadModuleдля модуляў,preinit, калі трэба ўжыць альбо запрацаваць які-небудзь элемент. - Актывацыя прыявлення частак маршрута на адпаведны сігналы намеру, такія як hover і focus, а таксама викорыстоўванне Early Hints і стрімінгавага SSR для паказу таго, што сервер вядомы ўжо.
- Раздзеліце практыку на маршруты і важкія бібліятэкі, памятайце пра альтернатыўу HTTP/2, і здзійсніце мэтрыкацыю пры пераходзе да болей дакладных настроек.
HTTP/3 не зробіў поперадню загрузку застарелай. Ён зробіў болей ясным, для чаго вона патрэбна.
Спадні матерыялы
- React 19.2 SSR Primitives: Activity, cacheSignal і PPR — пояснення — Дазнаецеся, як новы компонент Activity, cacheSignal і функція частковай прадрукавання ў React 19.2 даюць разработчыкам прымусовы контроль над выкарыстоўванням сервера для прадрукавання.
- Кераванне Docling Pipelines через HTTP: ад наладкі проекта да індексаваных частак — Паследовы аналіз REST API Docling Pipelines: запуск сервера, выявленне оператораў, перакананне і запуск процесу прабіркі дадзеных, а таксама чытанне метрык пра його выкарыстоўвання.