Code Splitting и entry points
Code splitting снижает размер стартового бандла и ускоряет первый экран. Разделение по маршрутам — базовое требование. В Next.js App Router действует принцип "Server by default": клиентский JS добавляется только при явной необходимости.
Разделение кода
MUST
- Разделение по маршрутам — каждый маршрут выделяется в отдельный асинхронный чанк.
- Entry point содержит только код для инициализации и отрисовки первого экрана — весь остальной код загружается асинхронно.
- Первый экран не блокируется ожиданием асинхронных чанков.
SHOULD
- Крупные или редко используемые зависимости пересматривать на необходимость, заменять лёгкими аналогами или выносить в динамические импорты.
FORBIDDEN
- Загружать весь код приложения в стартовый бандл.
- Держать в entry points что-либо кроме инициализации, композиции и dependency injection.
Next.js — Server Components
MUST
Принцип "Server by default": все компоненты серверные по умолчанию. 'use client' добавляется только при наличии явной необходимости:
- Компонент использует хуки состояния или жизненного цикла (
useState,useEffect,useReducerи т.д.) - Компонент подписывается на браузерные события (
onClick,onChangeи т.д.) - Компонент использует Browser API, недоступные на сервере (
localStorage,IntersectionObserver,navigatorи т.д.) - Компонент зависит от библиотеки, несовместимой с серверным окружением
Клиентская граница устанавливается как можно ниже в дереве рендера.
FORBIDDEN
- Ставить
'use client'на уровне страницы или layout, если интерактивность нужна только дочернему элементу.
// ❌ Плохо: вся страница становится клиентской из-за одной кнопки
'use client';
export default function ProductPage() {
return (
<>
<ProductDetails /> {/* не нуждается в клиенте */}
<AddToCartButton /> {/* только эта кнопка интерактивна */}
</>
);
}
// ✅ Хорошо: граница опущена до минимума
// app/products/[id]/page.tsx — Server Component
export default function ProductPage() {
return (
<>
<ProductDetails />
<AddToCartButton /> {/* отдельный 'use client' компонент */}
</>
);
}
Payload Reduction
MUST
- Данные из
getServerSideProps/ RSC payload содержат только необходимые поля. - Передача целых объектов из API увеличивает время гидратации — передавать только нужные поля.
Примеры code splitting
Next.js — Pages Router
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('../components/heavy-chart'), {
loading: () => <ChartSkeleton />,
ssr: false,
});
export default function AnalyticsPage() {
return <HeavyChart />;
}
Next.js — App Router
// app/dashboard/page.tsx (Server Component)
import dynamic from 'next/dynamic';
const RealtimeWidget = dynamic(() => import('./realtime-widget'), {
loading: () => <WidgetSkeleton />,
});
export default function DashboardPage() {
return <RealtimeWidget />;
}
Astro
---
import Reviews from '../components/Reviews.jsx';
import Chart from '../components/Chart.jsx';
---
<Reviews client:idle /> <!-- инициализация когда browser idle -->
<Chart client:visible /> <!-- инициализация при появлении в viewport -->
Контроль размера бандла
SHOULD
| Инструмент | Применение |
|---|---|
webpack-bundle-analyzer | Webpack / Next.js проекты |
source-map-explorer | Анализ через source maps |
vite-bundle-analyzer | Vite / Astro проекты |