Ми часто стикаємося з ситуацією: інтернет-магазин на React завантажується 8 секунд на мобільних. Причина — монолітний JS-бандл вагою 2 МБ. Браузер змушений завантажити, розпарсити та виконати весь код до показу першого контенту. Це погіршує INP і FCP, знижує конверсію на 30%. Наша команда пропонує системну оптимізацію бандлу: code splitting за маршрутами, tree shaking, lazy imports та заміну важких бібліотек. Під ключ — аналіз, доопрацювання, моніторинг у CI. Гарантуємо зниження початкового JS до 50 кБ. Оцінимо ваш проєкт за 2 дні.
Аналіз бандлу та пошук проблем
# Vite — візуалізація через rollup-plugin-visualizer
npm install -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true,
})
],
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
'vendor-query': ['@tanstack/react-query'],
},
}
},
chunkSizeWarningLimit: 500,
}
});
Після збірки відкривається інтерактивна карта бандлу. Шукаємо великі бібліотеки (moment.js, lodash), дублювання залежностей та імпорти цілком замість потрібної функції. У середньому такий аудит займає 1–2 години та виявляє основні «товсті» місця.
Code splitting за маршрутами
Code splitting ділить бандл на частини за маршрутами. Користувач завантажує лише код поточної сторінки. Це зменшує обсяг початкового завантаження та прискорює INP. Для React використовуємо React.lazy та Suspense:
// React Router v6 — lazy loading сторінок
import { lazy, Suspense } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const ProductCatalog = lazy(() => import('./pages/ProductCatalog'));
const ProductDetail = lazy(() => import('./pages/ProductDetail'));
const Cart = lazy(() => import('./pages/Cart'));
const Checkout = lazy(() => import('./pages/Checkout'));
const router = createBrowserRouter([
{ path: '/catalog', element: <Suspense fallback={<PageSkeleton />}><ProductCatalog /></Suspense> },
{ path: '/products/:slug', element: <Suspense fallback={<PageSkeleton />}><ProductDetail /></Suspense> },
{ path: '/cart', element: <Suspense fallback={<PageSkeleton />}><Cart /></Suspense> },
{ path: '/checkout', element: <Suspense fallback={<PageSkeleton />}><Checkout /></Suspense> },
]);
// Динамічний імпорт важких компонентів (редактор, графіки, карти)
const RichTextEditor = lazy(() => import('./components/RichTextEditor'));
const Chart = lazy(() => import('./components/Chart'));
function ProductForm() {
const [showEditor, setShowEditor] = useState(false);
return (
<>
<button onClick={() => setShowEditor(true)}>Додати опис</button>
{showEditor && (
<Suspense fallback={<div>Завантаження редактора...</div>}>
<RichTextEditor />
</Suspense>
)}
</>
);
}
Tree shaking та заміна бібліотек
Tree shaking видаляє невикористовуваний код, зменшуючи обсяг JS. Це знижує час парсингу та виконання, покращуючи FCP та INP. Vite та Webpack автоматично виключають dead code за умови правильної організації імпортів.
// Погано: імпорт всього lodash (~70кБ gzip)
import _ from 'lodash';
const sorted = _.sortBy(products, 'price');
// Добре: імпорт лише потрібної функції
import sortBy from 'lodash/sortBy';
const sorted = sortBy(products, 'price');
// Ще краще: нативний JS
const sorted = [...products].sort((a, b) => a.price - b.price);
// date-fns замість moment.js
import { format, addDays } from 'date-fns'; // tree-shakeable
import { ru } from 'date-fns/locale';
Таблиця заміни бібліотек
| Бібліотека | Заміна | Економія |
|---|---|---|
| moment.js (72кБ) | date-fns (тільки потрібні функції) | ~60кБ (в 3.6 рази менше) |
| lodash (70кБ) | lodash-es + tree-shaking | ~50кБ |
| axios (13кБ) | native fetch | 13кБ |
| jquery (87кБ) | Нативний JS | 87кБ |
| react-icons (всі) | Тільки потрібні з @heroicons | 100–500кБ |
Чому tree shaking не завжди працює?
Tree shaking ефективний лише для ES-модулів. Якщо бібліотека експортує CommonJS (require), dead code не видалиться. Перевірте, чи підтримує пакет "type": "module" або використовуйте lodash-es замість lodash. Також переконайтеся, що у вашому проєкті не ввімкнено плагін @rollup/plugin-commonjs без опції transformMixedEsModules. У SPA ця проблема зустрічається часто, особливо при використанні старих пакетів. Ми допомагаємо мігрувати на сучасні альтернативи, що покращує Core Web Vitals.
Що входить у роботу
- Аналіз поточної збірки та звіт з рекомендаціями.
- Реалізація code splitting та lazy loading.
- Заміна важких бібліотек зі збереженням функціональності.
- Налаштування tree shaking та оптимізація чанків.
- Інтеграція моніторингу розміру бандлу в CI/CD.
- Документація та інструкція з подальшої оптимізації.
- Підтримка інженерів протягом 2 тижнів після впровадження.
Процес оптимізації
- Аудит поточного бандлу за допомогою Vite visualizer або Webpack Bundle Analyzer.
- Виявлення великих бібліотек та дублікатів.
- Розробка стратегії code splitting за маршрутами та компонентами.
- Заміна важких бібліотек на легкі аналоги.
- Налаштування tree shaking та manual chunks у Vite/Rollup.
- Впровадження передзавантаження критичних чанків (prefetch при hover).
- Інтеграція перевірки розміру бандлу в CI.
- Документування результатів та навчання команди.
Терміни та результати
Від 2 до 5 днів залежно від складності проєкту. Включає аудит, code splitting, заміну бібліотек та налаштування CI. Гарантуємо зниження початкового JavaScript до 50 кБ. Така оптимізація дозволяє заощадити на трафіку та покращити користувацький досвід, що прямо впливає на конверсію та дохід. Замовте аудит, щоб отримати конкретні цифри для вашого проєкту.
Кейс із практики
Для одного інтернет-магазину (React, Next.js) вихідний бандл важив 1.5 МБ. Після code splitting за сторінками та заміни moment.js на date-fns початковий JS скоротився до 180 кБ. LCP покращився з 4.2 до 1.1 секунди, а INP — з 450 до 120 мс. Конверсія зросла на 12%. Весь проєкт зайняв 4 дні. Економія на CDN-трафіку склала приблизно 30% на місяць.
Моніторинг у CI та метрики
Цільові значення для інтернет-магазину (gzip):
| Чанк | Ціль |
|---|---|
| Початковий JS (critical path) | < 50 кБ |
| React + React DOM | ~42 кБ |
| Сторінка каталогу | < 30 кБ |
| Картка товару | < 20 кБ |
| Кошик/Оформлення | < 40 кБ |
# .github/workflows/bundle-size.yml
- name: Check bundle size
run: |
npm run build
MAIN_JS=$(ls dist/assets/index-*.js | xargs stat -c%s | head -1)
if [ "$MAIN_JS" -gt 200000 ]; then
echo "Bundle too large: ${MAIN_JS} bytes"
exit 1
fi
Докладніше про JavaScript-модулі у документації MDN. Ми гарантуємо покращення метрик Core Web Vitals. Зв'яжіться з нами, щоб отримати консультацію щодо вашого проєкту. Прискорення завантаження сайту — це внесок у його успіх, і ми допоможемо його досягти.







