DEV Community

René
René

Posted on

How We Ship 100/100 PageSpeed Sites for Small-Business Clients (Without a CMS)

Most small-business websites are slow. Not because the businesses don't care, but because the default stack they get sold — a heavyweight CMS, a page builder, twelve plugins — fights against performance from day one.

At webgaudi.at, our small web studio in Vienna, we build sites for therapists, lawyers, electricians and restaurants. Tiny budgets, no dev team on the client side, and one hard promise we make: 100/100 on PageSpeed Insights, mobile and desktop. Here's the actual playbook — no secrets, just decisions that compound.

1. Static-first with Astro: ship zero JS by default

The single biggest lever is architectural. A brochure site for a psychotherapist has maybe one interactive element (a contact form, an FAQ accordion). It does not need a client-side framework, and it definitely doesn't need a database rendering pages on every request.

We use Astro because its islands architecture makes "zero JavaScript" the default instead of an optimization:

---
// FaqSection.astro — pure HTML/CSS, no JS shipped
const faqs = await getCollection('faq');
---
{faqs.map((faq) => (
  <details>
    <summary>{faq.data.question}</summary>
    <div set:html={faq.rendered.html} />
  </details>
))}
Enter fullscreen mode Exit fullscreen mode

Native <details>/<summary> gives you an accessible accordion for free. The temptation to reach for a React component here is real — resist it. Every island you don't ship is hydration cost you never pay.

When we do need interactivity, we scope it:

<ContactForm client:visible />
Enter fullscreen mode Exit fullscreen mode

client:visible means the form's JS only loads when it scrolls into view. On a typical landing page, that's the difference between 0 KB and 40 KB of JS on initial load.

2. Images: the 80% problem

Almost every failing Lighthouse audit we inherit comes down to images. Our rules:

  • Author in highest quality, let the build optimize. Astro's <Image /> component generates AVIF/WebP, correct srcset, and explicit dimensions (no layout shift):
import { Image } from 'astro:assets';
import hero from '../assets/hero.jpg';

<Image src={hero} alt="..." widths={[400, 800, 1200]} format="avif" />
Enter fullscreen mode Exit fullscreen mode
  • The LCP image gets loading="eager" and fetchpriority="high". Everything below the fold gets lazy-loaded. Getting this one attribute right is often worth 0.5–1s of LCP.
  • No icon libraries. Inline the 6 SVGs you actually use. A 200 KB icon font for a hamburger menu is how good scores die.

3. Fonts without the FOIT (and without the GDPR headache)

We self-host fonts as subsetted WOFF2 with font-display: swap and preload only the body font:

<link rel="preload" href="/fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
Enter fullscreen mode Exit fullscreen mode

Side note for anyone building for EU clients: loading fonts from Google's CDN has been a legal problem since a 2022 German court ruling (LG München) — a visitor's IP gets transmitted to Google without consent. Self-hosting fixes performance and compliance in one move. Two birds.

4. Edge delivery instead of a single origin server

Static output means the whole site can live on a CDN. We deploy to an edge network so a site for a Viennese client loads just as fast for someone visiting from a hotel WiFi in Lisbon. TTFB consistently lands under 100ms — something a shared-hosting PHP origin can't match no matter how much you cache.

5. The boring checklist that protects the score

Performance regresses through a thousand small additions. Before every go-live:

  • Lighthouse CI run on throttled mobile (the real-world case), not desktop
  • No third-party scripts above the fold; analytics loads deferred and only after consent
  • One critical CSS file under ~50 KB, no unused framework CSS (we purge aggressively)
  • viewport meta, explicit width/height on all media, no render-blocking resources
  • Accessibility pass: semantic landmarks, contrast, focus states — Lighthouse's a11y score is part of the 100/100 promise, not an afterthought

What this means for clients (the part nobody measures)

The interesting outcome isn't the green circle — it's behavior. Average load time across our portfolio is ~0.8s, and the bounce-rate difference compared to the WordPress sites we replace is dramatic. Studies keep showing mobile users abandon pages after roughly 4 seconds; for a small business where the website is the sales funnel, that's lost revenue, not a vanity metric.

If you're a freelancer or studio serving small businesses, my honest pitch: static-first isn't a constraint, it's a feature you can sell. Faster sites, lower hosting costs, smaller attack surface, no plugin update treadmill.


I run webgaudi.at, a small web studio in Vienna building fast, GDPR-compliant sites for SMBs in Austria, Germany and Switzerland. Happy to answer questions about the stack in the comments — especially if you're fighting your own PageSpeed battles.

Top comments (0)