Performance budgets for mobile sites often focus on the wrong bottlenecks. While many teams blame slow servers or oversized images, the real culprit is JavaScript that loads unnecessarily. A typical marketing site with a hydrating SPA, analytics tags, and marketing pixels can block the main thread for one to two seconds on a mid-range Android device, making the page feel sluggish even before the user interacts. The solution isn’t to optimize that JavaScript—it’s to avoid shipping it in the first place.
Why mobile Lighthouse scores collapse under JavaScript weight
Lighthouse’s mobile score is heavily influenced by two metrics: Total Blocking Time (TBT) and Largest Contentful Paint (LCP). On a throttled CPU simulating a Moto G Power on 4G, every kilobyte of JavaScript that must parse, compile, and execute on the main thread translates directly into milliseconds of delay. A single-page application that hydrates, a tag manager firing on load, three marketing pixels, and a cookie consent banner can collectively push TBT into the 1,000–2,000ms range—long before the user sees a usable page.
The fix isn’t micro-optimizing that JavaScript. It’s ensuring it never runs at all.
Build static HTML first, opt into JS only when necessary
The most effective way to cut JavaScript is to avoid shipping it by default. That’s where Astro shines. Unlike conventional frameworks that hydrate components at runtime, Astro renders components to static HTML during build time. If you never opt into client-side JavaScript, the browser receives pure HTML and CSS—nothing to parse or execute. My marketing sites use zero framework islands; the only scripts present are lightweight vanilla utilities like a mobile menu toggle and a consent banner. With no hydration step, the main thread remains unblocked, and TBT drops to zero.
For teams using Next.js or similar, the principle is identical: prioritize static generation and server components while ruthlessly limiting client components. HTML is free; JavaScript is expensive in terms of performance and user experience.
Eliminate render-blocking CSS by inlining styles
External stylesheets force the browser to wait before painting the first pixel. On slow connections, this creates a visible delay that Lighthouse penalizes. In Astro, inlining CSS is simple:
// astro.config.mjs
export default defineConfig({
build: {
inlineStylesheets: 'always',
},
});With this setting, all page CSS is embedded directly into the HTML document. The browser makes one request instead of two, styles render immediately, and first paint occurs as soon as the HTML arrives. For small marketing sites, the CSS rarely exceeds a few kilobytes, making the tradeoff overwhelmingly positive.
Defer third-party scripts until real user interaction
Third-party scripts like Google Tag Manager, analytics, and marketing pixels are major contributors to TBT because they execute on the main thread during load. Lighthouse tests pages without user interaction, so anything triggered on load or idle will appear in the trace and inflate the score.
Instead, defer these scripts until the first meaningful interaction—scroll, tap, or key press. The following snippet loads GTM only after the user engages:
<script>
(function () {
let loaded = false;
const events = ['scroll', 'mousemove', 'touchstart', 'keydown', 'pointerdown'];
function loadGTM() {
if (loaded) return;
loaded = true;
events.forEach((e) => window.removeEventListener(e, loadGTM));
// Inject GTM/analytics here
}
events.forEach((e) => window.addEventListener(e, loadGTM, { passive: true }));
})();
</script>Real visitors typically scroll or tap within a second, so analytics still capture engaged sessions. Lighthouse never triggers these events, so third-party scripts never run during the test window, and TBT stays near zero. The tradeoff? Visitors who bounce without interaction won’t be measured—but for marketing sites, that’s acceptable since lab scores themselves are a selling point and engaged sessions are the ones that matter.
Use fonts that never swap mid-layout
Web fonts cause two performance issues: layout shift (if the fallback renders first) and delay (while waiting for the font to load). The solution is twofold:
- Set
font-display: optionalto tell the browser to use the fallback immediately and swap only if the web font arrives within a tiny window. - Inline
@font-facerules and preload the critical weights above the fold.
<link rel="preload" href="/fonts/display-700.woff2" as="font" type="font/woff2" crossorigin />
<style>
@font-face {
font-family: 'Display';
font-weight: 700;
font-display: optional;
src: url('/fonts/display-700.woff2') format('woff2');
}
</style>With this approach, layout shift from fonts is effectively zero, and Cumulative Layout Shift (CLS) remains flat at 0 across all pages.
Serve optimized images without dual fetches
Modern marketing sites rely heavily on images, but they’re often delivered in inefficient formats or resized at runtime. The fix is straightforward: use Astro’s built-in assets pipeline (or your framework’s equivalent) to serve WebP or AVIF at the exact dimensions needed.
For the Largest Contentful Paint image—usually a hero banner—preload it aggressively, but ensure the preload’s imagesrcset matches the actual <img> tag’s request. Otherwise, the browser fetches the image twice, turning the preload into a penalty. Generate the optimized URL at build time and emit a matching preload link in the <head>.
Deploy to a fast edge CDN
Static sites are ideal for edge caching. I use Cloudflare Pages, where Time To First Byte hovers around 50ms because the HTML is cached at the edge near the user. A fast origin matters less when the document is already sitting in a nearby data center. Just confirm your CDN’s edge network is genuinely fast—some providers claim global coverage but route requests through suboptimal paths.
The bigger takeaway: performance starts with restraint
Perfect mobile scores don’t require exotic tricks or expensive tooling. They come from a disciplined approach: ship static HTML, inline critical assets, defer non-essentials, and design interactions that serve real users—not synthetic tests. The sites that score 100/100 on Lighthouse’s harshest mobile profile aren’t the ones with the most optimizations; they’re the ones that sent the least.
AI summary
Learn the exact Astro-based setup to hit 100/100 on mobile Lighthouse. Reduce JavaScript, inline CSS, defer third-party scripts, and optimize fonts and images for peak performance.