We audit other people's websites for a living. It would be embarrassing to have a slow one ourselves. This is the full technical case study of folio.lu — every major decision, the Lighthouse scores, and the three issues our own audit process found that we had to go back and fix.
No self-congratulation: the purpose of this post is to show our methodology in action on a site you can inspect yourself. Open DevTools and check the claims.
The scores
The 88 accessibility score is a known gap — colour contrast on some of the muted-text elements is below WCAG AA. We're iterating on this. The 96 performance score reflects genuine optimisation, not a simple site with nothing on it.
Decision 1: No framework, no CMS.
The first architectural decision was to ship static HTML, CSS, and minimal vanilla JavaScript. No React. No Next.js. No WordPress. No Webflow.
We evaluated Astro — it's in our tech stack for client projects — and decided against it for our own site. Astro adds build complexity and an abstraction layer that doesn't benefit a site where the content doesn't change frequently. Static HTML ships as-is, runs on any CDN, has zero server surface, and loads faster than any framework alternative because there's nothing to hydrate.
The rule we use: if a site needs real-time data, user authentication, or content that changes daily, use a framework. If it's a marketing site that updates monthly, static HTML will outperform every framework alternative on every metric that matters to clients.
The consequence: our editors (us) write HTML. For a two-person studio, this is fine. For a client who needs to update blog content, we would add a headless CMS — Sanity or Payload connect to static output without giving up the performance benefits.
Decision 2: The typography stack.
Typography is the loudest design decision on a text-heavy marketing site. We use three typefaces, each with a specific role:
All three are loaded from Google Fonts with preconnect and display=swap. We load all axes in a single request to avoid multiple font connections.
Decision 3: Dark mode as primary.
Most web agencies default to a white site with a dark footer. We inverted this: #0A0A0B background as the primary experience, with #39D353 (GitHub-contribution green) as the accent.
The reasoning:
- Dark editorial design reads as premium and considered — not default. It signals we chose this intentionally.
- The green accent on near-black is high-contrast, legible, and unusual enough to be memorable.
- Dark backgrounds reduce LCP impact from hero images — the eye reads the text before the image loads, so a slow image is less jarring.
- The target client — an SME owner who cares about quality — responds better to confidence than to convention.
We don't offer a light mode toggle. The site has a point of view. Clients who prefer white backgrounds have 200 other agencies to choose from.
Decision 4: CSS custom properties as the design system.
Every colour, spacing unit, and type size is a CSS custom property defined in :root. This means:
- The entire visual language can be changed in one file
- There is no design-system drift — a component either uses the variable or it doesn't compile
- Dark mode (or any theme) would be a three-line CSS override, not a rebuild
- All spacing uses
clamp()with min, ideal, and max — the layout is fluid, not step-responsive
The grid lines you see throughout the site are not borders on individual elements. They are a 1px gap on a CSS Grid container with background: var(--border). Each child element has background: var(--bg). The gap exposes the container background. This technique makes every grid cell "bordered" without any per-cell border declarations — and it makes the entire grid remove its lines in one property change.
Decision 5: Structured data and schema.
Every page of folio.lu has JSON-LD structured data. The homepage has a full LocalBusiness schema with areaServed (7 Luxembourg cities), hasOfferCatalog linking to all three services, and priceRange. Service pages each have Service schema with price specifications. The services page has a FAQPage schema. Every inner page has BreadcrumbList.
This is the SEO work that's invisible to users but fully visible to Google. Rich results (FAQ dropdowns, business information in the knowledge panel) require structured data to be present and valid. Most SME websites have none of this.
The three issues our own audit found.
We ran our own site through the same audit process we use on clients. Here are the three issues it flagged, and what we did about them.
subset=latin parameter. The full character set for a variable font with five axes is significantly larger than the Latin subset. Fix: appended &subset=latin to the Google Fonts URL, reducing the font payload by ~40%. This directly improved LCP by removing unnecessary network weight from a render-blocking resource.<a> wrapping an image with no aria-label. Screen readers would announce "link" with no destination context. Fix: added aria-label="View [client name] case study" to each card link. This brought the accessibility score from 84 to 88 — and is a practice we now check on every client build before launch..sect-head, .cta-section, .hero-actions) were defined inline in index.html's <style> block and used across other pages without being redefined. The sample audit page looked completely unstyled when loaded directly. Fix: moved all shared layout classes that weren't already in style.css into the relevant pages' local <style> blocks. This is a hazard of no-build-tool development — components don't have automatic scope, so global styles must be in a global stylesheet.Lessons for client work.
Building and auditing our own site reinforced three practices we now apply to every client project:
- Load fonts with preconnect. Every Google Fonts link must be preceded by two preconnect tags — one for fonts.googleapis.com, one for fonts.gstatic.com. Without them, the browser doesn't start the font connection until it parses the stylesheet, adding 200–400ms to first render.
- Aria-label every image link. Any
<a>whose only child is an<img>needs an aria-label. We added this to our launch checklist. - All shared CSS lives in style.css. No class should be defined in a page's
<style>block if it's used on more than one page. The component model without a build tool requires discipline about this boundary.
The 96 performance score isn't a number we polished for a case study. It's the output of applying the same methodology we use on clients to ourselves. We'll keep auditing our own site — the three fixes above came from it, and there will be more.