---
title: "Defer for Contact Form 7: откладываем принудительные скрипты и стили CF7"
description: "Contact Form 7 — самый популярный плагин для форм в WordPress и один из самых частых виновников проседания PageSpeed. Проблема не в самой форме, а…"
url: "https://rowan-web.ru/blog/defer-for-contact-form-7-otkladyvaem-prinuditelnye-skripty-i-stili-cf7/"
date_modified: "2026-08-23T19:09:06+03:00"
language: "ru-RU"
---
Contact Form 7 — самый популярный плагин для форм в WordPress и один из самых частых виновников проседания PageSpeed. Проблема не в самой форме, а в том, как плагин подключает свои ресурсы: без разбора, в head, синхронно, независимо от того, нужна форма на конкретной странице или нет. Разбираю, как я искал способ это исправить, почему готовые решения не сработали, и что получилось в итоге.

Долгие поиски: три файла, которые никуда не хотели деваться
-----------------------------------------------------------

Открываешь вкладку Network на любой странице с формой — а часто и на любой странице сайта вообще, если явно не выключить автозагрузку константами WPCF7\_LOAD\_JS и WPCF7\_LOAD\_CSS — и там неизменно висят три запроса:

```
/wp-content/plugins/contact-form-7/includes/css/styles.css?ver=6.1.3
/wp-content/plugins/contact-form-7/includes/swv/js/index.js?ver=5.8.7
/wp-content/plugins/contact-form-7/includes/js/index.js?ver=5.8.7
```

Первая мысль — накинуть любой async CSS/JS-плагин и закрыть вопрос. На практике всё оказалось сложнее. Универсальные оптимизаторы либо ломали форму: стоило отложить скрипт contact-form-7, как переставала работать клиентская валидация или AJAX-отправка. Либо не могли ничего сделать: большинство таких плагинов работает по паттерну URL или handle в уже отрисованном HTML, а часть проблемы CF7 сидит глубже, на уровне зависимостей wp-hooks и wp-i18n, общих для десятков других плагинов и блоков. Слепое «отложить всё похожее на contact-form-7» либо не задевало эти зависимости вообще, либо задевало слишком широко и роняло несвязанный функционал.

Разбор через wp\_scripts() и Query Monitor дал неожиданный поворот. Два JS-файла из списка выше — swv/js/index.js и js/index.js — сам CF7 уже регистрирует с in\_footer в true. Они и так не блокируют первую отрисовку страницы, просто визуально висят в Network рядом с остальными. Настоящими узкими местами оказались CSS-файл формы, который браузер по спецификации обязан дождаться перед рендером, и скрипты-зависимости wp-hooks и wp-i18n, напечатанные в head. Ни один из готовых async-плагинов не смотрел на проблему под этим углом — все боролись с тремя видимыми файлами, а не с двумя настоящими причинами блокировки.

Почему тут не работает обычный defer
------------------------------------

С CSS всё понятно — атрибутом defer у script-тега блокировку по стилям не обойти, нужен другой приём. А вот с wp-hooks и wp-i18n есть более интересная засада.

WordPress сам вешает на wp-i18n инлайн-блок через wp\_add\_inline\_script — обычно это вызов wp.i18n.setLocaleData. Печатается он отдельным тегом script сразу после основного и выполняется синхронно, в порядке появления в документе. Если просто добавить defer внешнему wp-i18n.js, браузер отложит его выполнение до конца парсинга DOM, а соседний инлайн-блок запустится немедленно — до того как объект window.wp вообще появится. Результат — ошибка «wp is not defined» в консоли и обрыв части функционала, завязанного на локализацию.

Отсюда и решение: не трогать атрибуты script, а целиком переносить сам handle вместе с его инлайн-довеском в footer, сменив группу загрузки:

```
add_action('wp_enqueue_scripts', 'cf7defer_move_deps_to_footer', 100);
function cf7defer_move_deps_to_footer() {
    foreach (array('wp-hooks', 'wp-i18n') as $handle) {
        if (wp_script_is($handle, 'registered')) {
            wp_scripts()->add_data($handle, 'group', 1); // группа 1 = footer
        }
    }
}
```

Порядок печати «скрипт → инлайн-блок» сохраняется, просто весь блок теперь оказывается в конце документа — после первой отрисовки, но по-прежнему синхронно и без гонки между зависимостью и кодом, который на неё ссылается.

Для CSS используется классический приём loadCSS: media печатается как print, что не блокирует рендер, а по onload медиа переключается обратно на all:

```
add_filter('style_loader_tag', 'cf7defer_async_css', 10, 2);
function cf7defer_async_css($html, $handle) {
    if ('contact-form-7' === $handle || 'contact-form-7-rtl' === $handle) {
        return str_replace(
            "rel='stylesheet'",
            "rel='stylesheet' media='print' onload=\"this.media='all'\"",
            $html
        );
    }
    return $html;
}
```

Цена — форма примерно на 50 миллисекунд может мелькнуть неоформленной. На логику — валидацию, AJAX-отправку — это не влияет, а рендер-блокировку снимает полностью.

Что получилось в бесплатной версии
----------------------------------

Обе техники объединены в плагин Defer for Contact Form 7, с двумя настройками на странице Contact → Performance, включёнными по умолчанию: перенос wp-hooks и wp-i18n в footer и асинхронная загрузка CSS формы.

- Плагин строго зависит от активного Contact Form 7 — без него не делает вообще ничего, только показывает уведомление в админке.
- Обе оптимизации идемпотентны: если тема уже что-то похожее делает, повторное применение ничего не сломает.
- jQuery сознательно не трогается. Откладывать его безопасно только тогда, когда вообще все зависящие от него скрипты на сайте тоже отложены или уже в footer, а на произвольном сайте это никогда не гарантировано.

Pro-версия: ленивая reCAPTCHA v3 без удара по производительности
----------------------------------------------------------------

Раз плагин изначально затевался ради производительности, встроенную защиту от спама нельзя было делать как обычно. Штатная интеграция reCAPTCHA в Contact Form 7 подразумевает загрузку скрипта от Google, причем все также синхронно, т.е. получается что снова появляется сторонний скрипт в критическом пути, который свёл бы на нет всю проделанную работу. PageSpeed при таком подходе сразу теряем около 20 пунктов +-, что конечно, весьма существенно.

В платной версии подход другой. Скрипт Google не грузится заранее, а подтягивается только при первом взаимодействии пользователя с формой — по focusin, pointerdown или touchstart, то есть вне критического пути рендера, и в Lighthouse или PageSpeed Insights он просто не попадает. Токен v3 живёт около 120 секунд и не переиспользуется, поэтому отправка формы перехватывается ещё до обработчиков самого CF7: форма ставится на паузу, добывается свежий токен через grecaptcha.execute(), и только после этого отправка продолжается тем же событием. Проверка score выполняется на сервере, на хуке wpcf7\_spam, с настраиваемым порогом — по умолчанию 0.5. Если Google недоступен, плагин работает в режиме fail-open, чтобы не терять реальные заявки из-за сетевой ошибки.

Бейдж reCAPTCHA скрывается, а под формой автоматически добавляется обязательный по условиям Google дисклеймер о политике конфиденциальности и условиях использования. Отдельно плагин проверяет, не включена ли параллельно штатная интеграция reCAPTCHA в самом CF7, и предупреждает о конфликте — если оставить оба варианта, api.js загрузится дважды и смысл ленивой загрузки потеряется.

Итог
----

Бесплатная версия закрывает конкретную и часто незамеченную проблему: Contact Form 7 без спроса ставит в head каждой страницы блокирующий CSS и пару JS-зависимостей, с которыми обычные async-оптимизаторы не справляются — либо бьют мимо, в уже безобидные файлы в footer, либо задевают слишком широко и ломают локализацию. Платная версия развивает ту же идею на защиту от спама: reCAPTCHA v3, которая не платит перформансом за свою работу.

Плагин уже в каталоге WordPress.org, слаг — [defer-for-contact-form-7](https://wordpress.org/plugins/defer-for-contact-form-7/).

[Полный список страниц сайта, читаемых AI](https://rowan-web.ru/llms.txt)
