Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Скрипти в head». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Звичайний `<script src="...">` у `<head>` блокує HTML-парсер: браузер зупиняє побудову DOM, завантажує файл по мережі, виконує його і лише потім читає розмітку далі.** Він зобов'язаний так робити, бо скрипт може змінити документ через `document.write` або `innerHTML`. Поки це триває, DOM ще не існує, користувач бачить білий екран, а метрики FCP, LCP, TTI і TBT псуються, особливо на повільній мережі. Розв'язання: додати `defer` для основної логіки (паралельне завантаження, виконання після побудови DOM, порядок збережено), `async` для незалежних сторонніх скриптів або перенести тег у кінець `<body>`. ```html <script src="main.js" defer></script> <script src="analytics.js" async></script> ``` **Ключове:** проблема не в самому `<head>`, а в синхронному виконанні: скрипт без `defer` чи `async` зупиняє парсинг HTML і відкладає перший рендер.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Скрипт у `<head>` без атрибутів `defer` чи `async` зупиняє парсинг HTML: браузер мусить завантажити й виконати JS, перш ніж продовжити будувати DOM.** Через це користувач бачить порожню сторінку доти, доки файл не прийде по мережі, і всі метрики швидкості погіршуються. ## Теорія ### TL;DR - Синхронний `<script src>` блокує HTML-парсер, доки файл не завантажиться і не виконається. - Браузер робить так, бо скрипт може змінити документ, наприклад через `document.write`. - У `<head>` це найболючіше: DOM ще порожній, тому екран білий. - Псуються FCP, LCP, TTI та TBT, особливо на мобільній мережі. - Ліки: `defer` для основного коду, `async` для незалежних скриптів, або тег у кінці `<body>`. - Маленький inline-скрипт у `<head>` допустимий, якщо він справді маленький. ### Швидкий приклад ```html <head> <script src="main.js"></script> </head> <body> <h1>Привіт!</h1> </body> ``` Покроково: 1. Браузер починає читати HTML. 2. Доходить до `<script src="main.js">`. 3. Зупиняє парсинг HTML. 4. Чекає, поки `main.js` завантажиться по мережі, а це можуть бути сотні мілісекунд. 5. Виконує скрипт. 6. І лише потім продовжує парсити решту сторінки. Увесь цей час DOM ще не створено, користувач не бачить контенту, а метрики FCP і LCP страждають. ### Як браузер завантажує сторінку Коли браузер отримує HTML-документ, він виконує такі кроки: 1. Парсить HTML рядок за рядком, згори вниз. 2. Коли зустрічає тег `<link>` з CSS, завантажує стиль, бо стилі впливають на відображення. 3. Коли зустрічає тег `<script>`, зупиняє парсинг, щоб виконати JS. > Чому так? > Тому що скрипт може змінити сам DOM або навіть видалити елементи, які йдуть далі в HTML (`document.write`, `innerHTML` тощо). > Тому браузер зобов'язаний дочекатися виконання JS, перш ніж продовжити розбір. Схематично синхронний варіант виглядає так: ```text HTML: <head> -> <script> -> <body> | завантаження [======= JS завантажується =======] | виконання [======= JS виконується =======] | і лише потім будується DOM ``` А з `defer` завантаження йде паралельно, а виконання відкладається на кінець: ```text HTML і JS завантажуються разом -> DOM побудовано -> JS виконується ``` ### Які проблеми це спричиняє | Проблема | Що відбувається | | --- | --- | | Блокується HTML-парсер | Сторінка не будується, доки не завантажиться JS | | Довга «біла сторінка» | Користувач бачить порожнечу | | Повільні мережі, гірший UX | Особливо на 3G і 4G | | Погіршення Core Web Vitals | LCP, FCP, TTI, TBT зростають | | Скрипти заважають критичному CSS | Критичні стилі застосовуються невчасно | ### Як уникнути блокування **Спосіб 1. `defer`** ```html <script src="main.js" defer></script> ``` Скрипт завантажується паралельно з HTML, але виконується лише після повного парсингу DOM, перед подією `DOMContentLoaded`. Без блокування, з гарантованим порядком виконання, і це ідеальний варіант для більшості випадків. **Спосіб 2. `async`** ```html <script src="analytics.js" async></script> ``` Скрипт завантажується асинхронно, у фоні, і виконується одразу після завантаження, не чекаючи інших. Використовується для незалежних скриптів: аналітики, реклами, пікселів. Порядок виконання не гарантується. **Спосіб 3. Перенести скрипти в кінець `<body>`** ```html <body> ... <script src="main.js"></script> </body> ``` До моменту завантаження JS HTML вже повністю розпарсено, користувач бачить контент, а JS виконується останнім кроком. Це старий, але робочий спосіб, у сучасному HTML його замінив `<script defer>`. ### Вплив на метрики | Метрика | Що погіршується при скриптах у `<head>` | | --- | --- | | **FCP (First Contentful Paint)** | Довше показується перший контент | | **LCP (Largest Contentful Paint)** | Основний елемент відображається пізніше | | **TTI (Time to Interactive)** | JS блокує головний потік | | **TBT (Total Blocking Time)** | Зростає через важкі синхронні скрипти | ### Як робити в продакшені | Тип скрипта | Де і як підключати | | --- | --- | | Основна логіка (UI, SPA) | `<script src="main.js" defer>` | | Аналітика, реклама | `<script src="metrics.js" async>` | | Inline-ініціалізація | `<script>` усередині `<head>`, якщо ініціалізація справді маленька | | Старі бібліотеки | Краще перенести в кінець `<body>` | Підсумкова таблиця: | Сценарій | Що робить | Наслідок | | --- | --- | --- | | `<script>` у `<head>` без атрибутів | Блокує парсинг | Повільно | | `<script defer>` | Завантажує паралельно, виконує після DOM | Оптимально | | `<script async>` | Завантажує й виконує незалежно | Лише для незалежних скриптів | | `<script>` унизу `<body>` | Чекає кінця HTML | Без блокування | ### Типові помилки - **Вважати, що проблема в самому `<head>`.** Блокує не розташування, а синхронне виконання: `<script defer>` у `<head>`, це найкращий варіант. - **Ставити `async` на код, який залежить від DOM або від іншого файлу.** Він може виконатися до появи елементів і зламатися випадковим чином. - **Ховати проблему за `window.onload`.** Скрипт усе одно завантажується синхронно і затримує парсинг, просто логіка всередині чекає довше. - **Забувати про блокувальний CSS.** Важкий `<link rel="stylesheet">` теж затримує рендер, тому критичні стилі варто інлайнити. - **Класти в `<head>` величезний inline-скрипт.** Мережа тут ні до чого, але парсинг і виконання все одно займають головний потік. - **Підключати сторонні віджети без `async`.** Повільний чужий сервер тоді безпосередньо затримує рендер вашої сторінки.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.