Skip to content

Все для WordPress, веб-розробки — і не тільки

⚡ Як додати defer і async для скриптів WordPress у function.php

⚡ Як додати defer і async для скриптів WordPress у function.php

Сторінки завантажуються повільно, Google PageSpeed Insights видає помаранчеві попередження, а клієнт запитує: «чому сайт гальмує?» У дев’яти випадках із десяти корінь проблеми, JavaScript, який блокує рендеринг. Браузер доходить до <script>, зупиняє побудову DOM, завантажує та виконує скрипт, і лише потім продовжує. На сучасному сайті з десятком плагінів ця затримка перетворюється на секунди.

WordPress довгий час не давав штатного способу керувати завантаженням скриптів. Розробники танцювали з бубном: фільтрували script_loader_tag, патчили вивід через clean_url або взагалі писали власний walker для WP_Scripts. Але з виходом WordPress 6.3 ситуація змінилася радикально, і зараз у нас є чистий, підтримуваний спосіб додати defer або async будь-якому скрипту без жодного хаку.

Нижче, два робочі методи: сучасний нативний (WP 6.3+) і перевірений фільтр script_loader_tag (WP 4.1+). Обидва протестовані на реальних проєктах, обидва не ламають чергу залежностей.

💡 Швидкий огляд:

  • Розберіться, чим відрізняється defer від async і коли застосовувати кожен, від цього залежить, чи не зламається функціональність після оптимізації
  • Використовуйте нативний метод WordPress 6.3+ через wp_enqueue_script() з параметром strategy, найчистіший спосіб, що зберігає порядок виконання
  • Якщо сайт на версії нижче 6.3, застосуйте фільтр script_loader_tag з масивом обробників — це працює починаючи з WordPress 4.1
  • Для кількох скриптів зберіть масив обробників і проженіть його циклом, один фільтр на всі скрипти замість копіпасти

Що таке defer і async та коли їх застосовувати

Коли браузер зустрічає звичайний тег <script>, він робить три речі підряд: зупиняє парсинг HTML, завантажує скрипт, виконує його. І лише потім повертається до HTML. На сторінці з п’ятьма скриптами в <head> це означає, що користувач бачить білий екран, поки завантажується останній плагін коментарів, навіть якщо сам допис уже давно міг би відрендеритися.

Атрибути defer і async вирішують цю проблему, але працюють по-різному:

Атрибут

Коли завантажується

Коли виконується

Порядок виконання

(немає)

Блокує парсинг одразу

Негайно після завантаження

За порядком у DOM

defer

Паралельно з парсингом

Після повного завантаження DOM

За порядком у DOM

async

Паралельно з парсингом

Негайно після завантаження

Хто перший завантажився

Defer, робочий кінь для більшості сценаріїв. Скрипт завантажується паралельно з HTML, а виконується лише коли DOM повністю побудований. Порядок зберігається: скрипт A виконається раніше за скрипт Б, навіть якщо Б завантажився швидше. Це критично для jQuery і всього, що від нього залежить.

Async, інструмент для незалежних скриптів. Аналітика, реклама, віджети соцмереж: їм не потрібен DOM, їм не важливий порядок, їм потрібно просто відпрацювати якомога раніше. Але якщо поставити async на скрипт, який залежить від jQuery, отримаєте $ is not defined з високою ймовірністю.

Просте правило: скрипт залежить від інших скриптів або від DOM → defer. Скрипт повністю автономний → async. Якщо сумніваєтеся, завжди починайте з defer.

Спосіб 1: Нативний метод WordPress 6.3+

З липня 2023 року в ядрі WordPress працює новий механізм. Функції wp_register_script() та wp_enqueue_script() отримали перевантажений п’ятий параметр $args, масив, у якому можна вказати стратегію завантаження. Жодних фільтрів, жодної магії з рядками, жодного ризику зламати порядок залежностей.

Базовий синтаксис для defer:

1wp_enqueue_script(
2 'my-js-handle',
3 get_template_directory_uri() . '/js/my-script.js',
4 array('jquery'),
5 '1.0.0',
6 array(
7 'strategy' => 'defer',
8 'in_footer' => true,
9 )
10);

Для async, та сама механіка:

1wp_enqueue_script(
2 'google-analytics',
3 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX',
4 array(),
5 '1.0.0',
6 array(
7 'strategy' => 'async',
8 'in_footer' => false,
9 )
10);

Ключ in_footer всередині масиву працює так само, як і старий булевий параметр: true, скрипт у футері, false, у <head>. Для defer зазвичай ставлять true (скрипт і так чекає DOM, немає сенсу завантажувати його рано), для async, як зручно.

Головна перевага нативного методу, ядро саме перевіряє дерево залежностей. Якщо скрипт A з defer залежить від скрипту Б, а Б зареєстрований без стратегії (блокувальним), WordPress не зламає вам сайт: він автоматично знизить стратегію скрипту A до блокувальної. При використанні script_loader_tag ви такого захисту позбавлені, фільтр тупо підставляє атрибут, не дивлячись на залежності.

Важливо: масив $args з’явився в WordPress 6.3. Якщо тема або плагін мають працювати на версіях нижче, використовуйте спосіб 2 або додайте перевірку:

1if ( version_compare( $GLOBALS['wp_version'], '6.3', '>=' ) ) {
2 // нативный метод
3} else {
4 // фильтр script_loader_tag
5}

Спосіб 2: Фільтр script_loader_tag (WordPress 4.1+)

Якщо сайт працює на версії нижче 6.3 або потрібно підтримувати зворотну сумісність, застосовуйте перевірений фільтр script_loader_tag. Він існує з WordPress 4.1 і досі працює безвідмовно.

Фільтр спрацьовує прямо перед виводом тегу <script> у HTML, ви отримуєте готовий рядок тегу, обробник скрипту та шлях до файлу, і можете замінити src на defer="defer" src або async="async" src.

Одиночний скрипт із defer:

1function add_defer_to_my_script($tag, $handle) {
2 if ( 'my-js-handle' !== $handle ) {
3 return $tag;
4 }
5 return str_replace( ' src', ' defer="defer" src', $tag );
6}
7add_filter('script_loader_tag', 'add_defer_to_my_script', 10, 2);

Код розміщується у functions.php активної теми або, що правильніше, в окремому плагіні для сніпетів типу Code Snippets або WPCode. Якщо покласти в functions.php дочірньої теми, при зміні теми скрипти знову стануть блокувальними, і ви про це дізнаєтеся не одразу.

Обробник скрипту — це перший параметр, який ви передали в wp_register_script() або wp_enqueue_script(). Саме він фігурує в умові if. Не вгадуйте обробник, відкрийте вихідний код плагіна або теми та знайдіть виклик wp_enqueue_script.

Defer і async для кількох скриптів

Додавати по одному фільтру на кожен скрипт, шлях до пухкого functions.php і помилок при копіпасті. Правильне рішення: масив обробників і один фільтр із циклом.

1function add_defer_to_scripts($tag, $handle) {
2 $scripts_to_defer = array(
3 'my-js-handle',
4 'another-handle',
5 'third-party-lib',
6 );
7
8 foreach ( $scripts_to_defer as $defer_script ) {
9 if ( $defer_script === $handle ) {
10 return str_replace( ' src', ' defer="defer" src', $tag );
11 }
12 }
13 return $tag;
14}
15add_filter('script_loader_tag', 'add_defer_to_scripts', 10, 2);

Для async змінюється лише атрибут і назва масиву:

1function add_async_to_scripts($tag, $handle) {
2 $scripts_to_async = array(
3 'google-tag-manager',
4 'facebook-pixel',
5 'hotjar',
6 );
7
8 foreach ( $scripts_to_async as $async_script ) {
9 if ( $async_script === $handle ) {
10 return str_replace( ' src', ' async="async" src', $tag );
11 }
12 }
13 return $tag;
14}
15add_filter('script_loader_tag', 'add_async_to_scripts', 10, 2);

Обидва фільтри можна вішати одночасно, defer на свої скрипти, async на сторонні трекери. Вони працюють незалежно і не конфліктують.

Практичний приклад: Google Maps API

Google Maps, класичний кандидат на defer. Карта зазвичай у футері сторінки контактів, скрипт тягне 100+ КБ, а користувачеві карта потрібна далеко не одразу. При цьому сам API не залежить від інших скриптів сторінки, ідеальний випадок.

Підключаємо та відкладаємо:

1// functions.php темы
2function enqueue_google_maps() {
3 if ( ! is_page('contacts') ) {
4 return;
5 }
6
7 wp_enqueue_script(
8 'google-maps-api',
9 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY',
10 array(),
11 null,
12 array(
13 'strategy' => 'defer',
14 'in_footer' => true,
15 )
16 );
17}
18add_action('wp_enqueue_scripts', 'enqueue_google_maps');

Той самий результат через script_loader_tag:

1function add_defer_to_google_maps($tag, $handle) {
2 if ( 'google-maps-api' !== $handle ) {
3 return $tag;
4 }
5 return str_replace( ' src', ' defer="defer" src', $tag );
6}
7add_filter('script_loader_tag', 'add_defer_to_google_maps', 10, 2);

Після встановлення будь-якого з варіантів обов’язково перевірте карту на сторінці контактів. Відкрийте консоль браузера (F12), переконайтеся, що немає помилок JavaScript, і що карта відрендерилася коректно. Якщо з’явилася помилка на кшталт initMap is not a function, значить, ваш ініціалізувальний скрипт теж потрібно позначити як defer і розмістити строго після підключення API.

Як перевірити, що defer і async працюють

Після впровадження, перевірка. Без неї ви не знаєте, чи спрацювала оптимізація, чи просто лежить мертвим кодом.

Відкрийте вихідний код сторінки (Ctrl+U) і знайдіть свої скрипти. У тегу <script> мають з’явитися атрибути:

1<script defer="defer" src="/wp-content/themes/my-theme/js/my-script.js"></script>

Якщо атрибутів немає, перевірте, чи збігається обробник у фільтрі з реальним обробником скрипту. Часта помилка: в wp_enqueue_script обробник my-plugin-frontend, а у фільтрі my_plugin_frontend. Дефіс проти підкреслення, і фільтр мовчки пропускає скрипт.

Фінальний штрих, Google PageSpeed Insights або Lighthouse у вкладці Audits інструментів розробника. Розділ «Усуньте ресурси, що блокують відображення» має показати покращення. Конкретний виграш залежить від кількості та розміру скриптів, але для типового сайту на WordPress з 5-7 плагінами скорочення блокувального JavaScript на 40-60%, досяжний результат.

⁉️🤔 Часті запитання

Чи можна використовувати і defer, і async на одному скрипті?

Ні. Якщо вказати обидва атрибути одночасно, браузер проігнорує defer і відпрацює скрипт як async. Ця поведінка зашита в специфікації HTML, async завжди пріоритетніший. Виберіть щось одне, виходячи з того, чи важливий порядок виконання.

Що робити, якщо після додавання defer скрипт перестав працювати?

Скоріш за все, скрипт очікує, що DOM ще не побудований, і намагається маніпулювати елементами, яких у момент виконання немає. Замініть defer на стандартне блокувальне завантаження для цього конкретного скрипту. Або обгорніть код скрипту в DOMContentLoaded, тоді він зможе працювати з defer без помилок. Другий варіант кращий: ви зберігаєте оптимізацію і лагодите сумісність.

У чому різниця між defer і перенесенням скрипту в футер через wp_enqueue_script з $in_footer = true?

$in_footer = true лише переміщує тег <script> із <head> у кінець <body>. Скрипт, як і раніше, блокує рендеринг, просто пізніше. defer завантажується паралельно з парсингом HTML і виконується строго після побудови DOM. Спільне використання (in_footer => true + strategy => 'defer') дає максимальний ефект: скрипт у футері не затримує перший рендер, а defer гарантує, що він не заблокує і фінальне відтворення.

Чи потрібно оновлювати WordPress до 6.3 заради нативного методу?

Якщо сайт на версії 6.2 або старшій, оновлюватися варто не лише заради strategy. WordPress 6.3 закрив десятки вразливостей і приніс покращення продуктивності самого ядра. Але якщо оновлення з якихось причин неможливе, фільтр script_loader_tag працює абсолютно надійно з версії 4.1, випущеної у 2014 році. Ви нічого не втрачаєте, використовуючи його.

Як бути з jQuery, defer чи залишити як є?

jQuery має завантажуватися з defer, якщо всі залежні від нього скрипти теж позначені defer. Проблема в тому, що WordPress-плагіни вкрай рідко керують атрибутами своїх скриптів. Якщо ви ставите defer на jQuery, а плагін контактної форми підключає свій скрипт без атрибутів, браузер виконає плагін раніше за jQuery, і форма зламається. Практична порада: почніть із defer для власних скриптів теми. jQuery не чіпайте, поки не протестуєте кожен плагін на сайті.

Що ставити на бойовий сайт у 2026 році

Якщо сервер працює на WordPress 6.3 або новішій, тільки нативний метод. Чистий код, захист від конфліктів залежностей, підтримка ядром. Починайте з defer для всіх скриптів теми та критично важливих плагінів; async резервуйте для аналітики та сторонніх віджетів.

Якщо версія нижче 6.3, фільтр script_loader_tag з масивом обробників. Працює десятиліття, ламатися нічому. Єдине, чого він не вміє, автоматично перевіряти дерево залежностей, тому додавайте скрипти в масив по одному і після кожного перевіряйте сайт.

І головне: жоден метод не замінить ревізію самих скриптів. Якщо плагін галереї підключає 15 файлів заради показу трьох картинок, ні defer, ні async радикально не врятують. Оптимізація завантаження починається із запитання «а чи потрібен цей скрипт взагалі», і лише потім, «як його завантажити».

🔗 Офіційна документація WordPress 6.3, Script Loading Strategies