Коротко. На странице было много форм AjaxForm, каждая тянула https://www.google.com/recaptcha/api.js и рендерила свой виджет reCAPTCHA v2. Это приводило к «лавине» сетевых запросов (anchor, frame, webworker.js, recaptcha__*.js), просадке производительности и багам при валидации. Мы оставили единый загрузчик скрипта и включили ленивую отрисовку виджетов: капча рисуется только когда блок попадает во вьюпорт или появляется в модалке. Результат — резкое сокращение фоновых загрузок без ломки верстки и MODX-чанков.
Сценарий знакомый: на посадочной странице несколько форм, каждая добавляет контейнер <div class="g-recaptcha"> и тут же вставляет <script src="https://www.google.com/recaptcha/api.js?hl=ru">. В результате:
api.js грузится многократно (иногда с разными параметрами);anchor, frame, webworker.js, recaptcha__*.js);api.js поднимается много «служебных» ресурсов — потому что виджетов много и они рендерятся одновременно;reCAPTCHA v2 устроена так, что каждый отдельный виджет — это не просто один DOM-элемент, а связка из нескольких ресурсов. Когда на странице 5–10 форм, умножаем эту связку на количество виджетов — вот и «шум» в сети. А если скрипт api.js подключен несколько раз, проблемы множатся: дубли инициализации, гонки, пустой g-recaptcha-response на бэкенде.
api.js на страницу. Скрипт должен подключаться ровно один раз — централизованно (лучше внизу). Любые повторные вставки из чанков форм нужно убрать.display:none), IntersectionObserver не увидит её содержимое. Виджет нужно рендерить при открытии модалки или после появления в DOM.Я добился того, что скрипт грузится один раз, а сами виджеты рендерятся только тогда, когда они действительно нужны (в зоне видимости или при открытии модалки). Для этого достаточно двух компонентов:
<div class="g-recaptcha" data-sitekey=""></div>
Важно: никакого <script src=".../api.js"> в этом чанке быть не должно.
recaptchav2_lazy_loaderСниппет подключает api.js один раз с render=explicit и бережно, а также включает ленивую отрисовку всех .g-recaptcha по мере видимости и появления. В шаблоне достаточно вставить:
api.js уже подключён (нами или сторонним кодом), и добавляет его только при необходимости.grecaptcha.render станет доступен, и навешивает IntersectionObserver на все .g-recaptcha.<div class="g-recaptcha"> с корректным data-sitekey. (один раз на страницу).recaptcha:api.js (реально загруженный или из кэша);g-recaptcha-response при решении капчи.Почему в HAR всё равно видно несколько webworker.js и anchor?
Потому что для каждого реально отрисованного виджета reCAPTCHA v2 это норма. Мы убираем лишнюю одновременную инициализацию и дубли, но не меняем архитектуру v2 checkbox. Зато теперь виджеты рисуются только по мере нужды — и стартовая «лавина» исчезает.
Нужно ли вешать загрузку на события (клик/idle)?
Нет, если виджеты только ниже по странице и рендерятся через IntersectionObserver. Для скрытых модалок полезно добавить хук «по открытию» (рендерить внутри модалки в момент показа).
Что если другой виджет сам тянет reCAPTCHA?
«Умный» лоадер просто не подгрузит скрипт второй раз. Если сторонний код рендерит свои виджеты — они появятся только когда контейнер станет видимым (у нас это также покрыто наблюдателем DOM).
Я сохранил текущий стек (MODX + AjaxForm + reCAPTCHA v2) и убрал главную причину перегрузки: многократную инициализацию и ранний рендер всех виджетов сразу. Теперь reCAPTCHA подстраивается под реальное поведение пользователя: грузим один скрипт — рисуем только то, что нужно, тогда, когда нужно.