# Performance

> Frontend performance principles for building fast, responsive Oztix applications.

Speed is a feature. Performance is the foundation of user trust. Latency kills
flow.

## Measure reliably

You can't fix what you can't measure. Guesswork leads to optimisation of the
wrong things.

### Test on real conditions

Development machines lie. A fast laptop on fibre hides the problems your users face.

**Do**

Disable extensions. Use CPU and network throttling to simulate real
devices. Profile in an incognito window.

**Don’t**

Don't profile on a high-end machine with a fibre connection and assume
it's fine.

## Core Web Vitals

Three metrics that define real-world user experience. Target green on all
three.

### No layout shift (CLS)

Stability is quality. Things should not jump around as they load.

**Do**

```tsx
<!-- Set explicit dimensions on images -->
<img src="hero.jpg" width={800} height={400} alt="..." />

<!-- Reserve space for async content -->
<div className='min-h-50'>
  {content ?? <Skeleton />}
</div>
```

Set explicit dimensions on all images and media. Reserve space for content
that loads asynchronously.

**Don’t**

Don't let an image push text down the page when it finally loads, or
inject content above the viewport without reserving space.

### Fast first paint (LCP)

Prioritise the viewport. Bandwidth should be spent on what is seen immediately.

**Do**

```tsx
<!-- Preload above-the-fold images -->
<link rel="preload" href="/hero.jpg" as="image" />

<!-- Lazy-load everything else -->
<img src="footer.jpg" loading="lazy" alt="..." />
```

Preload above-the-fold images. Lazy-load everything below the fold.

**Don’t**

Don't lazy-load the Largest Contentful Paint image, causing a visible
delay on the most important element.

For Oztix-hosted imagery the [`Image` component](https://ticketsolutionsptyltd.github.io/roadie/components/image.md) handles
all of this: explicit dimensions to prevent CLS, a right-sized WebP `srcSet` to
cut bytes, lazy-by-default loading with `priority` for the LCP image, and
`defer` for off-screen carousel slides.

### Responsive to input (INP)

The main thread is for UI. Heavy logic belongs elsewhere.

**Do**

Move expensive calculations or data processing to Web Workers. Break long
tasks into smaller chunks with `requestIdleCallback` or
`scheduler.yield()`.

**Don’t**

Don't freeze the UI while parsing a large JSON file or running an
expensive sort on the main thread.

## Rendering

Reactivity has a cost. The DOM is slow. Minimise the work the browser has to
do.

### Track re-renders

Unnecessary renders burn battery and block interaction.

**Do**

Use React DevTools or React Scan to identify and minimise re-renders.
Colocate state near where it's used.

**Don’t**

Don't assume functional components are cheap enough to re-render
constantly. A parent re-rendering cascades to every child.

### Minimise layout work

Reading and writing layout properties triggers expensive reflows.

**Do**

Batch DOM reads and writes. Animate with `transform` and `opacity`
(compositor-only properties) instead of `width`, `height`, or `top`.

**Don’t**

Don't read a width, set a width, then read it again in a loop (layout
thrashing).

### Virtualise large lists

DOM nodes are heavy. Don't render what the user can't see.

**Do**

```tsx
/* Use virtualisation or CSS containment */
<VirtualList items={items} />

/* Or use CSS containment for simpler cases */
.offscreen { content-visibility: auto; }
```

Use virtualisation (such as `virtua`) or `content-visibility: auto` for
long lists and large tables.

**Don’t**

Don't render 10,000 rows in a table just because the API returned them.

## Network

Latency kills flow. API interactions should feel instantaneous.

### Network budgets

Set expectations for how fast things should be.

**Do**

Ensure POST, PATCH, and DELETE requests complete in under 500ms. Use
optimistic UI for fast, reversible actions so the interface moves at the
speed of thought.

**Don’t**

Don't block the UI for 2 seconds while waiting for a database write.

### Preconnect to origins

Handshakes take time. Establish connections before you need the data.

**Do**

```tsx
<link rel="preconnect" href="https://cdn.oztix.com.au" />
<link rel="dns-prefetch" href="https://api.oztix.com.au" />
```

Use `<link rel="preconnect">` for critical CDNs or API domains.

**Don’t**

Don't wait until JavaScript requests a font before opening the connection
to the font server.

## Fonts

Typography should not block reading. Text must appear fast. See
[Typography](https://ticketsolutionsptyltd.github.io/roadie/foundations/typography.md) for the full type system.

### Optimise font loading

Custom fonts are a luxury the user pays for with load time.

**Do**

Preload critical fonts. Subset fonts to only include used characters (such
as with `unicode-range`). Use `font-display: swap` so text is visible
immediately.

**Don’t**

Don't load a 5MB font file containing every language in the world for an
English site.

## What Roadie handles

The design system already takes care of several performance concerns. You get
these for free.

### Fluid typography

Sizes `text-lg` and above use `clamp()` for fluid scaling. No manual
breakpoint overrides needed.

### CSS-only utilities

Intent, emphasis, and interaction utilities are pure CSS. No JavaScript
runtime cost for styling.

### Tree-shakeable components

Components are built with `tsdown` in unbundle mode. Every leaf compiles to
its own dist file, so subpath imports only drag in what you use.

### Reduced motion

A global `prefers-reduced-motion` reset neutralises all transitions and
animations automatically.

## Quick reference

How performance rules map to existing foundation pages.

| Rule                  | Foundation                                                                                 | What it covers                                       |
| --------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
| No CLS                | [Layout](https://ticketsolutionsptyltd.github.io/roadie/foundations/layout.md)             | Grid defaults, explicit sizing, and gap over margin  |
| Font optimisation     | [Typography](https://ticketsolutionsptyltd.github.io/roadie/foundations/typography.md)     | Fluid scaling, font families, and preloading         |
| Compositor animations | [Motion](https://ticketsolutionsptyltd.github.io/roadie/foundations/motion.md)             | Transform and opacity transitions, and motion tokens |
| Reduced motion        | [Motion](https://ticketsolutionsptyltd.github.io/roadie/foundations/motion.md)             | Global `prefers-reduced-motion` reset                |
| Optimistic UI         | [Interactions](https://ticketsolutionsptyltd.github.io/roadie/foundations/interactions.md) | Loading states and feedback patterns                 |

Docs updated 9 Oct 2026.
