The Complete Guide to Lazy Loading reCAPTCHA for Maximum Performance
While reCAPTCHA is essential for security, its impact on page speed can be significant—especially for Core Web Vitals like Largest Contentful Paint (LCP). Ce guide complet vous montre comment mettre en œuvre le chargement différé pour reCAPTCHA sans compromettre la sécurité ou l'expérience utilisateur.
Why Lazy Load reCAPTCHA?
The Performance Problem:
reCAPTCHA v2/v3 scripts: ~100-300KB additional page weight
Requêtes réseau multiples : Scripts, styles et ressources
Potentiel de blocage du rendu : Peut retarder le LCP de 1 à 3 secondes
Impact sur mobile : Plus grave sur les connexions plus lentes
The Solution:
Load reCAPTCHA only when needed—typically when a user begins interacting with a form. Cela peut améliorer le LCP de 15 à 40 % sur les pages contenant beaucoup de formulaires.
Implementation Approaches
Method 1: Basic Focus-Based Lazy Load (Recommended)
Load reCAPTCHA when user interacts with any form field.
// reCAPTCHA Lazy Loader - Basic Version let reCaptchaLoaded = false; const formFields = document.querySelectorAll('input, textarea, select'); formFields.forEach(field => { field.addEventListener('focus', loadReCaptchaOnDemand); field.addEventListener('click', loadReCaptchaOnDemand); }); function loadReCaptchaOnDemand() { if (!reCaptchaLoaded) {// Charger le script reCAPTCHA const script = document.createElement('script'); script.src = 'https://www.google.com/recaptcha/api.js'; script.async = true; script.defer = true; document.head.appendChild(script);// Marquer comme chargé reCaptchaLoaded = true;// Nettoyer les écouteurs d'événements (facultatif) formFields.forEach(field => { field.removeEventListener('focus', loadReCaptchaOnDemand); field.removeEventListener('click', loadReCaptchaOnDemand); }); console.log('reCAPTCHA loaded on demand');}}
Method 2: Advanced Scroll-Based Lazy Load
Load reCAPTCHA when form becomes visible in viewport.
// Intersection Observer for Viewport Detection let reCaptchaLoaded = false; const observerOptions = { root: null, rootMargin: '100px', // Load 100px before entering viewport threshold: 0.1>;const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting && !reCaptchaLoaded) { loadReCaptcha(); observer.unobserve(entry.target);} }); }, observerOptions);// Observez votre conteneur de formulaireconst formContainer = document.getElementById('contact-form'); if (formContainer) { observer.observe(formContainer);}function loadReCaptcha() { const script = document.createElement('script'); script.src = 'https://www.google.com/recaptcha/api.js'; script.async = true; script.defer = true; script.onload = () => { reCaptchaLoaded = true; console.log('reCAPTCHA loaded via IntersectionObserver');>; document.head.appendChild(script); }
Method 3: Hybrid Approach (Focus + Scroll)
Best of both worlds—loads when form is visible OR when user interacts.
// Hybrid Lazy Loader class ReCaptchaLazyLoader { constructor() { this.loaded = false; this.observed = false; this.form = document.querySelector('form'); this.init();} init() { if (!this.form) return;// Méthode A : Observer la visibilité du formulaire this.setupIntersectionObserver();// Méthode B : écouter les interactions avec le formulaire this.setupInteractionListeners();// Fallback : charger après 5 secondes si aucune interaction this.setupFallback();} setupIntersectionObserver() { if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { this.load(); observer.unobserve(this.form);} }, { threshold: 0.1 }); observer.observe(this.form); this.observed = true;} } setupInteractionListeners() { const fields = this.form.querySelectorAll('input, textarea, select'); fields.forEach(field => { field.addEventListener('focus', () => this.load()); field.addEventListener('click', () => this.load()); });} setupFallback() { setTimeout(() => { if (!this.loaded && this.form.getBoundingClientRect().top < window.innerHeight) { this.load();} }, 5000);} load() { if (this.loaded) return; const script = document.createElement('script'); script.src = 'https://www.google.com/recaptcha/api.js'; script.async = true; script.defer = true; script.onload = () => { this.loaded = true; this.onLoadCallback();>; document.head.appendChild(script);} onLoadCallback() {// Initialisez votre reCAPTCHA ici console.log('reCAPTCHA ready for initialization');// Exemple : grecaptcha.ready(() => { ... });} }// Initialiser lorsque le DOM est prêtdocument.addEventListener('DOMContentLoaded', () => { new ReCaptchaLazyLoader(); });
reCAPTCHA v3 vs v2 Implementation
For reCAPTCHA v3 (Invisible):
// Lazy load v3 with token generation on form submit async function loadAndExecuteReCaptchaV3(action = 'submit') {// Charger le script s'il n'est pas chargé if (typeof grecaptcha === 'undefined') { await new Promise((resolve) => { const script = document.createElement('script'); script.src = 'https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY'; script.onload = resolve; document.head.appendChild(script); });} // Exécuter reCAPTCHA return new Promise((resolve) => { grecaptcha.ready(() => { grecaptcha.execute('YOUR_SITE_KEY', { action: action }) .then(token => resolve(token)); }); });} // Utilisation dans la soumission du formulairedocument.getElementById('myForm').addEventListener('submit', async (e) => { e.preventDefault(); const token = await loadAndExecuteReCaptchaV3();// Ajouter un jeton au formulaire et soumettre const input = document.createElement('input'); input.type = 'hidden'; input.name = 'g-recaptcha-response'; input.value = token; e.target.appendChild(input);// Continuer la soumission du formulaire e.target.submit(); });
For reCAPTCHA v2 (Checkbox):
// Lazy load v2 with checkbox rendering function loadAndRenderReCaptchaV2() { if (typeof grecaptcha === 'undefined') { const script = document.createElement('script'); script.src = 'https://www.google.com/recaptcha/api.js'; script.onload = () => renderReCaptcha(); document.head.appendChild(script); } else { renderReCaptcha();} }function renderReCaptcha() { const container = document.getElementById('recaptcha-container'); if (container && !container.querySelector('.g-recaptcha')) { grecaptcha.render(container, { sitekey: 'YOUR_SITE_KEY', theme: 'light', // or 'dark' size: 'normal' // or 'compact' });} }// Déclencheur lors de l'interaction avec le formulairedocument.getElementById('message').addEventListener('focus', loadAndRenderReCaptchaV2);
Performance Optimizations
1.Preconnect for Faster Loading
Add to your HTML :
<link rel="preconnect" href="https://www.google.com"> <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
2.Resource Hints
<link rel="dns-prefetch" href="//www.google.com"> <link rel="preload" as="script" href="https://www.google.com/recaptcha/api.js" onload="this.onload=null;this.rel='prefetch'">
3.Adaptive Loading Based on Connection
function shouldLoadReCaptchaEarly() { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; if (connection) {// Chargement anticipé sur les connexions rapides if (connection.effectiveType === '4g' || connection.saveData === false || (connection.downlink && connection.downlink > 3)) { // >3 Mbps return true;} }// Chargement différé par défaut return false;}if (shouldLoadReCaptchaEarly()) {// Chargez reCAPTCHA immédiatement sur les connexions rapides loadReCaptcha(); } else {// Utiliser le chargement paresseux sur les connexions plus lentes setupLazyLoading(); }
Error Handling & Edge Cases
1.Network Error Handling
function loadReCaptchaWithRetry(retries = 3, delay = 1000) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://www.google.com/recaptcha/api.js'; script.async = true; script.onload = resolve; script.onerror = () => { if (retries > 0) { setTimeout(() => { loadReCaptchaWithRetry(retries - 1, delay * 2) .then(resolve) .catch(reject); }, delay); } else { reject(new Error('Failed to load reCAPTCHA after multiple attempts'));}>; document.head.appendChild(script); }); }
2.Form Submission Before reCAPTCHA Loads
document.getElementById('contact-form').addEventListener('submit', async (e) => { e.preventDefault();// Afficher l'état de chargement const submitBtn = e.target.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.textContent = 'Verifying...'; submitBtn.disabled = true;essayez {// Assurez-vous que reCAPTCHA est chargé if (typeof grecaptcha === 'undefined') { await loadReCaptchaWithRetry(); await new Promise(resolve => grecaptcha.ready(resolve));}// Exécuter reCAPTCHA const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'submit' });// Procéder à la soumission du formulaire// ... votre logique de soumission } catch (error) { console.error('reCAPTCHA error:', error);// Solution de secours : utiliser une validation alternative ou afficher une erreur alert('Security verification failed. Veuillez réessayer.'); submitBtn.textContent = originalText; submitBtn.disabled = false;}});
Testing & Measurement
Performance Testing Script:
// Measure reCAPTCHA impact on LCP const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name === 'reCAPTCHA-load') { console.log(`reCAPTCHA loaded in ${entry.duration}ms`); console.log(`LCP before reCAPTCHA: ${window.lcpBeforeReCaptcha}`);} }}); observer.observe({ entryTypes: ['measure'] });// Marquer l'heure LCP avant reCAPTCHAwindow.lcpBeforeReCaptcha = performance.now();// Démarrer la mesure lorsque le chargement paresseux commenceperformance.mark('reCAPTCHA-start');// ... une fois le chargement terminéperformance.mark('reCAPTCHA-end'); performance.measure('reCAPTCHA-load', 'reCAPTCHA-start', 'reCAPTCHA-end');
Core Web Vitals Monitoring:
// Track CLS impact let clsBefore, clsAfter; function measureCLSImpact() { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name === 'layout-shift' && entry.hadRecentInput === false) { if (!clsBefore) clsBefore = entry.value; else clsAfter = entry.value;} } }); observer.observe({ entryTypes: ['layout-shift'] }); }
Framework-Specific Implementations
React Component:
import { useEffect, useRef, useState } from 'react'; function LazyReCaptcha({ siteKey, onLoad }) { const [loaded, setLoaded] = useState(false); const formRef = useRef(null); useEffect(() => { const form = formRef.current; if (!form) return; const loadReCaptcha = () => { if (!loaded && typeof window.grecaptcha === 'undefined') { const script = document.createElement('script'); script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`; script.async = true; script.onload = () => { setLoaded(true); onLoad?.();>; document.head.appendChild(script);}>;// Ajouter des écouteurs d'événements à toutes les entrées du formulaire const inputs = form.querySelectorAll('input, textarea'); inputs.forEach(input => { input.addEventListener('focus', loadReCaptcha, { once: true }); }); return () => { inputs.forEach(input => { input.removeEventListener('focus', loadReCaptcha); });>; }, [siteKey, loaded, onLoad]); return <div ref={formRef}>{/* Your form content */}div>; }
Vue.js Directive:
// Vue directive for lazy loading reCAPTCHA export const lazyRecaptcha = { mounted(el, binding) { const { siteKey, onLoad } = binding.value; let loaded = false; const loadScript = () => { if (!loaded && typeof grecaptcha === 'undefined') { const script = document.createElement('script'); script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`; script.async = true; script.onload = () => { loaded = true; onLoad?.();>; document.head.appendChild(script);}>;// Ajouter des écouteurs d'événements à tous les éléments interactifs const elements = el.querySelectorAll('input, textarea, select, button'); elements.forEach(element => { element.addEventListener('focus', loadScript, { once: true }); element.addEventListener('click', loadScript, { once: true }); });}};
Best Practices Summary
Do:
✅ Test on slow 3G connections to ensure usability
✅ Fournissez un retour visuel pendant le chargement de reCAPTCHA
✅ Mettre en œuvre des mécanismes de secours en cas de panne de réseau
✅ Surveiller les Core Web Vitals avant/après la mise en œuvre
✅ Utiliser les déclencheurs appropriés (focus, défilement ou hybride)
✅ Envisagez le chargement compatible avec la connexion pour les réseaux rapides
Don't:
❌ Block form submission if reCAPTCHA fails to load
❌ Oubliez de tester l'accessibilité (lecteurs d'écran, navigation au clavier)
❌ Ignorez les performances mobiles - testez sur des appareils réels
❌ Chargez reCAPTCHA sur chaque page - uniquement lorsque cela est nécessaire
❌ Sacrifiez la sécurité pour les performances - trouvez l'équilibre