Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «SSR і lazy loading компонентів». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)До React 18 `React.lazy()` фактично блокував рендеринг на сервері, бо класичний SSR (`renderToString`) не вміє чекати проміси; у React 18+ зі Streaming SSR сервер може "призупинити" рендер лінивого компонента, відправити fallback з `<Suspense>` і дозавантажити готовий контент потоком. **Ключове:** `React.lazy` не можна використовувати без `<Suspense>`, інакше SSR "зависне".Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Нагадування: що таке SSR і lazy loading | Механізм | Що робить | |---|---| | **SSR (Server-Side Rendering)** | Генерує HTML на сервері, щоб сторінка відображалася одразу (без очікування JS). | | **Lazy Loading компонентів** | Завантажує компонент (або його код) **лише за потреби** - щоб не тягнути весь бандл одразу. | --- ## Як вони взаємодіють Коли ти використовуєш `React.lazy()` або `dynamic import()` у SSR-застосунку: 1. Сервер починає **рендерити React-компоненти**. 2. Якщо зустрічає **лінивий компонент**, який ще не завантажено (код спліттиться в окремий JS-чанк), сервер не може відрендерити його прямо зараз. 3. React: - або **зупиняє рендер** (у класичному SSR); - або **показує fallback** з `<Suspense>` і продовжує стрім (у Streaming SSR); - або **дозавантажує модуль синхронно**, якщо він уже в пам'яті (у dev-режимі). --- ## 1. У класичному SSR (до React 18) `React.lazy()` фактично **блокує рендеринг**, якщо його модуль ще не завантажено. Наприклад: ```javascript const LazyComp = React.lazy(() => import('./BigChart')); function Page() { return ( <div> <Header /> <LazyComp /> {/* SSR тут "застрягне" */} </div> ); } ``` На сервері `import('./BigChart')` - це **Promise**, але класичний SSR (`renderToString`) не вміє чекати проміси. Результат: - Сервер видасть помилку: *"A component suspended while rendering, but no fallback UI was specified."* - Або просто не зможе завершити SSR. Тому до React 18 **lazy-компоненти не працювали на сервері** - їх доводилося вантажити синхронно або відключати SSR для них (`ssr: false` у Next.js). --- ## 2. У React 18 і Streaming SSR React 18 повністю змінив правила гри. Тепер SSR **уміє працювати з Suspense і lazy-компонентами** асинхронно. ### Що відбувається: 1. Сервер починає рендерити HTML. 2. Коли React зустрічає лінивий компонент, він **"призупиняє" рендер** цієї частини (suspend). 3. Надсилає в потік HTML для fallback-у (наприклад, `<Skeleton />`). 4. Коли модуль дозавантажиться, React **продовжує стримінг** і вставляє готовий контент пізніше. Приклад: ```javascript import { Suspense, lazy } from 'react'; const LazyComp = lazy(() => import('./BigChart')); export default function Page() { return ( <div> <Header /> <Suspense fallback={<p>Loading chart...</p>}> <LazyComp /> </Suspense> </div> ); } ``` У Streaming SSR: - `<Header />` і `<p>Loading chart...</p>` відрендеряться і **одразу відправляться клієнту**; - коли `BigChart` завантажиться, сервер **дошле** решту HTML. Це робить SSR **асинхронним, плавним і "лінивим" по суті**. --- ## 3. У Next.js У Next.js (починаючи з версії 13, App Router) це реалізовано з коробки: ```javascript import dynamic from 'next/dynamic'; const LazyChart = dynamic(() => import('./Chart'), { ssr: true, // або false, якщо не хочеш рендерити на сервері loading: () => <p>Loading...</p>, }); export default function Page() { return ( <> <Header /> <LazyChart /> </> ); } ``` - `ssr: true` → компонент буде відрендерений на сервері (через Suspense). - `ssr: false` → компонент повністю виключається з SSR і рендериться лише на клієнті. Це дає гнучкість: можна вибрати - "ліниво, але з SSR" або "ліниво, але лише в браузері". --- ## Переваги такої взаємодії | Перевага | Опис | |---|---| | Швидший перший рендер | SSR віддає HTML одразу, навіть якщо частина компонентів вантажиться ліниво | | Менший JS-бандл | Lazy loading знижує розмір initial bundle | | Плавне завантаження контенту | Через `Suspense` SSR віддає fallback, потім замінює на готовий компонент | | Підтримка асинхронних даних | Lazy-компоненти можуть дозавантажувати дані з API без блокування всього SSR | --- ## Потенційні складнощі | Проблема | Опис | |---|---| | Не можна використовувати `React.lazy` без `Suspense` | Інакше SSR "зависне" | | Fallback може залишатися довше очікуваного | Якщо імпорт важкий або мережа повільна | | Старий `renderToString` не підтримує Suspense | Потрібен `renderToPipeableStream()` | | Кешування чанків | Потрібно налаштувати CDN, щоб JS-чанки lazy-компонентів вантажилися коректно | --- ## Приклад повного SSR + lazy loading + Suspense ```javascript import { Suspense, lazy } from 'react'; import { renderToPipeableStream } from 'react-dom/server'; const LazySection = lazy(() => import('./Section')); function App() { return ( <div> <Header /> <Suspense fallback={<LoadingSection />}> <LazySection /> </Suspense> </div> ); } // Сервер app.get('/', (req, res) => { const stream = renderToPipeableStream(<App />, { onShellReady() { res.setHeader('Content-Type', 'text/html'); stream.pipe(res); }, }); }); ``` Клієнт отримує шапку і `LoadingSection` одразу, а `Section` додається в DOM пізніше, коли готовий. --- ## Висновок **SSR і lazy loading тепер відмінно поєднуються** - завдяки React 18 і Streaming SSR: | Критерій | Старий SSR | Streaming SSR | |---|---|---| | Lazy-компоненти працюють? | Ні | Так | | Suspense підтримується? | Ні | Так | | Можна віддавати частинами? | Ні | Так | | UX без "білого екрана"? | Не завжди | Завжди |Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.