Comment charger paresseux reCAPTCHA ? Optimisation des performances du site Web

Comment charger paresseux reCAPTCHA ? Optimisation des performances du site Web

December 03, 2025 18 Views
Comment charger paresseux reCAPTCHA ? Optimisation des performances du site Web

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.

javascript
// 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.

javascript
// 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.

javascript
// 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):

javascript
// 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):

javascript
// 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 :

html
<link rel="preconnect" href="https://www.google.com">
<link rel="preconnect" href="https://www.gstatic.com" crossorigin>

2.Resource Hints

html
<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

javascript
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

javascript
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

javascript
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:

javascript
// 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:

javascript
// 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:

jsx
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:

javascript
// 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


Share this article