Перейти к основному содержимому

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-analyzerWebpack / Next.js проекты
source-map-explorerАнализ через source maps
vite-bundle-analyzerVite / Astro проекты