How we built a website that scores 100 on Lighthouse
When tuwastudio.com launched on 19 September 2026, every page scored between 99 and 100 for performance on Lighthouse’s mobile test, and 100 for accessibility, best practices and SEO. The main content appeared in 1.2 to 1.8 seconds on a throttled phone connection, and nothing on any page moved while it loaded.
None of that came from a clever trick. It came from a handful of decisions made early, and from measuring after every change instead of at the end. This is what they were.
Start static
The site is built with Astro and generated once, at build time, into plain HTML files. When someone opens a page, the server has nothing to compute: it sends a file that already exists. There is no database to query and no application server to warm up, which also means there is very little to attack.
Most marketing sites can work this way. The pages change when you edit them, not every time someone visits, so building them on every request is wasted work.
Ship almost no JavaScript
The first version of the site used a React component for the mobile menu, the slide-out panel you open from the Menu button. It worked well. It also meant every page downloaded about 96 KB of compressed JavaScript before that menu could open, and the home page was stuck at 93 or 94.
Browsers now have a built-in way to do the same thing. The popover attribute opens and closes an element, closes it on Escape or a click outside, and handles keyboard focus, all without a line of script. Replacing the React menu with it removed JavaScript from five of the six pages entirely. Together with the next two changes, it took the home page from 94 to 98, and later to 100 once the live site was tuned.
The general rule: before adding a component library for a behaviour, check whether HTML already does it. Menus, dialogs, disclosures and accordions increasingly don’t need JavaScript at all.
Host your own fonts, one file per weight
Fonts are often the heaviest thing on a page that looks light. The first build loaded its display font as a single variable file that carried every weight and optical size. Four of those put about 175 KB of fonts in front of a 27 KB hero image.
We switched to self-hosted static files, one per weight, and only the weights the design actually uses. Astro’s font tools also generate a fallback font sized to match the real one, so text doesn’t jump when the web font arrives. That jump is layout shift, and Google counts it against you.
Inline the stylesheet
The whole stylesheet was about 9 KB. As a separate file, the browser had to request it and wait before drawing anything, which Lighthouse measured at around 150 milliseconds on a slow connection. Put straight into each page instead, it arrives with the HTML. For a small stylesheet this is almost always faster; for a large one it isn’t, so measure before copying this.
Give every image its size
Every image on the site is served as AVIF or WebP, in several widths so a phone doesn’t download a desktop-sized file, and with its width and height written into the HTML. That last part is what stops the page jumping as images load: the browser reserves the space before the picture arrives. Only the one image that is the main content of a page loads straight away. The rest wait until you scroll near them.
Check it on every build
A fast site can get slow again one small change at a time, so the checks run automatically. Before anything is published, a script reads every generated page and stops the release if it finds:
- a page marked to be hidden from search engines
- a wrong or missing canonical address
- a missing title or description, or a duplicate title
- an image without alt text or without its size
- a broken internal link
- an inline script that the site’s security policy would block
That last check matters more than it sounds. The site sends a strict content security policy, which lists every inline script by its hash. If a script ever changes without the policy changing with it, the browser silently refuses to run it. The check catches that before a visitor ever sees it.
Measure in production, not just locally
The site passed every check on our machines and then scored 93 for best practices once it was live. Our hosting provider was adding a script to every page as it was served, and our own security policy was blocking it. Local testing couldn’t have caught that, because the script only existed in production. We wrote up the settings that caused it.
What this means for your site
You don’t need to rebuild a site from scratch to get most of this. Oversized images, heavy fonts and scripts loaded for features nobody uses are the usual culprits, and they can usually be fixed in place. If your site is slow on a phone, that is exactly what our speed fix is for.