A delightful thing happens when you score 100 across the board in Google Chrome’s Lighthouse–you get animated confetti!
Early in the summer of 2024, I set about refactoring this website with a few goals in mind. I wanted robust content management; I wanted it to be fast, ideally scoring 100 in Lighthouse and PageSpeed Insights; and I wanted it to be secure.
Early Versions
There have been several iterations of my professional website over the years, mostly built on top of WordPress using a theme that I developed from scratch. In 2021, though, I wanted to rebuild primarily for speed. I was frustrated with the server-side rendering and slow reaction time of WordPress, so I used Vue.js to develop a flat, easily-hosted, compact website. I also wanted to develop the site using an automated CI/CD release process and decided on AWS Amplify for builds and hosting. I managed site changes in GitHub, and any code pushes to the origin triggered the site’s build process in Amplify. Amplify is backed by AWS CloudFront, so the build is quickly distributed to a global CDN. But there were still two things I didn’t love: content was being handled directly in the site code, and I couldn't quite get the 100 Lighthouse score I was after.
Moving to Astro and Directus
For my 2024 rebuild, I started by looking at the current crop of website libraries and frameworks. I found Astro pretty early in my search and was enticed by two things: its focus on being content-driven, and its output of flat HTML distribution files. I've never been fully onboard with the idea that you need JavaScript to inject content into your page before a user can see or interact with anything, so HTML output is much preferred. Astro has a lot of great features including dead-simple routing, straight-forward templating, easy component management and property propagation, powerful image handling, and a lot more.

Managing Content
The biggest challenge I had with the conversion was how to manage data. I briefly experimented with headless WordPress and was immediately frustrated with its very prescriptive data model and information architecture. Querying custom fields, custom post types, the menu system, etc., was going to be very cumbersome and unappealing. So I started searching for other headless CMSs and found Directus.

Directus has a completely unopiniated data model, which is awesome. This meant I could create collections and data fields based on my existing site design and fetch content in a way that matched my page layout. Once I had a pretty solid idea of how my data would be structured, I set up my application environment in AWS and an online content management system was born.
Connecting to the CMS
With the Directus all set up, connecting Astro to my content was easy using the Directus SDK. It took some experimentation to find the best front-end/back-end strategy, but I landed on a technique that allowed me to simplify my queries at the Astro component level, rather than creating complexing, nested requests for all the data related to any one page. For example, on my site’s homepage, the hero section has its own data collection and a corresponding front-end component.

Directus backend showing the edit screen for the homepage hero section
---import directus from "../../lib/directus-sdk"const pageRequest = await directus.request( readItems('home_hero_section', { fields: ['content'] }))const pageData = pageRequestconst page_content = pageData.content---<section id="hero-section"> <div id="hero-background-image" style={`background-image: url(${heroBkgOptimized.src})`}></div> <div class="hero-content"> <div class="container row name-row"> <div class="column" set:html={page_content}></div> </div> </div> <div class="hero-border-bottom"></div></section>
Astro component showing the SDK data fetch and the HTML output (hero image request not shown)
Image Speed
One thing that Lighthouse will consistently ding you for is image format. It really wants you to use next-generation formats like webp and will decrease your score if you do not.
I manage many WordPress sites, and this is extremely tricky to do in WP. It involves plugins to compress the images, and some sort of mechanism to filter and rewrite the img tags throughout your site (often resulting in messy HTML, if it works at all). Outside of WordPress, manually converting all your images to webp is also a bit of a pain, especially for existing assets. With Astro, though, you simply add an attribute to your Image tag or import statement, and that’s all there is to it. The Astro build process will pull in the source images, run the compression, and generate properly structured image tags with next-generation image formats.
import heroBackground from "../../images/hero-bkg-abstract.jpg"const heroBkgOptimized = await getImage({ src: heroBackground, format: "webp", quality: 75, width: 1024, height: 576})
Image request inside Astro front matter
Security
With site content completely decoupled from the content management system, the site is completely secure. There’s simply no vector for attack–no plugins or themes to exploit, no PHP versions to manage, no script injection, nothing–just flat HTML files on a CloudFront distribution. One thing that Lighthouse enforces is a Content Security Policy for any scripts, styles, images, and data being loaded into the page. This strictly defines where everything is coming from and adds an even greater level of security.
The site does have a contact form that uses a third-party form processor (fieldgoal.io). The form uses a honeypot technique to discourage bots along with SPAM filtering by the form provider. So far, my inbox has been clear of unwanted emails.
Would I use this setup for clients? Absolutely! I might not self-host Directus now that there's a reasonably priced hosting plan. But the overall strategy of completely customizable, isolated content management and highly efficient HTML output is a huge win for speed and security. And who doesn’t want all the confetti they can get!