Skip to content
Websites

How to reach Lighthouse 99/100 on WordPress

Maciej Rostocki 10 min read Updated 2026-05-12
How to reach Lighthouse 99/100 on WordPress

A Lighthouse score of 99/100 on WordPress is achievable for a real business site with analytics, web fonts and form handling. A perfect 100/100 requires compromises most Hanse Studio clients will not accept: no third-party scripts, no web fonts, no advertising pixel. This runbook lists the exact steps we use at our studio and on several other client projects that have held 99/100 mobile for months.

Assumptions: WordPress 6.4+, Astra Pro as parent theme, custom child theme, LiteSpeed Cache 7.8+ and Cloudflare as CDN. The whole stack follows our no page builders philosophy, described in our services context. For clients still on Elementor or Divi, a parallel topic is the Elementor to Gutenberg migration runbook, because bloated builders rarely allow scores below 85/100.

How to reach Lighthouse 99/100 on WordPress: the full runbook

Lighthouse 99/100 is measured in mobile mode, on slow 4G, with CPU throttling on. Every point below 99 indicates a real problem Google also treats as a ranking signal. Our process moves from fundamentals (theme, hosting) through assets (CSS, JS, images) to caching and post-deploy monitoring. Each step has measurable effect and measurable cost.

Why 99/100 instead of 100/100

A perfect 100/100 requires giving up three things that are non-negotiable for business sites. The first is third-party analytics: GA4, Meta Pixel, Hotjar. Each costs 1 to 3 points in Best Practices and Performance, but without them you have no idea what users do. The second is web fonts: even self-hosted Manrope or Fraunces increases Time to First Byte and Largest Contentful Paint by tens of milliseconds. The third is third-party widgets: chat, Google Maps, YouTube embeds. Each is a separate domain lookup and a separate JS bundle.

Our internal benchmark holds 99/100 with GA4, Polylang 5 languages, two web fonts (Manrope, Fraunces) and Brevo SMTP for form delivery. This is a real production setup. By comparison we only reach 100/100 on static one-pagers without instrumentation, which is fewer than 5 percent of our portfolio.

Practical rule: 99/100 is the maximum under real business constraints, 100/100 is a portfolio project without instrumentation. For clients in the DACH market with annual budgets of 3000 to 15000 EUR we recommend 99/100 with full tracking, because without data you cannot optimise conversion.

Theme and child theme: the foundation

The theme choice decides 30 to 50 Lighthouse points before we touch any plugin. Astra Pro has a 5 KB CSS footprint after minification, GeneratePress similar, Kadence around 8 KB. For comparison Divi loads 80 KB of CSS and 60 KB of JS in a base setup, Avada 120 KB of CSS, Elementor alone is 200 KB plus 50 KB for each Pro widget. Multiplied by an average layout, that is easily 400 to 500 KB of redundant assets.

Hanse Studio decision: Astra Pro plus a custom child theme for every client. The child theme contains only what Astra lacks natively: custom Gutenberg blocks, brand colors in theme.json, and section patterns as block patterns. The whole custom CSS stays under 15 KB minified, custom JavaScript is plain vanilla (zero jQuery) and loaded with defer.

  • Astra Pro: 5 KB base CSS, 0 KB base JS (loaded conditionally)
  • Child theme: custom CSS max 15 KB minified, custom JS max 8 KB minified
  • theme.json for design tokens (color palette, spacing scale, typography ramps)
  • Block patterns instead of Elementor templates
  • Critical CSS inlined in head for above-the-fold (typically 4 to 8 KB)

We generate critical CSS in the build step, store it inside the child theme and inject it in the head section. The remaining stylesheets are deferred using standard CSS defer techniques. This pattern saves 200 to 300 ms of LCP on typical mobile.

Image optimisation checklist

Images are usually 60 to 80 percent of total page weight and directly drive LCP, the hardest Core Web Vital to keep green. Baseline rule: every image on the page should be WebP or AVIF, have width and height attributes set, ship an adequate srcset and be lazy loaded unless it is above the fold.

  • Format: WebP as default, AVIF for high-traffic sites (Cloudflare Polish or LSCache image WebP handles it automatically)
  • Responsive: srcset with 3 to 4 width variants (400w, 800w, 1280w, 1920w), precise sizes attribute
  • Width and height attributes: always present, removes Cumulative Layout Shift
  • Loading: loading="lazy" below the fold, loading="eager" plus fetchpriority="high" for the hero LCP image
  • Weight: after compression each image under 200 KB, hero under 150 KB
  • Decorative images: as CSS background or inline SVG (bypass the HTML parser priority queue)

Concrete example from our studio: the homepage hero image is a render saved as WebP 1920×1080, weight 145 KB, srcset with three variants. LSCache 7.8 generates WebP automatically for every PNG/JPG upload, plus Cloudflare Polish acts as a second optimisation layer. Mobile LCP time on slow 4G: 1.8 seconds, well below the 2.5 second threshold.

Most common mistake: the client uploads an 8 MB PNG as the hero, the auto-resize plugin saves the day but after the WP scaling pipeline the image still weighs 2 MB and blocks LCP. Procedure: ALWAYS compress the source file (Squoosh.app, ImageOptim) before WP upload, even if LSCache will reconvert anyway. Source files live in backups, disk space savings add up long term (see our 155 KB WebP versus 12 MB PNG practice for hero featured images).

JavaScript: minimise, defer, split

JavaScript is the second heaviest resource after images and costs points in Performance, TBT (Total Blocking Time) and INP (Interaction to Next Paint). Each KB of JS is potentially several milliseconds of parse time on lower-end phones. Target: under 50 KB minified JS without third party, under 150 KB total including GA4 and an optional chat widget.

  • Defer all non-critical scripts with the defer attribute, not async (async may change execution order)
  • Load on-demand widgets only when needed (e.g. Google Maps loaded on click, not on page load)
  • Audit jQuery: if the theme does not need it, deregister it from the child theme
  • Audit third party: any plugin loading its JS on every page is a red flag (Contact Form 7 loads its JS globally, FluentForm only when a form is on the page)
  • Self-host fonts: subset latin and latin-ext separately (a single .woff2 from Google Fonts is always latin only, Polish diacritics fall back)
  • Font-display swap for all custom fonts, removes FOIT (Flash of Invisible Text)

Self-hosted fonts trap: .woff2 files downloaded directly from fontsource v5 or Google Fonts are subset to latin only. Polish characters like ł, ż, ć and Czech ě, ř fall back to system fonts (Cambria, Consolas), causing a visible per-glyph mismatch. Fix: serve two files per font (NAME-latin.woff2 and NAME-latin-ext.woff2) with correct unicode-range in the CSS. We hit this bug twice in May 2026 on our own site.

First plugin to audit: Asset CleanUp Pro lets you selectively disable plugin CSS/JS on pages where they are not needed. In real life: Contact Form 7 loading on 100 pages while the form sits on only 2 is a typical 30 KB JS plus 40 KB CSS saving on 98 pages.

CSS: critical inline plus defer the rest

CSS must be minified, combined and split into critical (above-the-fold inline in head) plus the rest (deferred). Target: under 14 KB inline critical (fits the first TCP packet), the rest deferred with a noscript fallback for users with JS off.

  • Inline critical CSS in the head section, maximum 14 KB (single TCP packet limit with TLS overhead included)
  • The rest as an external file with standard deferred-loading technique (preload as style, swapped to stylesheet on onload)
  • Noscript fallback for JS-disabled users (rare but an a11y best practice)
  • PurgeCSS or Asset CleanUp to remove unused selectors (typically 60 to 70 percent of a plugin CSS is unused)
  • No @import in CSS, it blocks rendering
  • Font-display swap in every @font-face declaration

LiteSpeed Cache 7.8 has a built-in Generate Critical CSS option that works per page type. Enable it for home, single post, single page, archive, product (if WooCommerce). It will generate critical CSS for each type separately, store it in uploads/litespeed/ccss/ and serve it automatically. Generation time: 30 to 60 seconds per page type, once after deployment.

Caching: LiteSpeed plus Cloudflare combo

Caching is the single biggest lever for Lighthouse score after eliminating bloat in theme and plugins. LiteSpeed Cache 7.8 delivers page cache at the web server level (no PHP execution), object cache via Redis (DB queries skipped), CSS/JS minify and combine, image WebP auto-convert. Cloudflare adds edge HTML cache, static asset cache, Cache Reserve for cold start mitigation. Together they yield 100 ms TTFB and 0.5 to 0.8 seconds LCP for cached requests.

  • LSCache page cache: enabled for anonymous users, bypass for logged-in and wp-admin
  • LSCache object cache: Redis backend, 60 minute expiry for post objects, 5 minutes for menu queries
  • LSCache CSS minify plus combine plus inline critical: enabled
  • LSCache image WebP auto-convert: enabled, quality 80
  • Cloudflare CDN: domain proxied (orange cloud), SSL Full Strict with Origin Cert
  • Cloudflare Cache Rules: cache HTML edge 5 min for anonymous, bypass for wp-admin and wp-login
  • Cloudflare Cache Reserve: enabled (mitigates cold edge cache after purge)

Purge sequence matters. After editing a post in WP: LSCache purge (post_id specific), then Cloudflare purge (URL specific). The LiteSpeed plugin has a Cloudflare integration tab where you paste an API token and Zone ID, the purge runs automatically. Without it, Cloudflare edge cache serves stale content for 5 to 60 minutes after publish. More technical detail is in our separate write-up on Cloudflare and WordPress setup.

Hosting and baseline server

Without decent hosting all the above optimisation is pointless. Target TTFB under 200 ms, API response time under 100 ms for cached requests. Technical requirements: PHP 8.2 or higher with OPcache enabled, MySQL 8 or MariaDB 10.6 plus with tuned query cache, Redis server for object cache, LiteSpeed Web Server or OpenLiteSpeed (Apache plus nginx also OK but the LSCache plugin needs LSWS for server-level page cache).

  • VPS: Hetzner CPX32 (4 vCPU, 8 GB RAM, 80 GB NVMe SSD) at 14 EUR/month: our default for all in-house projects
  • Managed: SiteGround GoGeek (20 EUR/month) or Kinsta Starter (35 EUR/month) for clients who refuse to manage a server
  • PHP 8.2 plus with OPcache 128 MB minimum
  • MySQL/MariaDB with innodb_buffer_pool_size 2 GB plus for a mid-size site
  • Redis server locally (not shared, each WP instance has its own redis_db_id)
  • HTTP/2 or HTTP/3 mandatory (connection multiplexing)

Shared hosting (OVH economical tier, home.pl base) makes 300 to 500 ms TTFB unavoidable and costs 5 to 10 Performance points compared with a VPS. For any client with a budget above 800 PLN/month retainer we recommend migrating from shared to CPX32 plus self-managed or SiteGround. Lighthouse gain: typically 5 to 12 Performance points and noticeable LCP improvement (1.5 s on shared to 0.8 s on VPS).

Post-deploy audit: Lighthouse plus WebPageTest

DevTools Lighthouse is the first gate, but its result depends on the developer machine. Realistic benchmarking uses PageSpeed Insights with lab Lighthouse plus Chrome UX Report (CrUX) with 28-day data from real users. CrUX shows LCP, INP and CLS as actual users experience them, not just simulated.

  • Lighthouse mobile slow 4G plus desktop: target 99/100 on both
  • PageSpeed Insights field data (CrUX): LCP under 2.5 s, INP under 200 ms, CLS under 0.1 (75th percentile)
  • WebPageTest 4G plus cable: TTFB under 200 ms, Speed Index under 3 s
  • Monitor INP and CLS in real user data via Sentry Performance or Vercel Analytics
  • Regression test after every update: a dedicated CI step or a manual check after a major plugin update

For clients on the 800 PLN/month retainer we run a weekly auto-check via WP-CLI plus uptimerobot, with a Telegram alert if the score drops below 95. For premium clients we add a monthly manual review and rotate through the 5 most-visited URLs. The full scope of our retainer shows exactly what we monitor.

Security cannot wait for the score

Performance and security are two sides of the same infrastructure. A site at 99/100 Lighthouse with a leaky plugin is a liability, not an asset. After stabilising the Lighthouse score, the next step is our WordPress security hardening: 10 mandatory steps, where we list the minimum baseline for every B2B client. Without it a 12000 PLN rebuild can be lost within 6 months of go-live to a plugin CVE.

For the full architecture picture we also recommend technical documentation: web.dev Learn Performance as the Core Web Vitals foundation and the LiteSpeed Cache documentation for plugin-side setup.

FAQ

Will 99/100 hold after every WordPress or plugin update?

Yes, provided the plugin/theme stack is stable and a regression test runs after every major update. Astra Pro, LiteSpeed Cache and Polylang hold the score release over release. Risk arises with major plugin updates (e.g. WooCommerce 8.x to 9.x), where it pays to check Lighthouse in staging before pushing to prod.

Can a WooCommerce shop with a large catalog reach 99/100?

Yes, but only after dedicated work on WooCommerce-specific blockers. We hold 92/100 for a DACH e-commerce client with 30k SKUs, with query cache optimisation on archive pages and a custom lazy load for the product gallery. Single product pages reach 96/100, archive pages 92/100. For a typical B2B shop under 5k SKUs, 99/100 is realistic.

GTmetrix vs Lighthouse: which one matters more?

Lighthouse, because its data feeds Google as a ranking factor (Core Web Vitals from CrUX). GTmetrix is a useful secondary view (waterfall, server response details), but for SEO decisions Lighthouse plus PageSpeed Insights field data win. The GTmetrix free plan tests from Vancouver, which skews results for Polish traffic.

What about TTFB above 200 ms: how to optimise?

TTFB above 200 ms typically indicates shared hosting, missing OPcache, missing object cache (Redis) or a slow admin-side plugin blocking requests. Procedure: check New Relic or Query Monitor for the offending plugin, enable OPcache plus Redis, consider migrating to a VPS. Cloudflare Cache Reserve helps mask a slow origin, but it is a patch, not a fix.

Next steps for your site

If your WordPress site currently scores below 90 and you want to know exactly what is blocking 99/100, Hanse Studio offers a technical Lighthouse audit plus performance roadmap from 1500 PLN. The audit covers theme and plugin analysis, identifies the top 5 bottlenecks, lists 10 to 15 prioritised quick wins (typically returning 10 to 25 Lighthouse points) and scopes a larger rebuild if quick wins are not enough. Contact us for a quote, we reply within 24 hours on business days.

§ From the studio

A new article every month, zero spam.

One case study or technical deep-dive. No clickbaits, no „10 reasons". Unsubscribe with one click.

— Related articles
Websites

Cloudflare + WordPress: setup + cache strategy

2026-03-16 · 10 min read
Websites

Multilingual WordPress: Polylang vs WPML in 2026

2026-03-09 · 11 min read
Websites

Elementor to Gutenberg migration: runbook

2026-03-02 · 9 min read
Back to all posts
Scroll to Top