A few days ago I ran Lighthouse in a couple of my websites and the performance score wasn't very good (around ~50). Most of the recommendations provided to improve the score were server-side, like caching and compressing assets but the score gains when I applied them were not that good. I realized that one the things that what was impacting the website performance the most was reCaptcha.
I use reCaptcha in every page that contains a form to avoid spam so getting rid of it was not an option. After searching online for some ways to improve the situation, I found this article which explained how to solve all my issues. The solution is awesome by its simplicity: do not load reCaptcha on the initial page load but lazy load it when the user actually interacts with one of the website forms.
Let's say we have a page with a simple subscription form and we load reCaptcha as it's detailed in their docs:
<html>
<head>
<title>My page</title>
</head>
<body>
<header>
<h1>My awesome website</h1>
</header>
<main>
<form id="contactForm" action="#" method="POST">
<input aria-label="Name" id="sub_name" type="text" required placeholder="Johnny Mnemonic" />
<input aria-label="Email address" id="subEmail" type="email" required placeholder="eightgigs@memory.com" />
<input id="submitBtn" value="Subscribe" type="submit" data-sitekey="GOOGLE_RECAPTCHA_KEY"
data-callback="sendForm">
</form>
</main>
<!-- reCaptcha API JS -->
<script src="https://www.google.com/recaptcha/api.js" defer></script>
<script>
function sendForm() {
document.getElementById('contactForm').submit();
}
</script>
</body>
</html>
The reCaptcha API library is loaded with the page, just 1.2KB, but it automatically triggers a request to https://www.gstatic.com/recaptcha/releases/ which adds another extra 127KB to our page. And what happens if the user never interacts with the form? We've loaded a JavaScript file for no reason at all.
The solution is pretty simple and easy to implement:
<html>
<head>
<title>My page</title>
</head>
<body>
<header>
<h1>My awesome website</h1>
</header>
<main>
<form id="contactForm" action="#" method="POST">
<input aria-label="Name" id="subName" type="text" required placeholder="Johnny Mnemonic" />
<input aria-label="Email address" id="subEmail" type="email" required placeholder="eightgigs@memory.com" />
<input id="submitBtn" value="Subscribe" type="submit" data-sitekey="GOOGLE_RECAPTCHA_KEY"
data-callback="sendForm">
</form>
</main>
<script>
function sendForm() {
document.getElementById('contactForm').submit();
}
// Function that loads recaptcha on form input focus
function reCaptchaOnFocus() {
var head = document.getElementsByTagName('head')[0]
var script = document.createElement('script')
script.type = 'text/javascript';
script.src = 'https://www.google.com/recaptcha/api.js'
head.appendChild(script);
// remove focus to avoid js error:
document.getElementById('subName').removeEventListener('focus', reCaptchaOnFocus)
document.getElementById('subEmail').removeEventListener('focus', reCaptchaOnFocus)
};
// add initial event listener to the form inputs
document.getElementById('subName').addEventListener('focus', reCaptchaOnFocus, false);
document.getElementById('subEmail').addEventListener('focus', reCaptchaOnFocus, false);
</script>
</body>
</html>
Let me explain what's happening here:
- We're no longer loading the reCaptcha API JS library by default.
- We've declared a function recaptchaOnFocus that adds the script tag with the reCaptcha API JS library to our page header when it's invoked.
- We've added event listeners in our form inputs to invoke the recaptchaOnFocus function.
This way, our initial page load has 2 less requests and we've saved 128KB. For me this was the difference between these two results:
I hope this helps you improve your page load times but don't apply this just to reCaptcha. Think about other libraries you might be loading by default in your pages that can be lazy loaded in a similar way. I'm sure you'll be able to find some of them that are only necessary in edge cases.
If you liked this article, you can follow me on Twitter where I share dev tips like this one, updates about my projects and insteresting articles I find online 😎
This article was originally posted in my blog where you can find other articles about web development focused on Laravel, Node.js Vue and more.
Happy coding!
Top comments (7)
Great idea! It works perfectly on Chrome and Edge, but in Firefox the script is not being added in production.
It works in development mode but I'm having these warnings:
Content-Security-Policy: Ignoring “'unsafe-inline'” within script-src: ‘strict-dynamic’ specified
Content-Security-Policy: Ignoring “https:” within script-src: ‘strict-dynamic’ specified
Could this be preventing the script from being added in production?
I don't know how to workaround this problem
The instructions say add the js to the page header --- but it looks like it was added to the bottom of the body.
Where is the best place to add this on a wordpress website?
The other thing that is confusing, is the title of the article says "Lazy load" but then the article goes on to explain that the code makes it so the code doesn't load at all until somebody clicks submit on a contact form. In that case it isn't "lazy load."
Bottom line... this JS doesn't work when I add it to the body or the page header. I get the erro "sendform defined but not used, and semi-colon's missing, etc..
I don't know JS at all, I just want to use the snippet, and know where to put it, and know what it is actually designed to do.
Thank you!
Excellent idea! But this function will add the script every focus event, right? Do you think that's a good idea if you check if this script was already added?
removeEventListener is preventing this,, isn't it?
Sorry, you're right
Great post! There is so much more to learn from this writing (for me). The coolest thing about this is adding the node to the
<head>
element.works fine,thanks