Зауважимо: коли бандл вашого додатку важить більше 2 МБ, а LCP сягає за 3 секунди, причина часто криється в надлишковості фреймворка. Для embed-віджетів, анімацій на JS або розширень браузера Vanilla JavaScript (чистий JavaScript) стає єдиним адекватним вибором. Ми реалізували понад 30 проєктів без жодного фреймворка, забезпечивши LCP менше 1 секунди та бандл розміром 20–40 КБ. Відсутність віртуального DOM і прямий доступ до браузерних API дають максимальну продуктивність сайту, а економія бюджету на розробку сягає 40% (до 50 000 ₴ на типовому віджеті). Вартість розробки простого віджету починається від 10 000 ₴. Наша команда використовує сучасні можливості мови: Web Components, IntersectionObserver, Proxy та ESModules, щоб створювати швидкі та надійні рішення. Наша команда має 10+ років досвіду у веб-розробці та виконала 30+ проєктів на чистому JavaScript. Кожен проєкт починається з аудиту метрик та підбору оптимальної архітектури. Зв'яжіться з нами для аудиту вашого проєкту.
Коли Vanilla JS стає єдиним вірним вибором
Vanilla JS виправданий у сценаріях, де фреймворки додають зайву складність. Embed-віджети вбудовуються на чужі сайти — не можна конфліктувати з їхніми фреймворками. Бібліотеки для npm з нульовими залежностями не обтяжують проєкт користувачів з іншим стеком. Високонавантажені анімації на JS використовують Canvas API, WebGL та Web Animations API безпосередньо, без прошарку virtual DOM. Розширення браузера працюють в ізольованому середовищі Content Scripts, де фреймворки збільшують ризики. Статичні сайти з мінімальною інтерактивністю не потребують 50 КБ заради одного дропдауна. Типовий віджет на Vanilla JS обійдеться на 30-50% дешевше, ніж на React.
| Сценарій | Vanilla JS | React | Vue |
|---|---|---|---|
| embed-віджет | оптимально | надлишково | надлишково |
| SPA з формами | складно | оптимально | оптимально |
| Анімації | відмінно | середньо | середньо |
В одному з проєктів ми замінили React-віджет кошика на Vanilla JS. Бандл скоротився з 120 КБ до 28 КБ (зниження на 77%), LCP впав з 2,4 с до 0,8 с (на 67% швидше). Навантаження на CPU знизилося на 40%. Vanilla JS завантажується в 2 рази швидше React на повільних з'єднаннях, а економія на розробці склала близько 40 000 ₴. У порівнянні з React, бандл Vanilla JS в 3-4 рази менший, а LCP — в 2-3 рази швидший.
Чому Vanilla JS продуктивніший за фреймворки?
Відсутність virtual DOM виключає накладні витрати на порівняння та перемальовування. Пряма робота з document.createElement та appendChild дозволяє керувати кожним оновленням вручну. У поєднанні з IntersectionObserver для лінивого завантаження та requestAnimationFrame для анімацій ви отримуєте LCP менше 1 секунди навіть на мобільних пристроях. Ми також використовуємо preload для критичних ресурсів і prefetch для наступних сторінок. Використання замикань для приватних методів та прототипного наслідування дозволяє досягти високої продуктивності без зайвих абстракцій. Делегування подій та мемоізація скорочують кількість обчислень. Оптимізація LCP досягається через пряму роботу з DOM та мінімізацію перебудов. Скорочення бандла на 77% та економія бюджету на розробку до 40% — не рідкість при міграції на чистий JS.
| Характеристика | Vanilla JS | React (типовий проєкт) |
|---|---|---|
| Розмір бандла | 20–40 KB | 80–150 KB (з React DOM) |
| Контроль | повний | обмежений virtual DOM |
| Залежності | нульові | React + екосистема |
| Сумісність | будь-який сайт | може конфліктувати |
| Продуктивність LCP | < 1 с | 1–2 с (середнє) |
Які сучасні можливості Vanilla JS доступні
Браузерні API значно розширилися: тепер доступні Custom Elements, Shadow DOM, AbortController, Proxy, Service Workers. Приклад Fetch з таймаутом:
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { ...options, signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} finally {
clearTimeout(timeout);
}
}
Web Components — компонентна модель без фреймворка
class ProductCard extends HTMLElement {
static get observedAttributes() { return ['product-id']; }
connectedCallback() {
this.#render();
this.#attachEvents();
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'product-id' && oldVal !== newVal) {
this.#fetchProduct(newVal);
}
}
async #fetchProduct(id) {
const data = await fetchWithTimeout(`/api/products/${id}`);
this.#update(data);
}
#render() {
this.innerHTML = `
<article class="product-card">
<img class="product-card__img" alt="">
<h3 class="product-card__name"></h3>
<span class="product-card__price"></span>
<button class="product-card__btn">В кошик</button>
</article>
`;
}
#update({ name, price, image }) {
this.querySelector('.product-card__img').src = image;
this.querySelector('.product-card__name').textContent = name;
this.querySelector('.product-card__price').textContent = `${price} ₴`;
}
#attachEvents() {
this.querySelector('.product-card__btn').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('add-to-cart', {
bubbles: true,
detail: { productId: this.getAttribute('product-id') }
}));
});
}
}
customElements.define('product-card', ProductCard);
MDN: Web ComponentsВикористання в HTML: <product-card product-id="42"></product-card>. Працює в будь-якому фреймворку або без нього.
Як керувати станом без зайвих бібліотек? Реактивний store з Proxy
function createStore(initialState) {
const listeners = new Set();
const state = new Proxy(structuredClone(initialState), {
set(target, key, value) {
target[key] = value;
listeners.forEach(fn => fn(structuredClone(target)));
return true;
}
});
return {
state,
subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); },
getSnapshot: () => structuredClone(state),
};
}
const cartStore = createStore({ items: [], total: 0 });
cartStore.subscribe(state => {
document.getElementById('cart-count').textContent = state.items.length;
});
Структура проєкту з ESModules
src/
components/
product-card.js
modal.js
lib/
store.js
api.js
utils.js
pages/
catalog.js
product.js
app.js
{
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
Нульових runtime-залежностей. Vite тільки для розробки та збірки.
Як ми працюємо над проєктами на Vanilla JS
- Аналітика — визначаємо критичні сценарії, заміряємо метрики (LCP, TTFB).
- Проєктування — архітектура модулів, карта компонентів, API-контракти.
- Реалізація — ітеративна розробка з код-рев'ю та тестами (Vitest + jsdom).
- Оптимізація — bundle splitting, prefetch, lazy loading, tree-shake.
- Деплой — збірка через Vite, викладка на CDN або хостинг.
Що входить в роботу
- Вихідний код на ESModules з коментарями.
- Документація по компонентах та інструкція з розгортання.
- Гарантія підтримки 3 місяці після здачі.
- Метрики продуктивності до та після.
Строки орієнтовно
- Простий віджет (до 5 компонентів) — від 2 до 5 днів.
- Середній проєкт (до 20 компонентів) — від 1 до 3 тижнів.
- Комплексний додаток (до 50 компонентів) — від 4 до 8 тижнів.
Вартість розраховується індивідуально. Оцінимо ваш проєкт безкоштовно — напишіть нам. Обговоримо ваш проєкт — замовте консультацію. Додаткова економія бюджету до 50 000 ₴ можлива при міграції з фреймворків.







