A Lighthouse performance score below 70 is not unusual for sites that have grown organically — adding third-party scripts, larger images, and more features over time without dedicated performance work. The good news is that most sites have a handful of specific, fixable issues that account for the majority of the score deficit.
This guide covers five changes that reliably improve Lighthouse scores with minimal implementation effort. Each applies to a wide range of sites regardless of tech stack, delivers measurable impact quickly, and can be implemented in a few hours.
After reading this, export your full Lighthouse JSON from PageSpeed Exporter and feed it to an AI agent to get a site-specific prioritized list — these five wins are a good starting point, but your data will show you exactly which ones apply to your site and in what order to tackle them.
Why These Five?
Lighthouse's performance score is a weighted average of six metrics: FCP (10%), Speed Index (10%), LCP (25%), TBT (30%), CLS (15%), and TTI (10%). TBT and LCP together account for 55% of the score — fixes that reduce either of these have the highest scorecard impact.
The five wins below target the most common causes of poor TBT and LCP across real-world sites.
Win 1: Add fetchpriority="high" to Your Largest Contentful Paint Image
Impact on LCP: High
Implementation effort: 10 minutes
Risk: Very low
The Largest Contentful Paint element is almost always an or a CSS background image on the hero area of your page. The browser does not know this image is critical until it parses the HTML and discovers it — which means it may load it with normal priority, after other less-important resources.
Adding fetchpriority="high" tells the browser to prioritize this image in the resource loading queue, before stylesheets and scripts that have not yet been rendered.
<!-- Find your LCP image (usually the hero or first large image on the page) -->
<img
src="/hero.jpg"
alt="Hero"
width="1200"
height="630"
fetchpriority="high"
loading="eager"
>
Also add a preload hint in the :
<head>
<link rel="preload" as="image" href="/hero.jpg" fetchpriority="high">
</head>
Next.js ( component):
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={630}
priority // This sets fetchpriority="high" and adds a preload tag automatically
/>
What to avoid: Do not add fetchpriority="high" to multiple images. Only the LCP element benefits — applying it broadly dilutes the browser's prioritization.
How to find your LCP element
Run an audit in PageSpeed Exporter and look at the largest-contentful-paint-element audit in the results. It will identify the exact element causing your LCP render time.
Win 2: Defer Non-Critical Third-Party Scripts
Impact on TBT: High Implementation effort: 30–60 minutes Risk: Medium — test on staging firstTotal Blocking Time measures how much the main thread is blocked by JavaScript. Every script that executes during page load competes for the main thread and delays user interaction.
Third-party scripts — analytics, chat widgets, marketing pixels, A/B testing tools — are the most common culprits. They are added incrementally over time, and collectively they often block the main thread for hundreds or thousands of milliseconds.
Strategy: Any script that does not need to run before the user can interact with your page should bedeferred or loaded asynchronously.
Plain HTML:
<!-- Before: blocking -->
<script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"></script>
<!-- After: defer until after HTML parsing -->
<script defer src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"></script>
For scripts that should only load after the page is interactive:
<script>
window.addEventListener('load', function() {
// Load Intercom, HubSpot, or other non-critical scripts here
var s = document.createElement('script');
s.src = 'https://widget.intercom.io/widget/YOUR_APP_ID';
document.head.appendChild(s);
});
</script>
Next.js component strategies:
import Script from 'next/script';
{/* strategy="afterInteractive" — loads after hydration, before user interaction */}
<Script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX" strategy="afterInteractive" />
{/* strategy="lazyOnload" — loads during browser idle time, lowest priority */}
<Script src="https://widget.intercom.io/widget/APP_ID" strategy="lazyOnload" />
WordPress:
// functions.php — defer specific registered scripts
function defer_non_critical_scripts($tag, $handle, $src) {
$handles_to_defer = ['google-tag-manager', 'facebook-pixel', 'hotjar'];
if (in_array($handle, $handles_to_defer)) {
return str_replace(' src=', ' defer src=', $tag);
}
return $tag;
}
add_filter('script_loader_tag', 'defer_non_critical_scripts', 10, 3);
Identify which scripts are causing TBT: Look for the third-party-summary and bootup-time audits in your Lighthouse report. They list every script's main thread blocking time in milliseconds, sorted by impact.
Win 3: Serve Images in WebP Format
Impact on LCP, Speed Index: Medium–High Implementation effort: 2–4 hours (mostly asset generation) Risk: Very lowJPEG and PNG images are typically 30–80% larger than equivalent WebP images at the same visual quality. For most sites, image bytes are the single largest waste identified by Lighthouse — and the uses-webp-images and uses-optimized-images audits typically show estimated savings of 1–5 seconds of load time.
WebP is supported by all modern browsers (Chrome, Safari 14+, Firefox, Edge).
HTML — Use for progressive enhancement:
<picture>
<source srcset="/hero.webp" type="image/webp">
<source srcset="/hero.jpg" type="image/jpeg">
<img src="/hero.jpg" alt="Hero" width="1200" height="630" loading="lazy">
</picture>
Next.js — Automatic WebP via :
import Image from 'next/image';
// Next.js automatically converts to WebP and serves via the /api/image endpoint
<Image src="/hero.jpg" alt="Hero" width={1200} height={630} />
Converting existing images to WebP (command line):
# Install cwebp (macOS)
brew install webp
# Convert a single image
cwebp -q 85 hero.jpg -o hero.webp
# Batch convert all JPEGs in a directory
for file in *.jpg; do
cwebp -q 85 "$file" -o "${file%.jpg}.webp"
done
WordPress — Automatic WebP:
Install a plugin like Imagify, ShortPixel, or WebP Express. These handle batch conversion of existing images and automatic WebP serving to supported browsers.
Shopify:{# Shopify image URL transform — serve WebP with width and format parameters #}
<img src="{{ image | image_url: width: 1200, format: 'webp' }}" alt="{{ image.alt }}">
Win 4: Add Explicit width and height Attributes to All Images
Impact on CLS: High
Implementation effort: 1–3 hours
Risk: Very low (purely additive HTML attributes)
Cumulative Layout Shift (CLS) measures how much page elements shift after they initially render. Images without explicit dimensions cause significant layout shifts because the browser does not know how much space to reserve for them before they load.
When the browser encounters without 
width and height, it allocates zero space. When the image loads, everything below it shifts down — causing a high CLS score and poor user experience.
<!-- Before -->
<img src="/product.jpg" alt="Product">
<!-- After: declare dimensions matching the rendered size -->
<img src="/product.jpg" alt="Product" width="800" height="600">
The width and height values should match the intrinsic dimensions of the image file, not the CSS display size. CSS handles the responsive sizing; the attributes give the browser the aspect ratio it needs to pre-allocate space.
width: 100%):
<!-- Declare intrinsic dimensions even if CSS overrides display size -->
<img
src="/product.jpg"
alt="Product"
width="800"
height="600"
style="width: 100%; height: auto;"
>
For dynamically sized images (React/Next.js):
// Always provide width and height when using <img>
<img
src={product.imageUrl}
alt={product.name}
width={product.imageWidth}
height={product.imageHeight}
/>
// Or use Next.js <Image> which enforces dimensions
import Image from 'next/image';
<Image src={product.imageUrl} alt={product.name} width={800} height={600} />
Audit for missing dimensions: The unsized-images Lighthouse audit identifies every image missing explicit dimensions. Check this audit in your PageSpeed Exporter results — it lists every offending element.
Win 5: Inline Critical CSS and Load Non-Critical CSS Asynchronously
Impact on FCP, LCP: Medium–High Implementation effort: 4–8 hours for manual implementation; minutes with tooling Risk: Medium — incorrect critical CSS extraction can break layoutRender-blocking CSS is one of the most common causes of poor FCP and LCP. By default, the browser must download, parse, and apply all stylesheets before it renders anything visible. If your main stylesheet is 200KB (common on sites using utility frameworks like Bootstrap or Tailwind without purging), it delays first paint by hundreds of milliseconds.
The approach:- Extract the CSS rules needed to render above-the-fold content ("critical CSS")
- Inline them directly in
so they are available immediately - Load the rest of the stylesheet asynchronously after the page is visible
<head>
<!-- Critical CSS inlined directly -->
<style>
/* Only include styles needed for above-the-fold content */
body { margin: 0; font-family: system-ui, sans-serif; background: #fff; }
.header { display: flex; align-items: center; padding: 16px; }
.hero { width: 100%; aspect-ratio: 16/9; background: #f0f0f0; }
h1 { font-size: 2rem; font-weight: 700; margin: 0; }
</style>
<!-- Load full stylesheet asynchronously -->
<link
rel="preload"
href="/styles.css"
as="style"
onload="this.rel='stylesheet'"
>
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
</head>
Automated approach using critical (Node.js):
npm install -g critical
critical https://example.com \
--base=./dist \
--target=index.html \
--inline \
--width=1300 \
--height=900
The critical package automatically extracts and inlines the CSS needed for the above-the-fold viewport and updates the HTML file.
tailwindcss --purge (production mode) or PurgeCSS to eliminate unused utility classes from the bundle.
WordPress: Use the "Autoptimize" plugin, which handles critical CSS extraction, CSS concatenation, and async loading automatically.
A note on Tailwind CSS
If your site uses Tailwind and you see a large unused-css-rules warning in Lighthouse, ensure your tailwind.config.js includes your content paths:
// tailwind.config.js
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}',
],
// ...
};
Without this, Tailwind generates the full CSS utility set (~3MB) instead of only the classes you actually use.
How to Find Your Biggest Wins First
These five improvements cover the most common issues across real-world sites, but they are not equally valuable for every site. Your specific Lighthouse report will tell you which ones apply and in what order to tackle them.
The fastest way to get a prioritized list:
- Go to speedexporter.com and run an audit
- Click Download JSON to get the AI-ready report
- Upload it to ChatGPT or Claude with this prompt:
I'm attaching my Lighthouse JSON performance report.
Identify the top 5 opportunities with the highest estimated ms savings.
For each, provide the exact code fix needed.
Sort by effort-to-impact ratio — easiest, highest-impact fixes first.
I'm using [your framework].
The AI agent will return a prioritized list specific to your actual site data — which issues exist, in what order to fix them, and what the code should look like for your specific framework and setup.
Tracking Improvement
After implementing each fix, re-scan your URL in PageSpeed Exporter. If you are on the Starter or Pro plan, the before/after comparison shows:
- The delta in each Core Web Vitals metric
- The change in performance score
- Which specific audits moved from failing to passing
Iterate: fix the top issue, re-scan, fix the next. For most sites, implementing these five wins moves the performance score from the 50–70 range into the 80–90 range in a single session.