When we built the Grove Foundry website, we scored 100 across all four Google Lighthouse categories on build: Performance, Accessibility, Best Practices, and SEO. The total page weight is under 35KB. We didn't use React, Next.js, Astro, or any other framework. No build tools, no bundlers, no node_modules folder. Just HTML, CSS, and a tiny bit of JavaScript.

This wasn't an accident or a vanity exercise. It was a deliberate choice, and in this post I'll explain why we went this route, the specific techniques that made it work, and when this approach does (and doesn't) make sense.

Why This Matters

The median website weighs 2.86 MB on desktop. Ours is under 35KB. That makes it roughly 75 times smaller than the average page.

For context, the median page ships 613KB of JavaScript alone. Our entire site, HTML and CSS and JS included, is smaller than a single React import.

Performance isn't just about developer bragging rights. Lighthouse Performance scores closely model Core Web Vitals, which Google uses as a ranking signal based on real-world field data. The median Lighthouse Performance score across all websites is around 34 out of 100, and less than half pass all three Core Web Vitals thresholds. For a digital marketing consultancy, having a fast site isn't just nice to have. It's proof that we practice what we advise. Site speed is a foundational part of any SEO strategy - you can have perfect content and backlinks, but slow pages will hold you back in rankings. Site speed also matters for eligibility in programs like Google Ad Grants, which requires a homepage PageSpeed score above 50.

Why No Framework

This might be controversial in 2026, where the default answer to "I need a website" seems to be "spin up a Next.js project." But for a brochure site, a marketing site, a portfolio, or a small business site, a JavaScript framework is overhead you don't need and your visitors pay for.

React adds 42KB+ (minified and gzipped) before you write a single line of your own code. A full Next.js application easily ships 200 to 500KB of JavaScript. That JavaScript has to be downloaded, parsed, and executed before your page becomes interactive. On a fast connection with a powerful device, you barely notice. On a budget phone with patchy mobile signal (think regional Australia, crowded event WiFi, or a building with terrible reception), it's the difference between a page that loads in under a second and one that takes five.

Framework sites also carry hydration costs. The server renders HTML, sends it to the browser, then the browser downloads and executes JavaScript to "hydrate" that HTML and make it interactive. For a site where the most complex interaction is a hamburger menu, that's an absurd amount of machinery.

Static HTML renders immediately. There's no JavaScript to wait for. The page is interactive the moment the HTML arrives. Every element is there, every link works, every form functions. No loading spinners, no layout shifts, no flash of unstyled content.

The tradeoffs are real. Static sites are harder to maintain if you have lots of repeated elements (headers, footers, navigation) across many pages. There's no component abstraction, so changes to shared elements mean editing multiple files. You wouldn't build a complex web application this way. But for a five-to-ten-page marketing site? Static HTML is the right tool.

The Techniques

Inline Critical CSS

CSS is render-blocking by default. When a browser encounters a <link rel="stylesheet"> tag, it stops rendering the page until that stylesheet has been downloaded and parsed. On a slow connection, that pause can be hundreds of milliseconds.

Critical CSS is the subset of your stylesheet that's needed to render the above-the-fold content. By inlining it directly in a <style> tag in the HTML <head>, the browser can start rendering immediately without waiting for an external file.

<head>
  <style>
    /* Only the CSS needed for above-the-fold content */
    body { margin: 0; font-family: system-ui, sans-serif; }
    .hero { padding: 2rem; }
    /* ... */
  </style>
</head>

The performance impact is significant. When your critical CSS fits within the first TCP round-trip (roughly 14KB compressed), the browser can begin rendering as soon as the HTML document arrives. That's around 200ms after the request, compared to 3x longer if the browser has to fetch a separate stylesheet first.

The key is discipline: only inline what's actually needed for the initial viewport. Inline too much and you bloat the HTML document. Inline too little and you get a flash of unstyled content.

Async Stylesheet Loading

The rest of the CSS (anything below the fold, print styles, non-critical components) gets loaded asynchronously so it doesn't block rendering.

The widely recommended approach is the media="print" pattern:

<link rel="stylesheet"
      href="/styles/main.css"
      media="print"
      onload="this.media='all'; this.onload=null;">
<noscript>
  <link rel="stylesheet" href="/styles/main.css">
</noscript>

Setting media="print" tells the browser this stylesheet is only for print, so it downloads it at low priority without blocking rendering. The onload handler then switches the media type to all, applying the styles once the file has loaded. The <noscript> fallback ensures the stylesheet loads normally for the rare visitor with JavaScript disabled.

This approach has better browser support than rel="preload" and doesn't compete with critical resources for download priority. Filament Group (who maintained the popular loadCSS library for years) now recommends this pattern over their own preload-based solution.

Minimal JavaScript

We use JavaScript sparingly and only for genuine interactivity: mobile navigation toggle, form validation, and a couple of small UI enhancements. No frameworks, no libraries, no polyfills.

All scripts load with defer or at the bottom of the body so they never block rendering. The total JavaScript payload is measured in bytes, not kilobytes.

If something can be done with HTML and CSS alone, it should be. CSS handles animations, transitions, hover states, responsive layouts, and basic interactivity through :target, :focus, and :checked selectors. You'd be surprised how much "interactivity" doesn't actually need JavaScript.

Schema Markup

Structured data helps search engines understand your content and can earn rich results in search. For a service business, the most valuable schema types are LocalBusiness (or the more specific ProfessionalService subtype), Organization, Service, WebSite, and FAQPage.

Implementation uses JSON-LD format in a script tag, which is Google's preferred approach:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "ProfessionalService",
  "name": "Grove Foundry",
  "url": "https://www.grovefoundry.com.au",
  "telephone": "+61...",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Perth",
    "addressRegion": "WA",
    "addressCountry": "AU"
  }
}
</script>

This contributes to the SEO Lighthouse score and, more importantly, helps Google surface your business information correctly. Structured data is also central to Answer Engine Optimisation (AEO), where the goal is to appear in AI-generated answers rather than just ranked results.

Semantic HTML and Accessibility

Getting a perfect Accessibility score starts with using HTML the way it was designed to be used.

Proper landmarks: <header>, <nav>, <main>, <footer>. Heading hierarchy that makes sense. Descriptive alt text on content images, empty alt="" on decorative ones. Visible focus indicators on all interactive elements (the majority of websites remove focus outlines, which is both a WCAG violation and an accessibility failure). Sufficient colour contrast (4.5:1 minimum for normal text). Touch targets at least 48x48 pixels. Relative font units so text can be resized.

Static HTML actually has accessibility advantages over framework-rendered pages. No virtual DOM means screen readers get a clean, predictable document structure. No client-side routing means the back button works as expected. No hydration means there's no period where the page looks interactive but isn't responding to input.

An important caveat: a perfect Lighthouse Accessibility score does not mean your site is fully accessible. Lighthouse catches roughly 30 to 40% of accessibility issues through automated testing. Manual testing with screen readers (VoiceOver, NVDA) and keyboard-only navigation is still essential. One developer famously demonstrated building a completely inaccessible site that scored 100 on Lighthouse Accessibility, proving the tool has real limitations. We treat the score as a floor, not a ceiling.

How Lighthouse Actually Scores Performance

Understanding the scoring helps explain why these techniques work. The Performance score is a weighted average of five metrics:

Total Blocking Time (30% weight): How long the main thread is blocked by JavaScript. With virtually no JavaScript, our TBT is effectively zero.

Largest Contentful Paint (25%): When the largest visible element finishes rendering. With inline critical CSS and no render-blocking resources, our LCP happens almost immediately after the HTML arrives.

Cumulative Layout Shift (25%): Visual stability. No JavaScript-injected content, no late-loading fonts causing reflow, no dynamically sized images. Elements are where they should be from the first paint.

First Contentful Paint (10%): When the first text or image appears. Same story as LCP: inline CSS means the browser starts painting immediately.

Speed Index (10%): How quickly content is visually displayed. With a sub-35KB page, the entire page loads within one or two network round-trips.

Each metric is scored against real-world data from the HTTP Archive. The curves are log-normal, meaning the difference between 90 and 95 requires similar effort to the difference between 95 and 100. Going from 99 to 100 is genuinely hard. Our approach makes it straightforward by eliminating the usual bottlenecks at the source.

What We Didn't Do

We didn't use a CSS framework like Tailwind or Bootstrap. Custom CSS written for your specific design will always be smaller than a utility framework's output, even after purging.

We didn't use web fonts from Google Fonts or any external CDN. System fonts (system-ui, -apple-system, sans-serif) render instantly with zero network requests. If your design absolutely requires a specific typeface, self-host it and preload it. But for body text, system fonts are faster and look native on every device.

We didn't use a static site generator. Tools like Hugo, Eleventy, or Jekyll are good at what they do, but they add a build step and dependencies that we didn't need for a small site. Every page is a standalone HTML file. It deploys by copying files to a server.

We didn't use images where we didn't need them. No hero banners with 2MB stock photos. No decorative images that add visual weight without communicating anything useful. Where we do use images, they're optimised, properly sized, and include explicit width and height attributes to prevent layout shifts.

When This Approach Makes Sense (and When It Doesn't)

This approach is ideal for brochure sites, marketing sites, landing pages, portfolio sites, small business websites, and any content-focused site with minimal interactivity. If the most complex thing on your site is a contact form or a mobile menu, you don't need 200KB of JavaScript. For paid media landing pages, where every click costs money, a fast page means more of that spend converts.

It's not the right choice for web applications with complex state management, real-time features, dashboards with heavy interactivity, or sites where dozens of engineers need to work on the same codebase with component-level isolation. Those are the problems frameworks were built to solve.

The vast majority of small business and marketing sites fall into the first category. They're using frameworks not because they need them, but because the developer's workflow defaults to them.

The Results

100/100/100/100 on Lighthouse. Under 35KB total page weight. Pages load in under a second on typical connections. No layout shifts. No JavaScript execution delays. No render-blocking resources.

A note on Best Practices: once you add Google Tag Manager (which virtually every production site needs), the score drops to around 81. GTM's service worker uses the AttributionReporting API, which Chrome flags as deprecated. That deduction comes entirely from Google's own code, not ours. It's a trade-off worth making - a site without analytics is a site without feedback.

A website that's 75 times smaller than the median page, scoring in the top fraction of a percent for performance, built without a single npm package.

Sometimes the best technology choice is less technology.

Frequently asked questions

Common questions about Lighthouse scores and static site performance.

No. For brochure sites, marketing sites, portfolios, and small business websites, a JavaScript framework adds overhead your visitors pay for. React alone adds 42KB+ before you write any code; a full Next.js app ships 200-500KB of JavaScript. Static HTML renders immediately with no hydration cost. The right tool depends on your site's complexity - frameworks shine for complex web applications, not five-to-ten-page marketing sites.

Lighthouse scores performance using a weighted combination of metrics: Largest Contentful Paint (LCP, 25%), Total Blocking Time (TBT, 30%), Cumulative Layout Shift (CLS, 25%), First Contentful Paint (FCP, 10%), and Speed Index (10%). The biggest gains come from reducing render-blocking resources, minimising JavaScript execution time, and keeping total page weight low. Lighthouse Performance scores closely model Core Web Vitals, which Google uses as a ranking signal.

Inline critical CSS is the minimum CSS needed to render the above-the-fold content of a page, placed directly in a <style> tag in the <head>. This eliminates a render-blocking stylesheet request, so the browser can paint the page immediately without waiting for an external CSS file to download. The full stylesheet is then loaded asynchronously, which prevents it from blocking rendering while still applying styles once loaded.

Static HTML is the right choice for brochure sites, marketing sites, portfolios, and small business websites with five to ten pages. The trade-off is maintenance: without component abstraction, updating shared elements like headers and footers means editing multiple files. If your site has complex interactivity, user authentication, dynamic data, or many repeated components, a framework or static site generator becomes worthwhile. The decision should be driven by your site's actual requirements, not default assumptions.

Yes - adding GTM drops the Best Practices score from 100 to around 81. GTM's service worker uses the AttributionReporting API, which Chrome flags as deprecated. This deduction comes from Google's own code, not your site's code. It is a trade-off worth making: a site without analytics is a site without feedback. The Performance, Accessibility, and SEO scores remain unaffected by GTM when implemented correctly via a server-side proxy.

Stuart Walker

Written by Stuart Walker

Digital marketing and tech consultant based in Perth, with 5+ years across government, private sector, and not-for-profit organisations.

Ready to talk?

No obligation, no pitch. Just a conversation about your organisation and how we might help.

Get in touch