Performance

Performance isn't a phase at the end — it's the framework defaults and the build pipeline working for you. The panels below are live where they can be: the page measures its own Core Web Vitals, lets you watch OnPush refuse needless work, and shows the template and image contracts that keep the initial render cheap. Everything that touches a browser-only API is guarded so it prerenders and tests the same way.

1 · Core Web Vitals, measured live

These numbers come from this very page via PerformanceObserver, updated through signals as the browser reports them. LCP fills in on load; CLS accrues as things settle; the INP proxy needs you to interact, so click around the page and watch it appear. The thresholds are Google's good / needs-improvement / poor bands.

Largest Contentful Paint Measuring…

Time until the biggest above-the-fold element renders, for the initial page load (LCP finalizes at first interaction). Good ≤ 2.5s.

Cumulative Layout Shift Measuring…

How much visible content jumps around. Good ≤ 0.1.

Interaction to Next Paint (proxy) Measuring…

Slowest interaction latency. Click around to feed it. Good ≤ 200ms.

2 · OnPush vs default change detection

This component runs ChangeDetectionStrategy.OnPush and its state lives in signals, so Angular only re-checks the view when a signal it reads actually changes — not on every timer, event, or async tick elsewhere in the app. A default component re-checks on all of those. Press the button: the OnPush side updates exactly once per click, with no wasted passes.

OnPush + signalsthis component

0view updates

One update per real change. Unrelated app activity triggers nothing here.

Default strategyfor contrast

0+checks it would run

A default component re-checks on every event in the app, whether or not its data moved — so its real number would climb well past the clicks shown.

3 · @defer for below-the-fold cost

The block below is wrapped in @defer (on viewport). Its content — and in a real app its component and dependencies — is split into a separate chunk that the browser only fetches once it scrolls into view. That keeps it out of the initial bundle, so the first paint ships less JavaScript and the page becomes interactive sooner.

Scroll down inside this box to trigger the deferred block ↓

4 · Incremental hydration

This block is wrapped in @defer (hydrate on viewport). The distinction from panel 3 is the whole point: plain @defer ships nothing for the block on first load and renders a placeholder on the client until the trigger fires. Incremental hydration is the inverse — the content is server-rendered and present in the prerendered HTML, so it paints immediately and is crawlable; only its JavaScript stays dormant. Angular hydrates it in place when you scroll it into view, with no flash, no re-render, and no event-handler gap thanks to event replay. You get the SEO and first-paint of SSR without paying to hydrate below-the-fold work eagerly.

Scroll down inside this box to hydrate the block ↓

Server-rendered and already in the HTML — this text was readable before any of its script loaded. Scrolling it into view hydrated it in place; nothing re-rendered.

5 · NgOptimizedImage

The image below uses Angular's NgOptimizedImage directive. Because width and height are required, the browser reserves the right box before the bytes arrive — no layout shift (CLS). Marking the LCP image priority preloads it and drops lazy-loading, so it paints sooner. The directive also enforces best practices at build time (a missing dimension is a hard error).

Placeholder graphic standing in for an optimised hero image
A fixed 240×135 box reserved up front. The ngSrc points at a documented placeholder path; the contract — ngSrc, width, height, priority — is exactly what a real hashed hero image would use, and the directive enforces it at build time.

6 · Bundle budgets & lazy routes

The cheapest JavaScript is the kind that never ships. This app guards that on two fronts, so a regression is caught in CI rather than in the field.

How this project keeps the bundle honest
Every route in this dashboard is lazy-loaded with loadComponent, so a page's code only downloads when you navigate to it — including this one. On top of that, the build defines bundle budgets: the production build fails if the initial bundle grows past its ceiling. Together they mean adding a heavy page can't silently inflate what every visitor pays on first load.