Most advice on how to improve website speed is a list of tips with no numbers attached. This is the other kind: one real site, every page measured, and what each change did.
We rebuilt digitalstorming.com in 2026 as a static site with Astro, Tailwind CSS and a small amount of JavaScript for smooth scrolling, menus and forms. It went live on October 9, 2026, served from our own Apache server with HTTP/2 and gzip. Then we tested every page and fixed what the tests found.
Below is what moved the score, what fooled us, and where we stopped short of 100. The last big section turns each lesson into steps you can apply on WordPress, Shopify or anything else.
Two things up front. We didn’t change the design to chase the score, lower photo quality, or remove our web font. And not every page reached 100 on a phone. Four didn’t, and we’ll show you why.
How we tested
We ran Lighthouse 13.5 on every page we could test, which was 59 pages (Lighthouse can’t test a 404 page), with both the phone and desktop profiles. Lighthouse 13 also adds an “Agentic Browsing” category, scored as a fraction of the checks that apply: an accessibility tree an AI agent can use, layout stability, and an llms.txt file.
Speed work can quietly break a layout, so we checked that too. Before and after the changes, a script compared every element’s position and size, plus a full-page screenshot, on all 60 pages at 390 px and 1440 px wide. The result: no layout changes anywhere. Pixel differences were under 0.003%, which is image-codec rounding.
What does a Lighthouse score actually measure?
Short answer: Lighthouse’s performance score is a weighted mix of five lab metrics: Total Blocking Time (30%), Largest Contentful Paint (25%), Cumulative Layout Shift (25%), First Contentful Paint (10%) and Speed Index (10%). A score of 90 to 100 counts as good.
Those weights come from Chrome’s Lighthouse scoring documentation, and they tell you where to look first. Total Blocking Time is the biggest single slice: the time the page is too busy running scripts to respond. Largest Contentful Paint is when the main content, usually the headline or hero image, appears.
Lighthouse is a lab test. By default it simulates a slow connection (150 ms of latency and 1.6 Mbps download) and a 4x CPU slowdown to imitate a mid-range phone. Real visitors are measured separately: Google’s Core Web Vitals count a page as good when, at the 75th percentile of visits, LCP is 2.5 seconds or less, Interaction to Next Paint is 200 ms or less and CLS is 0.1 or less. PageSpeed Insights shows that field data, from the previous 28 days of Chrome users, above its lab score when your site has enough traffic.
Lesson 1: check the test before you trust the score
Our first full run showed some pages at 97 to 98 with a Speed Index around 3.7 seconds, while others scored 100. It looked like a real problem on certain pages.
The cause was our test script, not the site. On Windows, it launched Lighthouse through a shell that split the Chrome flags into separate arguments, so Chrome lost the flags that stop background-window throttling. Frames were then randomly delayed by about 2 seconds. Once the script passed the flags correctly, the same pages painted in about 0.4 seconds every time.
The takeaway is to doubt a surprising result before you act on it. Run Lighthouse several times, compare its “observed” and “simulated” numbers, and check the page in PageSpeed Insights, which runs on Google’s own machines.
Tip: Lighthouse’s own variability guide says the median score of 5 runs is twice as stable as a single run. Run one test at a time, close other heavy programs, and use a browser profile without extensions.
Images: from WebP to AVIF without losing quality
Next came the photos. We converted all 424 stock photo files, 212 photos at 1600 and 800 px wide, to AVIF. We converted from the original downloads, not from our WebP files, so nothing was compressed twice.
The quality rule was strict. Each AVIF was kept only if it was smaller and at least as close to the original as the WebP. “Close” here means SSIM, a measure of visual similarity, scored against the original. All 424 passed, and the set went from 32.0 MB to 21.4 MB, 33% smaller.
Logos and blog screenshots got the same check at build time: 199 AVIF copies, from 3.15 MB to 2.2 MB, or 30% smaller. Five files were left as they were because AVIF wasn’t at least 10% smaller. Animated images are skipped entirely: converting an animated WebP to AVIF kept only its first frame, and the visual comparison caught it.
Delivery uses a <picture> element with an AVIF <source> and the WebP <img> as the fallback, so browsers without AVIF get WebP. That’s the pattern MDN recommends, since AVIF support only arrived in Safari 16.1 and Edge 121. On the phone profile, the homepage’s total transfer fell from 296 KB to 214 KB.
Watch out: Wrapping an image in
<picture>can change a flex layout. In our photo strip, each<source>became an empty flex item and added a 20 px gap, which the visual check caught. The fix waspicture { display: contents }pluspicture > source { display: none }.

The main thread: stop measuring the page while it loads
Total Blocking Time carries the most weight in the score, and on our pages the main-thread problem was forced layouts. When a script asks for a size or scroll position, the browser has to calculate the page’s layout right then, instead of once at its normal time. Google’s guide to layout thrashing explains the mechanics.
We found three versions of this:
- Measuring during load. A header script read
scrollYthe moment it loaded, which forced Chrome to lay out the whole homepage before the first paint, about 131 ms of work. Wide tables were also measured while the page was still being parsed, costing up to about 170 ms on a long guide. The fix: start at 0 and measure only after the first paint, usingrequestAnimationFrameplussetTimeout. - Libraries measuring at start-up. The smooth-scroll library and two carousels read sizes as soon as they started. They now start after the first paint. The carousels’ “previous” buttons start disabled in the HTML, so the first frame is already correct.
- The result. Homepage Total Blocking Time on the phone profile fell from about 230 ms to under 60 ms in our runs.
Largest Contentful Paint: keep decoration out of the way
Our LCP fixes were all about things that downloaded or rendered before the headline painted. Three changes mattered:
- Gallery photos below the headline. On the homepage, the photos under the headline were set to load immediately, so on a phone they downloaded before the headline painted. That included cards hidden on phones, which still downloaded. Making them lazy-load cut the photos fetched before the headline from 5 (147 KB) to 3 (71 KB), and Lighthouse’s LCP estimate fell from about 2.0 s to 1.5 s.
- Decorative photo columns on landing pages. These purely decorative photos drift upward next to the headline. They now load just after the first paint, and phones get a 400 px copy instead of the 800 px one, about 13 KB instead of 26 KB as AVIF. LCP estimates on those pages fell from 2.0 to 2.2 s to between 1.2 and 1.7 s.
- Long articles. Paragraphs and headings far below the screen use
content-visibility: auto, which lets the browser skip laying them out until they come near the screen. That cut the first layout of a very long blog post from about 55 ms to about 22 ms.
Caching: the rule that never matched
We had an Apache rule meant to cache the site’s hashed scripts for a year. It never matched. FilesMatch sees only file names, not folders, as the Apache documentation describes, so the rule never applied and the scripts were never cached.
The fix was <LocationMatch "^/_astro/"> with Cache-Control: public, max-age=31536000, immutable, which is what Google’s HTTP cache guide recommends for files whose names change whenever their content does. We also added .avif to the 30-day image cache rule, since the new format wasn’t covered.
Did every page score 100?
Short answer: On desktop, yes: 100 in performance, accessibility and best practices on all 59 testable pages. On a phone, performance reached 100 on 55 of 59 pages; four pages scored between 96 and 98.
Here are the full results from local Lighthouse 13.5 runs on October 12, 2026:
| Profile | Performance | Accessibility and best practices | SEO |
|---|---|---|---|
| Desktop | 100 on all 59 pages | 100 on all 59 pages | 100 on every page meant for search |
| Phone | 100 on 55 of 59 pages | 100 on every page meant for search | 100 on every page meant for search |
Agentic Browsing scored 3/3 on every page, on both the phone and desktop profiles. Pages we deliberately mark noindex, such as internal and utility pages, score lower on SEO because Lighthouse flags noindex. That’s by design.
Of the four phone pages at 96 to 98, two have native dropdowns (<select>), one of them with 11, and one is a very long article. On our Windows test machine, the first layout of a native <select> cost about 90 ms, even when empty, and text laid out before the web font arrives costs extra too. We haven’t confirmed how much of this shows up on PageSpeed Insights’ Linux machines.
Our take: A 97 with working native dropdowns and a readable web font beats a 100 without them. Taking those out would make the pages worse for people to gain a point or two, and Google’s own page experience guidance says that trying to get a perfect score just for SEO reasons “may not be the best use of your time.”
How to improve website speed on your own site
None of this needs a static site. The same ideas work on WordPress, Shopify and most builders, usually through settings, a theme fix or a plugin rather than a rebuild. Work through them in this order, because the first steps usually pay the most.
Step 1: Measure with real-visitor data first
Open PageSpeed Insights, enter your homepage and your busiest page, and look at the field data at the top before the lab score. If it’s missing, your site doesn’t have enough Chrome traffic yet, so rely on the lab test. Run Lighthouse five times and use the median. This takes about 15 minutes and costs nothing.
Step 2: Get your LCP element loading first
Find the LCP element in the Lighthouse report. If it’s an image, it must not be lazy-loaded; Google’s lazy-loading guide is blunt about that. Add fetchpriority="high" to it, which current Chrome, Edge, Firefox and Safari all support; Google cites a Google Flights test where it improved LCP from 2.6 s to 1.9 s.
- WordPress: since 6.3, WordPress adds
fetchpriority="high"to the image it judges most likely to be the LCP and lazy-loads images further down. Check that a slider or page builder hasn’t overridden it. - Shopify: the
image_tagfilter loads images eagerly in sections 1 to 3. Shopify’s theme guidance says to never lazy-load the LCP image, and that includes JavaScript lazy loaders.
Step 3: Lazy-load everything below the first screen
Add loading="lazy" to images further down the page. Watch for images hidden on phones, such as desktop-only cards: hidden doesn’t mean skipped, and ours were still downloading.
Step 4: Serve AVIF or WebP at the size the screen needs
According to MDN, lossy AVIF files are around 50% smaller than JPEGs and lossy WebP 25 to 35% smaller at similar quality. Size matters as much as format, so phones should get a smaller copy.
- WordPress: since 6.5 you can upload AVIF if your server’s image library supports it; check under Tools > Site Health > Info > Media Handling. Core doesn’t convert existing images or pick a format per browser, so use a plugin or image CDN for that. The WordPress Performance Team’s Performance Lab includes a Modern Image Formats feature, though the team calls it a beta-testing plugin.
- Shopify: the CDN picks the format each browser supports, as long as your theme builds image links with the
image_urlfilter. Hand-built image URLs skip that.
Step 5: Reserve space so nothing jumps
Give every image and video width and height attributes, reserve space for embeds and ads, and keep late banners from pushing content down. Google’s CLS guide also covers web fonts: a fallback font with matching metrics stops text from reflowing when the real font arrives.
Step 6: Keep scripts from blocking the page
Chat widgets, sliders, trackers and tag managers are the usual culprits. Remove what you don’t use and load the rest after the page appears. Chrome DevTools’ Performance panel has a Forced reflow insight that names the functions measuring the page too early. On a phone profile, Lighthouse counts 0 to 200 ms of Total Blocking Time as fast.
Step 7: Cache static files for a year
Open DevTools, go to the Network tab, click a script or image, and read its Cache-Control header. Files with a version or hash in the name can be cached for a year with immutable; HTML should use no-cache so visitors get updates. Don’t assume a caching rule works because it’s in the config; ours didn’t match until we checked the headers. A good host, or a plugin on WordPress, can set these for you.
Step 8: Let the browser skip work on long pages
For long articles and guides, content-visibility: auto with a contain-intrinsic-size lets the browser skip rendering what’s far off-screen. Google’s content-visibility article notes it reached all three major browser engines in September 2025.
What to do this week
- Run PageSpeed Insights on your homepage and busiest page, and write down the field data and the LCP element.
- Make sure your LCP image isn’t lazy-loaded and has
fetchpriority="high". - Lazy-load everything below the first screen, including images hidden on phones.
- Check the caching headers on your scripts and images in DevTools.
- List every third-party script on the site and remove one you don’t need.
Speed sits alongside the other basics we covered in our website accessibility checklist and our guide to what a small business website costs. If you’re choosing a store platform, our Shopify vs WooCommerce comparison covers speed too. If you’d rather hand it off, our web development team builds and tunes sites, and our web hosting page lists the plans we run.


Question about this article?
Send it to our team and we’ll reply by email within one business day. Questions aren’t published on this page.