Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
What Changed in Core Web Vitals and Angular Performance Tooling from 2020 to 2026
Since 2020 CLS moved to session windows, INP replaced FID in March 2024, LCP and INP became measurable in Firefox and Safari, and in 2026 soft navigation metrics entered CrUX as experimental; in Angular, hydration, @defer, incremental hydration, zoneless and OnPush became defaults. By the end of this series Bookstore's home page went from 65 to 97 in Lighthouse; we also list the first five things to check in an old Angular 12 project.
Speeding Up Navigations: bfcache, Speculation Rules, Router Preloading and Brotli/zstd Compression
To speed up moving between pages, do not block bfcache for the back button, use Router preloading inside the SPA, Speculation Rules for document navigations and the right compression level. In Bookstore the in-app transition went from 810 ms to 207 ms and the catalog list from 2.8 s to 1.6 s; we measured that Speculation Rules do nothing for routerLink links and that ASP.NET Core's default Brotli level sent the JSON as 160 KB.
Angular SSR, Hydration and Performance: Prerendering, Incremental Hydration, Event Replay and the TTFB Cost
SSR and prerendering put the LCP content into the HTML and lower FCP and LCP compared with CSR, but because SSR renders on every request it adds a slow backend to TTFB; the fix is a short Output Cache. On Bookstore's book page FCP went from 1.28 s to 0.3 s; we also measured a block hydrating late with hydrate on viewport missing the transfer cache, repeating its request and breaking event replay.
Reducing JavaScript Cost: Lazy Routes, @defer Triggers, injectAsync and Bundle Budgets
To reduce JavaScript cost, split startup code by route, defer invisible sections with @defer, load services needed on a click with injectAsync and protect the result with a bundle budget. In Bookstore startup code went from 383 KB to 359 KB and the admin report with Chart.js left the first load, but we also measured LCP getting 200-300 ms worse on lazy pages users land on directly.
Optimizing CLS: Unsized Images, Late Content, Font Swaps and @defer Placeholders
To lower CLS, reserve the space of every piece of late content from the start: sized images, placeholders at their final size and skeletons. We measure the impact × distance calculation of the "Similar books" block added to Bookstore's book page, show that @defer (on viewport) moves the shift to the moment the user scrolls (0.1079 on a phone), and bring CLS to 0 in every scenario with a slot, a skeleton and a margin-collapse fix.
Optimizing INP: Input Delay, Processing and Presentation Delay, Breaking Up Long Tasks and a Smooth List in Angular
To lower INP, find in which phase the worst interaction waits (input delay, processing, presentation delay) and either break up or shrink the long task blocking the main thread. In Bookstore's 2,000-book catalog we show with LoAF that zoneless Angular's change detection runs in a frame callback, and cut INP from 416 ms to 8 ms with computed, the right track, scheduler.yield and CDK virtual scroll.
Image and Font Optimization: AVIF/WebP, srcset, NgOptimizedImage Loaders and Font Subsets with Turkish Characters
Resizing images to their size on screen, serving them in the best format the browser accepts (AVIF, WebP, JPEG) and loading fonts with the right subset and fallback metrics are the two jobs that affect LCP and CLS most. We encode Bookstore's covers by Accept in ASP.NET Core with Magick.NET, generate srcset with an NgOptimizedImage loader and cut LCP from 13.7 s to 1.34 s; we measure ğ, ş and İ falling back with the latin subset and how size-adjust reduces the shift.
Optimizing LCP: Reducing TTFB, Resource Load Delay, Load Duration and Render Delay One by One
To lower LCP, split it into TTFB, resource load delay, load duration and render delay and look at each one; if the image cannot be discovered in the HTML, fetchpriority alone does not help. On Bookstore's home page we measure that NgOptimizedImage priority gains nothing with client-side rendering while prerendering cuts load delay from 1.1 s to 27 ms, and we show the SSR pitfalls (document, transfer cache, UseDefaultFiles).
The Critical Rendering Path: How HTML, CSS and JavaScript Reach the Screen and What Blocks Rendering
The critical rendering path is the longest chain the browser waits on before the first paint; stylesheets in head block rendering and scripts without async/defer block the parser, but not every blocking resource is on the critical path. In the waterfall of Bookstore's home page we measure that fixing the font CSS and analytics does not change FCP because the real chain is main.js, then cut FCP from 2.35 s to 1.10 s with response compression and analytics after load.
Measuring Web Performance Correctly: Lab vs Field, CrUX, p75 and RUM in Angular with web-vitals
Web performance is measured not by a single Lighthouse score but by the 75th percentile (p75) of real users' LCP, INP and CLS, separately for mobile and desktop. The post explains why lab and field disagree, what CrUX collects and which browser can measure what in 2026; we collect RUM with web-vitals 6 in Bookstore's Angular client and compute a per-route p75 with an alarm in ASP.NET Core.