01 — Foundation

Why performance matters

Performance is product quality — not a launch-week optimisation pass.

Users experience slow loading, delayed interaction, layout shifts, janky scrolling, blocked forms, and battery drain. They do not experience your Lighthouse screenshot.

These standards define practical performance expectations for real-world delivery — real users, real devices, and real networks, not benchmark theatre.

02 — Principles

Performance is a feature

Fast systems feel trustworthy. Slow systems feel broken — even when they technically work.

  • how quickly content appears and whether the page stays stable
  • whether interactions and forms feel reliable
  • shipping less JavaScript, CSS, images, and third-party weight
  • building performance in early — not “we’ll optimise later”
  • treating performance as UX and accessibility, not a separate track

03 — Budgets

Build less and set budgets

Most frontend problems improve when you ship less. Without budgets, performance becomes wishful thinking.

Complexity creates weight. Weight creates delay. Smaller systems are usually faster, easier to maintain, debug, secure, and make accessible.

Ship less of

  • JavaScript, CSS, and decorative UI that does not earn its place
  • oversized images and unreviewed dependencies
  • third-party scripts and unnecessary rendering work

Example budgets

  • homepage under ~1.5MB total payload
  • product or content pages under ~2MB where appropriate
  • JavaScript size capped and reviewed per route
  • image payload and third-party scripts controlled intentionally

04 — JavaScript

JavaScript has a cost

Users pay for every byte with download, parse, execution, memory, and battery — especially on mobile.

Prefer

  • smaller bundles, code splitting, and dynamic imports where they help
  • static HTML where possible, then server-rendered HTML over client-only rendering — progressive enhancement, not a JavaScript-gated shell
  • less framework overhead for simple interfaces

How you deliver HTML

“Server-rendered” and “progressive enhancement” only make sense when you say what you are choosing instead of. The default order of preference:

  1. Static site generation (SSG) when pages can be built ahead of time — pre-rendered HTML on a CDN, the flattest and usually fastest path, with little work per visit.
  2. Server-side rendering (SSR) when HTML must be fresh, personalised, or driven by request-time data — still sends real content in the first response before client JavaScript runs.
  3. Client-side rendering (CSR) only where a dense client application truly needs it — the document is largely empty until JavaScript downloads, parses, and executes. If that script fails, is blocked, or is slow, users get no meaningful page: no article, no form, no navigation.

Progressive enhancement sits on top of that stack: HTML and server-delivered content carry the core task; JavaScript adds polish, validation, and convenience. That is the opposite of CSR-only shells that treat JavaScript as the delivery mechanism for content itself.

See rendering strategy andprogressive enhancement strategy.

Load code only when needed

async function openDialog() {
    const { initDialog } = await import('./dialog.js');

    initDialog();
}

Avoid shipping megabytes of JavaScript for pages that mostly render content. If removing a dependency makes the product faster and simpler, remove it.

On Frontend Foundations, syntax highlighting loads only on routes with code samples — not on the homepage, index hubs, or legal pages. The header nav script stays small and scoped to the layout.

05 — Enhancement

Progressive enhancement

Core functionality should work before JavaScript enhancement.

Content should load first. Forms should function first. Navigation should exist first. Users should not wait for hydration to complete basic tasks.

JavaScript should improve the experience — not justify whether the experience exists at all. That requires HTML delivered by static or server rendering — not a client-only shell — see JavaScript has a cost.

06 — Assets

Images and fonts

Images are often the largest problem. Fonts affect rendering speed and layout stability.

Images

  • assets close to displayed size — compression and format before extra markup
  • modern formats where appropriate
  • width and height to reserve space
  • lazy loading and fetchpriority used intentionally
<img
    src="/images/hero-1200.webp"
    width="1200"
    height="675"
    alt="Team reviewing a performance budget"
    loading="lazy"
    decoding="async"
>

Default to one well-sized file. Do not upload giant assets because they looked sharp on a Retina display — export for how the image is actually shown.

Add picture when the mobile image needs a different shape or crop. Add srcset only when smaller variants save meaningful bytes. With lazy loading, sizes="auto" can replace hand-writtensizes in supporting browsers — it does not remove the need forsrcset when you use width descriptors at all.

Fonts

Limit families and weights. Preload only what you need. Usefont-display sensibly. Prefer system fonts where brand requirements allow — typography matters, and so does loading behaviour.

Variable fonts, when chosen and subset with care, can replace several static woff2 files with one — fewer HTTP requests and sometimes a smaller total download than loading regular, medium, and bold as separate files. One family can cover a range of weights (and other axes you actually use) through font-weight and font-variation-settings, which gives more typographic flexibility without multiplying font files on every route. That only holds if you subset to the weights and characters you need: a full “kitchen sink” variable font with every glyph and axis is often larger than two or three well-chosen static cuts — compare file sizes and font loading guidance before committing.

Variable family — one file, a weight range

@font-face {
    font-family: "Brand Sans";
    src: url("/fonts/brand-sans-variable.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
}

.body {
    font-family: "Brand Sans", system-ui, sans-serif;
    font-weight: 400;
}

.content-card > .title {
    font-weight: 700;
}

Declare the weights the file supports in @font-face, then use normal font-weight in CSS — no separate file per weight.format("woff2") is enough for variable WOFF2 in current browsers;format("woff2-variations") only makes the variations capability explicit and is not required if you declare a font-weight range.

Static face — one file per weight

This site self-hosts a minimal set of woff2 files (DM Sans 400 and 700, Fraunces 600, JetBrains Mono 400) withfont-display: swap — no third-party font CDN. Preload only the faces needed for first paint: DM Sans 400 and Fraunces 600 on most routes; JetBrains Mono 400 only where code samples appear above the fold. Ship only the weights your CSS actually uses and document the choice in your performance budget.

@font-face {
    font-family: "Brand Sans";
    src: url("/fonts/brand-sans-700.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

07 — CSS

CSS performance

CSS is usually cheaper than JavaScript — but bloated CSS still hurts.

  • avoid huge unused stylesheets and deep framework override wars
  • keep specificity predictable with simpler selectors
  • limit expensive animations and layout-thrashing patterns
  • ship less CSS — remove what pages do not use

08 — Stability

Layout stability

Unexpected movement destroys trust — especially in checkout, forms, and navigation.

  • reserve image and embed dimensions before they load
  • reserve space for components that load asynchronously
  • avoid content jumping during interaction or late UI injection
  • treat Cumulative Layout Shift as user frustration, not only a metric

09 — Third party

Third-party scripts and Core Web Vitals

Every embed is a product decision — not a harmless marketing tag.

Before adding a script, ask

  • does this solve a real problem?
  • what is the performance, privacy, and accessibility cost?
  • what happens if it fails or loads slowly?
  • is there a lighter alternative, and who maintains it?

“Marketing wanted it” is not technical justification. Scripts affect stability, debugging, and user trust.

Core Web Vitals

Monitor LCP, CLS, INP, FCP, and TTFB — use them as signals, not the full story. Real usability on weaker devices still matters more than dashboard screenshots alone.

10 — Delivery

Mobile reality, delivery, and maintenance

Optimise for real conditions — and keep performance from quietly regressing.

Do not optimise for office laptops only. Users are on slow networks, older phones, battery saver mode, and shared devices. Performance should reflect that reality.

Performance is accessibility: heavy pages and delayed interaction increase cognitive load and hurt people on older hardware, assistive technology, and unstable networks.

Delivery

Use caching headers, CDN delivery, compression, and intentional preload and fetch priority. Performance is not only frontend code — it is delivery strategy too.

Long-term maintenance

Review and measure regularly. Protect budgets continuously. Slow decline happens quietly through extra scripts, growing bundles, duplicated code, and forgotten assets. Guard against entropy.

Anti-patterns to avoid

  • “we’ll optimise later” — later usually means never
  • huge JavaScript bundles for basic content pages
  • unreviewed third-party embeds
  • infinite loading states instead of shipping the feature
  • blaming user devices when normal hardware struggles

11 — Review

Before you approve

A short checklist for performance in code review.

  • page weight controlled intentionally against budgets
  • JavaScript justified by real enhancement, not default bloat
  • images sized and compressed for how they are displayed — not oversized defaults
  • layout shifts prevented and third-party scripts reviewed
  • progressive enhancement respected on critical flows
  • Core Web Vitals monitored with real-device spot checks
  • experience tested on weaker devices and networks

When in doubt, ask: Would removing this make the product faster, simpler, and easier to trust? If the answer is yes, remove it.