TehnoBlog.org

Google Invisible reCaptcha – How To Boost Lighthouse Performance Score?

This article is about reCaptcha v2.0 API with explicit rendering method integration — it is not intended for reCaptcha v3.0

Did you know that Google’s reCaptcha anti-spam system SLOWS DOWN all of your websites?

Google reCaptcha Logo

If you don’t know what Lighthouse is check our brief introduction to Lighthouse project.

Google Invisible reCaptcha + Lighthouse Performance Score

Recently, we were working on speed optimization and come to some very interesting findings during our website performance audit. One thing in particular that came as a real shocker was recaptcha__en.js resource that was listed as NO.1 culprit in our JS performance profiling section under Lighthouse audit session ($300 laptop). Over 2 seconds were spent just to evaluate that huge code base (api.js initiates recaptcha__en.js) and little over 50 ms to run it. The later part is not a big deal, but the former one is. Why? Because it affects Time to Interactive parameter in Lighthouse, apparently, and, that is a big deal.

Google Lighthouse And reCaptcha Artwork

To further verify these findings, we did a ON/OFF A/B testing with/without reCaptcha plugin enabled in WordPress, and it was clear as black on white that it is a big deal, after all. See screenshots below + summarized table with scores.

Google Lighthouse Page Performance with-without reCaptcha

Google Lighthouse Page Diagnostics with-without reCaptcha

Google PageSpeed Insights Score – with+without reCaptcha

N Audit Test with
reCaptcha plugin
without
reCaptcha plugin
Score Difference
1 Google Chrome Browser
Lighthouse Performance Score
52 63 over 20%
2 Google PageSpeed Insights
Performance Score
24 38 over 55%

The above results are far from ideal, but can you notice (can you?) that single “thing” in form of a reCaptcha library contributing to a huge score penalty? What alternatives we have at our disposal to improve this awkward situation?

Well, for one, we have absolutely no intentions leaving our property unprotected. No, sir! On the other hand, we don’t wish to penalize every user/visitor of our website, who doesn’t want to leave comments or fill-in contact and registration forms, pushing towards it’s browser’s throat all protection stuff completely unnecessary!

Few things comes to mind:

IDEA #1

Get rid of reCaptcha for good! Yeah, easier said than done. Good luck with that approach! :)

IDEA #2

Separate forms with reCaptcha and load them via Ajax only when user decides to interact with them (e.g. click to show/load comments and new comment submit form button). This sounds sound, but how do we actually achieve it? Well, it’s complicated, really. It sounds simple, but in reality it’s not. Particularly, if you don’t know JavaScript and PHP inside-out and if you aren’t prepared to rewrite huge portions of your WordPress theme, than you are on your own. Oh, btw, good luck googling hands-on easy-to-follow tutorials about this.

IDEA #3 – BINGO!

Forget about Ajax splitting, keep things just as they are, and trigger loading/execution/running of reCaptcha library only when user interacts with the input form(s). This is, actually, quite possible and relatively easy to implement!

Let’s do it!

Google reCaptcha Manual Loading On Custom Trigger Event

Ideally, we should trigger Google reCaptcha loading only when users interacts with our forms – e.g. when the form is in focus, or when user starts typing into the forms. While they are typing their nonsense, reCaptcha will silently load in the background in a split of a second, starting it’s protective role on our form(s), but, not before that event!

PART 1: HTML Input Form

HTML – simplest WordPress comment submit form:

<form id="commentform" action="./wp-comments-post.php" method="post">
    <input id="author" name="author" type="text" aria-label="author-name" />
    <textarea id="comment" name="comment" aria-label="comment-text"></textarea>
    <input id="submit" class="invisible-recaptcha" name="submit" type="submit" value="Submit" aria-label="submit-comment" />
</form>

PART 2: reCaptcha div holder

This is usually injected by WP plugins, but here it is for completeness:

<div class="invisible-recaptcha"></div>

PART 3: reCaptcha Explicit Render

Important part here is to remove loading of https://www.google.com/recaptcha/api.js?… script right away in the header – that’s a big no-no now! If you use WordPress or other CMS plugins, you should modify plugin’s enqueue function.

Example universal reCaptcha explicit render implementation for WordPress (note: see invisible-recaptcha plugin > PublicEngine.php file for more details).

Note: We have greatly simplified below code, because in newer plugin versions support was added for Contact Form 7 (CF7) plugin as well, which is not relevant in this case.

<script type='text/javascript'>
function renderReCaptcha() {
    for (var i = 0; i < document.forms.length; ++i) {
        var form = document.forms[i];
        var holder = form.querySelector('.invisible-recaptcha');
        if (null === holder) continue;
        (function(frm){
            var holderId = grecaptcha.render(holder, {
            'sitekey': 'MY-API-KEY-HERE',
            'badge': 'inline', // or 'badge'
            'size': 'invisible',
            'callback': function (recaptchaToken) {HTMLFormElement.prototype.submit.call(frm);},
            'expired-callback': function(){grecaptcha.reset(holderId);}
            });
            frm.onsubmit = function (evt){evt.preventDefault();grecaptcha.execute(holderId);};
        })(form);
    }
};
</script>

PART 4: reCaptcha Load

Bind reCaptcha to form input field focus event listener.
This is the most important part that makes the whole magic fully functional! ;)

Of course, here’s the part where you can get creative. For example, you can initially hide comment input form and place a button named ‘Leave comment’ or similar. When user clicks on that button, it will reveal comment input form, and load reCaptcha library simultaneously. Naturally, code will require some modification e.g. to change event listener from ‘focus’ to ‘click’ event and target elements (tags), but everything else should remain essentially the same.

JavaScript solution

<script type="text/javascript">
	// trigger loading api.js (recaptcha.js) script
	var reCaptchaFocus = function() {
	var head = document.getElementsByTagName('head')[0];
	var script = document.createElement('script');
	script.type = 'text/javascript';
	script.src = 'https://www.google.com/recaptcha/api.js?onload=renderReCaptcha&render=explicit';
	head.appendChild(script);

	// remove focus to avoid js error:
	// Uncaught Error: reCAPTCHA has already been rendered in this element at Object.kh
	document.getElementById('author').removeEventListener('focus', reCaptchaFocus);
	document.getElementById('comment').removeEventListener('focus', reCaptchaFocus);
	};
	// add initial event listener to our basic HTML form
	document.getElementById('author').addEventListener('focus', reCaptchaFocus, false);
	document.getElementById('comment').addEventListener('focus', reCaptchaFocus, false);
</script>

jQuery solution (partial)

<script type="text/javascript">
	$('#author,#comment').on('focus', function() {
		// trigger loading api.js (recaptcha.js) script
		var head = document.getElementsByTagName('head')[0];
		var script = document.createElement('script');
		script.type = 'text/javascript';
		script.src = 'https://www.google.com/recaptcha/api.js?onload=renderReCaptcha&render=explicit';
		head.appendChild(script);

		// remove focus to avoid js error:
		// Uncaught Error: reCAPTCHA has already been rendered in this element at Object.kh
		$('#author,#comment').off('focus');
	});
</script>

AND DONE!

That’s the whole trick, really! We improved our website score by some 20% in Chrome’s v71 Lighthouse, and 55% in Google’s PageSpeed insights online test suite (mobile emulation mode in both cases). Plus, we will not load reCaptcha to visitors who will never interact with our forms in the first place! Ok, score of 38 in PageSpeed still sucks (unusually high interaction time could be a bug on their end or they are using like really slow ancient device), but, hey – it’s still an improvement! :)