Integration With Google Lighthouse API - Why Is It Worth It?

Dec 02, 2022 5 minutes read
Photo by L. F (pexels.com)

Google's Lighthouse is an open-source tool used for analyzing the performance, accessibility, progressive web apps, SEO, and more of web pages. It is an excellent tool for developers to ensure their websites are optimized and following the best practices.

In this article, I will show you how to integrate the Google Lighthouse API into a Laravel project to automate the auditing process. This article assumes that you have some knowledge of PHP and Laravel. Before we start, based on the article on the Google Developer Blog, it is stated that PageSpeed v5 is fully supported by Lighthouse engine and offers the same metrics as Google Lighthouse.

Google API Console

Step 1: Go to the Google Cloud Platform Console

To start, navigate to the Google Cloud Platform Console. You can access it through this link: Google Cloud Platform Console.

Step 2: Create a new project or select an existing one

In the GCP Console, you'll see a dropdown at the top, click on it to select a project or create a new one by clicking on New Project button.

Step 3: Enable the Lighthouse API

Now that you have a project, you need to enable the PageSpeed Insights API. Click on the hamburger menu on the top left corner, then navigate to APIs & ServicesLibrary. In the API Library, search for PageSpeed. Once you find it, click on it and then click Enable.

Step 4: Create an API Key

After you have enabled the PageSpeed Insights API, you need to create an API key that will be used to authenticate your requests. Navigate to APIs & ServicesCredentials. On the Credentials page, click Create Credentials and then API key. The console will create the key and show you a dialog box with the created API key.

Step 5: Restrict your API Key (Optional but Recommended)

Restricting your API Key is optional but recommended to prevent unauthorized use of your key. In the dialog box where your key is displayed, click Restrict key. On the next page, you can set restrictions under API restrictions.

It's important to remember that API keys are sensitive and should be kept secure. They should not be embedded in publicly accessible client-side code or shared publicly.

Setting up Laravel things

In this step, we need to prepare a migration, model, service, and command to execute our report. This article assumes that each execution of our command will analyze all the pages visible in the sitemap. This makes a lot of sense, as we are primarily interested in the condition of the pages that are visible in the sitemap (indexed by search engines).

Migration

Our target table google_lighthouse_log will contain only 7 columns. We will store the entire JSON returned by the Google API.

Schema::create('google_lighthouse_log', function (Blueprint $table) {
    $table->id();
    $table->json('data')->nullable();
    $table->boolean('has_error');
    $table->text('message')->nullable();
    $table->integer('duration_seconds')->nullable();
    $table->timestamps();
});

Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
class GoogleLighthouseLog extends Model
{
    protected $table = "google_lighthouse_log";

    protected $casts = [
        'data' => 'array',
    ];
}

Service

Remember to add the GOOGLE_API_KEY variable in .env file, which stores the Google API key generated at the beginning of the article.

<?php

namespace App\Service\GoogleLighthouse;


use Exception;
use Illuminate\Support\Facades\Http;

class GoogleLighthouse
{
    public const CATEGORIES = [
        'PERFORMANCE',
        'ACCESSIBILITY',
        'BEST_PRACTICES',
        'PERFORMANCE',
        'PWA',
        'SEO',
    ];

    /**
     * @throws Exception
     */
    public function get(string $url, string $strategy, array $categories = []): ?array
    {
        $categories = !empty($categories) ? : self::CATEGORIES;
        $categoriesQuery = "";
        foreach ($categories as $category) {
            $categoriesQuery .= "&category=".$category;
        }

        $key = env('GOOGLE_API_KEY');
        $baseUrl = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed";
        $baseUrl = $baseUrl."?key=$key";
        $baseUrl = $baseUrl."&url=$url";
        $baseUrl = $baseUrl."&strategy=$strategy";
        $baseUrl = $baseUrl.$categoriesQuery;

        $response = Http::timeout(120)->get($baseUrl);

        if ($response->status() === 200) {
            $data = data_get($response->json(), 'lighthouseResult.categories', []);
            $result = [];
            foreach ($data as $categoryName => $categoryData) {
                $result[$categoryName] = $categoryData;
            }

            return $result;
        } else {
            throw new Exception($response->body());
        }
    }
}

Command

Here you have to replace $sitemapUrl with your own url.

<?php

namespace App\Console\Commands;

use App\Models\GoogleLighthouseLog;
use App\Service\GoogleLighthouse\GoogleLighthouse;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;

class GoogleLighthouseReport extends Command
{
    protected $signature = 'mevelix:lighthouse-report';

    protected $description = 'Generate lighthouse stats for each page';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle(): int
    {
        $start = now();
        $log = new GoogleLighthouseLog();
        try {
            $data = $this->generateStats();
            $log->data = $data;
            $log->has_error = false;
            $log->duration_seconds = $start->diffInSeconds(now());
        } catch (Exception $exception) {
            $log->has_error = true;
            $log->message = $exception->getMessage();
            $log->duration_seconds = $start->diffInSeconds(now());
        }
        $log->save();

        return 0;
    }

    private function generateStats(): ?array
    {
        $sitemapUrl = "https://mevelix.com/sitemap.xml";
        $sitemap = Http::get($sitemapUrl);
        $xmlSitemap = simplexml_load_string($sitemap->body());
        $result = [];
        foreach($xmlSitemap as $urlData){
            $url = (string)$urlData->loc;
            if ($sitemapUrl === $url) {
                continue;
            }

            $result[$url] = [
                'mobile' => [],
                'desktop' => [],
            ];

            $googleLighthouseService = new GoogleLighthouse();
            $resultDesktop = $googleLighthouseService->get($url, 'desktop');
            $resultMobile = $googleLighthouseService->get($url, 'mobile');

            foreach ($resultDesktop as $category => $item) {
                $result[]['desktop'][$category] = data_get($item, 'score');
            }

            foreach ($resultMobile as $category => $item) {
                $result[$url]['mobile'][$category] = data_get($item, 'score');
            }
        }

        return $result;
    }
}

This report will generate an output like below (this is just one item)

{
  "https://mevelix.com/articles/laravel-cqrs-from-scratch,1": {
    "mobile": {
      "performance": 0.9,
      "accessibility": 1,
      "best-practices": 1,
      "seo": 1,
      "pwa": 0.33
    },
    "desktop": {
      "performance": 0.99,
      "accessibility": 1,
      "best-practices": 1,
      "seo": 1,
      "pwa": 0.25
    }
  }
}

How to run it?

To generate this report, simply run the command using php artisan mevelix:lighthouse-report . It will generate a report and save the result to our table in the database.  You can also create an entry in cronjob or scheduler or k8s`s job (depends on your setup).

You can find a live example, which shows all the pages available in my sitemap.

So why is it worth generating this?

Well, there are various ways to generate a Lighthouse report (Chrome, Github Actions, or any CI/CD step). In this case, we can be sure that our sitemap is being tested on a production environment (where we have optimized HTTP servers, caching, image optimizations, etc).

I hope that this post will help you have fun with PageSpeed / Lighthouse, regards!

Disclaimer: The opinions expressed here are my own and do not necessarily represent those of current or past employers.
Comments (0)