
⚡ Как добавить 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 |
| Параллельно с парсингом | После полной загрузки DOM | По порядку в DOM |
| Параллельно с парсингом | Немедленно после загрузки | Кто первый загрузился |
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:
1 wp_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, та же механика:
1 wp_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 или добавьте проверку:
1 if ( 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:
1 function 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 } 7 add_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 и ошибкам при копипасте. Правильное решение: массив хендлов и один фильтр с циклом.
1 function 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 } 15 add_filter('script_loader_tag', 'add_defer_to_scripts', 10, 2);
Для async, меняется только атрибут и название массива:
1 function 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 } 15 add_filter('script_loader_tag', 'add_async_to_scripts', 10, 2);
Оба фильтра можно вешать одновременно, defer на свои скрипты, async на сторонние трекеры. Они работают независимо и не конфликтуют.
Практический пример: Google Maps API
Google Maps, классический кандидат на defer. Карта обычно в футере страницы контактов, скрипт тянет 100+ КБ, а пользователю карта нужна далеко не сразу. При этом сам API не зависит от других скриптов страницы, идеальный случай.
Подключаем и откладываем:
1 // functions.php темы 2 function 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 } 18 add_action('wp_enqueue_scripts', 'enqueue_google_maps');
Тот же результат через script_loader_tag:
1 function 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 } 7 add_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



