Что такое lazy и Suspense в React?
React.lazy и Suspense
React.lazy и Suspense — два взаимосвязанных механизма, позволяющих реализовать ленивую загрузку компонентов и управлять состоянием ожидания асинхронных данных.
React.lazy
React.lazy принимает функцию, которая возвращает динамический import(). Это позволяет браузеру загрузить JS-бандл компонента только тогда, когда он действительно нужен — не при старте приложения, а при первом рендере.
const HeavyChart = React.lazy(() => import('./HeavyChart'));
Под капотом React.lazy создаёт специальный объект-обёртку. При первом рендере React видит, что загрузка ещё не завершена, и «приостанавливает» рендер, бросая специальный Promise — именно его перехватывает ближайший <Suspense>.
Suspense
<Suspense fallback={...}> — это граница ожидания. Пока дочерние компоненты «приостановлены», Suspense показывает fallback. Как только загрузка завершается, fallback убирается и отрисовывается реальный контент.
<Suspense fallback={<Spinner />}>
<HeavyChart data={data} />
</Suspense>
Зачем это нужно
- Уменьшение размера начального бандла — пользователь быстрее получает первый экран (улучшается FCP и TTI).
- Разделение по маршрутам — каждая страница загружается только при переходе на неё.
- Приоритизация загрузки — критичный UI грузится сразу, второстепенный — по требованию.
Использование с React Router
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Settings = React.lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
Suspense для данных (React 18+)
Начиная с React 18, Suspense работает не только с lazy, но и с асинхронными источниками данных через библиотеки (React Query, Relay, SWR) или нативный use() hook:
// use() приостанавливает рендер, если Promise ещё не resolved
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
Важные ограничения
React.lazyработает только с default export.- Suspense не перехватывает ошибки загрузки — для этого нужен отдельный Error Boundary.
- На сервере (SSR без специальной поддержки)
React.lazyне работает — для Next.js используютnext/dynamic.
Вложенные границы Suspense
Можно вкладывать несколько Suspense для разной гранулярности:
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart />
</Suspense>
</Suspense>
Внутренняя граница перехватывает приостановку HeavyChart, не блокируя рендер остальной страницы.
Что хочет услышать интервьюер
Понимание механизма code splitting и зачем он нужен для производительности
Объяснение связки lazy + Suspense: lazy бросает Promise, Suspense его перехватывает и показывает fallback
Знание ограничений: только default export, нужен Error Boundary для обработки ошибок
Практический пример — разбивка по маршрутам с React Router
Понимание расширенных возможностей Suspense в React 18 (данные, use())
Пример: Базовое использование React.lazy + Suspense
import React, { Suspense } from 'react';
// Компонент загрузится только при первом рендере
const HeavyChart = React.lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<Suspense fallback={<div>Загружаем график...</div>}>
<HeavyChart />
</Suspense>
);
}
Пример: Code splitting по маршрутам + Error Boundary
import React, { Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import { ErrorBoundary } from 'react-error-boundary';
const Home = React.lazy(() => import('./pages/Home'));
const Profile = React.lazy(() => import('./pages/Profile'));
const Settings = React.lazy(() => import('./pages/Settings'));
function App() {
return (
// ErrorBoundary перехватит ошибки загрузки чанков
<ErrorBoundary fallback={<div>Ошибка загрузки страницы</div>}>
<Suspense fallback={<div>Загрузка...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</ErrorBoundary>
);
}
Пример: Suspense для данных через хук use() (React 18+)
import { use, Suspense } from 'react';
// Функция создаёт Promise с данными пользователя
async function fetchUser(id: string) {
const res = await fetch(`/api/users/${id}`);
return res.json() as Promise<{ name: string; email: string }>;
}
// Компонент приостанавливает рендер, пока Promise не resolved
function UserCard({ userPromise }: { userPromise: Promise<{ name: string; email: string }> }) {
const user = use(userPromise); // приостанавливает рендер
return <div>{user.name} — {user.email}</div>;
}
function Page() {
const userPromise = fetchUser('42');
return (
<Suspense fallback={<div>Загружаем данные пользователя...</div>}>
<UserCard userPromise={userPromise} />
</Suspense>
);
}
Типичные ошибки
Путают lazy с обычным динамическим import() — не понимают, что lazy создаёт специальный React-объект
Забывают обернуть lazy-компонент в Suspense — приложение падает с ошибкой
Не добавляют Error Boundary рядом с Suspense, и сетевые ошибки загрузки никак не обрабатываются
Используют named export вместо default export с React.lazy
Думают, что Suspense в React 18 для данных работает «из коробки» без поддерживающей библиотеки или хука use()


