How we achieved Google Lighthouse score of 99/100?

How we achieved google Lighthouse score of 99/100 using Astro; a new static site generator

Sat Mar 12 2022

How we achieved Google Lighthouse score of 99/100?

Our main website lightrains.com is 5 years old. We believe 5 years is a good enough time to rethink about redesign and some other improvements in terms of framework and technology. The old site was built in Jekyll; our all time favorite until we came across Next.js and Astro thereafter.

The story

We had almost 70 blog posts among the last 5 years. Yes we were not so blog-ish about what we do. However every member in our team spend enough time writing/optimizing/educating. That being said, last few years was astonishing in terms of growth as well as improvements we adapted in our development and other activities.

Challenges

When we start thinking about redoing the website we had a few things in mind. The main one was the SEO score and page structure.

  1. Overall page links and SEO score
  2. Pagespeed score
  3. Google Lighthouse score of 88/100
  4. Jekyll has this less/no-javascript approach which we loved a lot
  5. Migrating all those plain HTML Templates
  6. Migrate 70+ blog posts
  7. Migrate content pages and keep url structure
  8. Increase pagespeed and Lighthouse score
  9. Increase meta tag adaptability

Potential candidates

We have evaluated a few existing blogging/static site builder to see how developer friendly and effective the migration process would be. We have considered

Among these strong candidates we had a lineage towards Next.js because Next.js is one of the favorite framework among our team. Almost all of our team members had worked on NextJS in one point or the other. During this research phase we read a lot pros and cons of this and that; there are quite a lot of javascript static site builders out there.

And then we read this article by Strapi about How to create a static site with Strapi and Astro. We got curious about Astro in the very first minute. Spent a few more hours in the documentation and we fully bought into the way Astro implemented Partial Hydration. And voila Astro is fixed.

Astro sites can be up to 40% faster and use 90% less JavaScript compared to leading static site generators.

Step 1: Writing the migration scripts

Writing all the content pages and blog articles and job posts and case studies into new Astro boilerplate which I have copied from @withastro/astro/tree/latest/examples. We had to consider a few points from migrating Jekyll Frontmatter to Astrojs-like Frontmatter. We achieved this using @jxson/front-matter smooth as butter.

---
title: Just hack'n
description: Nothing to see here
---

This is some text about some stuff that happened sometime ago

With a few lines of javascript code like the following; we managed to get the required formatting for Astro.

var fs = require('fs'),
  fm = require('front-matter')
fs.readFile('./example.md', 'utf8', function (err, data) {
  if (err) throw err
  var content = fm(data)
  console.log(content)
})

And a few formatting was required as we heavily used categories and tags in our content

Step 2: Creating other components

We use React.js heavily in our projects we have decided to use little bit of React and Vanilla js to see how Astro would accommodate it. So obviously we built our responsive menu component in React. Following is a trimmed down version of the menu we hae used in this website.

// src/components/BaseMenu.jsx
import React, { useState } from 'react'
import Logo from './Logo.jsx'
const menu = [
  { title: 'About', path: '/about/lightrains/' },
  { title: 'Services', path: '/consulting/' },
  { title: 'Research', path: '/about/research/' },
  { title: 'Blogs', path: '/blogs/' },
  { title: 'Get a Quote', path: '/requirement', as: 'button' }
  // { title: 'Get a Quote', path: '/get-a-quote/', as: 'button' }
]
const BaseMenu = ({ currentPage = '' }) => {
  const [isOpen, setIsOpen] = useState(false)
  return (
    <nav className="container mb-10">
      <div className="flex flex-row justify-between items-center py-6 mx-auto">
        <a href="/">
          <Logo />
        </a>
        <div className="hidden md:flex flex-row items-center space-x-7 font-medium">
          {menu.map((item, index) => {
            return (
              <a key={index} className='cursor-pointer hover:text-brand hover:underline' href={item.path}>
                {item.title}
              </a>
            )
          })}
        </div>
        <div className="md:hidden">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 24 24"
            width="32"
            height="32"
            className="cursor-pointer"
            onClick={() => setIsOpen(!isOpen)}>
            <path fill="none" d="M0 0h24v24H0z" />
            <path
              d="M3 4h18v2H3V4zm6 7h12v2H9v-2zm-6 7h18v2H3v-2z"
              fill="#000"
              fillRule="nonzero"
            />
          </svg>

          {isOpen && (
            <div
              className="origin-top-right absolute right-0 w-full bg-black focus:outline-none top-0 animate-fade-in-down"
              role="menu"
              aria-orientation="vertical"
              aria-labelledby="menu-button"
              tabIndex="-1">
              <div className="flex flex-row justify-between items-center px-5 py-6 container">
                <Logo />
                <svg
                  onClick={() => setIsOpen(!isOpen)}
                  xmlns="http://www.w3.org/2000/svg"
                  viewBox="0 0 24 24"
                  className="cursor-pointer"
                  width="36"
                  height="36">
                  <path fill="none" d="M0 0h24v24H0z" />
                  <path
                    d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"
                    fill="#FFF"
                    fillRule="nonzero"
                  />
                </svg>
              </div>

              <div
                className="p-6 flex flex-col space-y-4 text-white mb-8 container"
                role="none">
                {menu.map((item, index) => {
                  return (
                    <a
                      key={index}
                      className="cursor-pointer inline-block"
                      href={item.path}>
                      {item.title}
                    </a>
                  )
                })}
            </div>
          )}
        </div>
      </div>
    </nav>
  )
}
export default BaseMenu

And this BaseMenu.jsx React component is called in Astro Layout

---
import '../styles/global.css'
import BaseMenu from '../components/BaseMenu.jsx'
const { title, description, metaDesc = '' } = Astro.props
const url = new URL(Astro.request.url)

---

<html lang="en">
  <head>
    <BaseHead {title} {description} {metaDesc} />
  </head>
  <body class="antialiased font-sans bg-[#F5F5F7] relative">
    <BaseMenu client:load currentPage={url.pathname} />
    <main>
      <slot />
    </main>
  </body>
</html>

That was it; we just called our Reactjs component .jsx file in our .astro file. Also we have enabled the most compelling feature of astro using client: directive. We have called <BaseMenu client:load {currentPage} /> telling Astro to hydrate menu as soon as the page is loaded using client:load directive.

If this is some other component we could use other hydration directive given by astro. You can read more Hydrate Interactive Components

Step 3: Creating SEO Components

For SEO purposes we have not used any custom plugins as Astro ecosystem is not matured and not enough plugins are there. However we had built many Astro components as a part of developing this new website. We are planning to open source them in the near future.

And we slowly started migrating our whole pages and components into Astro site. After 20 days, 229 commits and 400+ Build minutes we are live lightrains.com

Results

Old website 87/100

  • Performance 87
  • Accessibility 80
  • Best Practices 100
  • SEO 98

New website 99/100

  • Performance 99
  • Accessibility 97
  • Best Practices 100
  • SEO 100
Astro Site

Honorable mentions

Other resources

Leave a comment

To make a comment, please send an e-mail using the button below. Your e-mail address won't be shared and will be deleted from our records after the comment is published. If you don't want your real name to be credited alongside your comment, please specify the name you would like to use. If you would like your name to link to a specific URL, please share that as well. Thank you.

Comment via email
Nikhil M
Nikhil M

Entrepreneur / Privacy Freak / Humanist / Blockchain / Ethereum / Elixir / Digital Security / Online Privacy

Tags Recent Blogs