iWpress's avatar

How to speed up the loading speed of websites that use reCAPTCHA.

Hello everyone, In my project, I am using the josiasmontag/laravel-recaptchav3 package for reCAPTCHA validation. The package is nice, easy to set up and use. But there is one problem. It greatly reduces the page loading speed. The PageSpeed Insights test shows a decrease in speed from 97 to 51. He considers the main problem to be the loading of reCAPTCHA (recaptcha__en.js). Naturally, the first thing I wanted to do was to defer the loading of the reCAPTCHA. The main thing is that, in my opinion, it does not interfere with the project at all, because reCAPTCHA is needed only after the page is fully loaded and the user fills out the form. To implement this, I did the following:

0 likes
7 replies
iWpress's avatar

Inherited the parent class of the RecaptchaV3 package:

<?php

namespace App\Services;

use Lunaweb\RecaptchaV3\RecaptchaV3;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Psr7\Response;

class RecaptchaV3Async extends RecaptchaV3
{
    /**
     * @param $token
     * @param null $action
     * @return Promise
     */
    public function verifyAsync($token, $action = null)
    {
        return $this->http->requestAsync('POST', $this->origin . '/api/siteverify', [
            'form_params' => [
                'secret'   => $this->secret,
                'response' => $token,
                'remoteip' => $this->request->getClientIp(),
            ],
        ])->then(function (Response $response) use ($action) {
            $body = json_decode($response->getBody(), true);

            if (!isset($body['success']) || $body['success'] !== true) {
                return false;
            }
            if ($action && (!isset($body['action']) || $action != $body['action'])) {
                return false;
            }
            return isset($body['score']) ? $body['score'] : false;
        });
    }
}

iWpress's avatar

Added a provider and a facade for easy use:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\RecaptchaV3Async;
use Illuminate\Contracts\Config\Repository;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Contracts\Foundation\Application;

class RecaptchaV3ServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        $this->app->singleton(RecaptchaV3Async::class, function ($app) {
            return new RecaptchaV3Async(
                $app->make(Repository::class), 
                $app->make(Client::class), 
                $app->make(Request::class), 
                $app->make(Application::class)
            );
        });
        $this->app->alias(RecaptchaV3Async::class, 'recaptchaV3Async');
    }
    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        //
    }
}

iWpress's avatar
<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class RecaptchaV3AsyncFacade extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'recaptchaV3Async';
    }
}

I added it to config/app.php:

  'providers' => [
	...
     App\Providers\RecaptchaV3ServiceProvider::class,
    ],

  'aliases' => [
	...
    'RecaptchaV3Async'  => App\Facades\RecaptchaV3AsyncFacade::class,
    ],

Now instead of {!! RecaptchaV3::initJs() !! }, I use {!! RecaptchaV3Async::initJs() !!} It's all working, but I haven't achieved the desired result. The test shows a low download speed. Maybe someone has already done something similar and can tell me where I went wrong?

chiefguru's avatar

@iwpress there are two issues here, first of all, recatcha.js gets loaded 3 times every time it gets used on a page which sucks, something to do with not sharing cached code across the iframes, this is a known and reported issue. Secondly Page Speed Insights complains loudly about the ~600k of render blocking code loaded by recaptcha.js

The closest thing to a solution I've come across is wrapping the recaptcha initialization and loading scripts in a 1-2 second timeout. This does prevent both from being loaded until after the page has loaded so you don't get any render blocking, but it does add a second to the page loading.

1 like
iWpress's avatar

Thanks @chiefguru, so it makes no sense to "defer" loading scripts until the page is fully loaded?

Google complains about its own product, somehow it becomes a tradition 馃榾

How to deal with fraudsters?

chiefguru's avatar

@iwpress

<script src="https://www.google.com/recaptcha/api.js?onload=onloadRegCaptcha&render=explicit" async defer></script>

I've tried with all combinations of async, defer and nothing and the behaviour doesn't seem to change.

1 like
iWpress's avatar

Thank you for your help @chiefguru. Like you, I added both async and defer but got an error:

<script>
	grecaptcha.ready(function() {   <<< TypeError: grecaptcha.render is not a function.
    	grecaptcha.execute('****', {
        	action: 'captcha3'
        }).then(function(token) {
        	document.getElementById('g-recaptcha-response-65e992a6ccd14').value = token;
        });
    });
</script>

After that, I did as described in the Google reCAPTCHA documentation (Loading reCAPTCHA ): Added a script:

<script>
    if (typeof grecaptcha === 'undefined') {
        grecaptcha = {};
    }
    grecaptcha.ready = function(cb) {
        if (typeof grecaptcha === 'undefined') {
            const c = '___grecaptcha_cfg';
            window[c] = window[c] || {};
            (window[c]['fns'] = window[c]['fns'] || []).push(cb);
        } else {
            cb();
        }
    }
    grecaptcha.ready(function() {
        grecaptcha.render("container", {
            sitekey: '****'
        });
    });
</script>

But the error does not disappear:

<div id="container" class="container">
    <script defer src="https://www.google.com/recaptcha/api.js?hl=&render=****"></script>
    <script>
        if (typeof grecaptcha === 'undefined') {
            grecaptcha = {};
        }
        grecaptcha.ready = function(cb) {
            if (typeof grecaptcha === 'undefined') {
                const c = '___grecaptcha_cfg';
                window[c] = window[c] || {};
                (window[c]['fns'] = window[c]['fns'] || []).push(cb);
            } else {
                cb();
            }
        }
        grecaptcha.ready(function() {
            grecaptcha.render("container", {   <<< TypeError: grecaptcha.render is not a function.
                sitekey: '****'
            });
        });
    </script>

I don't have a solution yet.

Please or to participate in this conversation.