Skip to content

Frontend Performance

217 companion flashcards · AI-assisted study content · Open the deck →

performance, a topic that sits at the heart of building fast, responsive websites and web apps. You'll work through clear definitions of core concepts like Time to First Byte, Largest Contentful Paint, Interaction to Next Paint, and Cumulative Layout Shift, which together form the backbone of how browsers measure user experience. From there, the deck moves into practical techniques such as lazy loading, code splitting, tree shaking, and optimizing critical CSS, giving you a well-rounded view of both the "why" and the "how" of performance work.

It's a great fit for web developers who already know HTML, CSS, and JavaScript and want to deepen their understanding of what makes a site feel snappy versus sluggish. Beginners learning about web performance for the first time will also find it approachable, since the cards start with foundational questions before moving into more specific optimization strategies. If you're preparing for a frontend interview or aiming to improve real-world projects, this deck covers the vocabulary and ideas you'll encounter again and again.

To get the most out of these flashcards, try reviewing them in small, spaced-out sessions rather than cramming everything at once. Concepts like render blocking, main-thread bottlenecks, and bundle size connect to each other, so revisiting them over several days helps the relationships stick. A useful tip when studying: pause after each card and think about how the concept would show up in a real app you have worked on. That small habit turns isolated definitions into practical knowledge you can actually apply when debugging or optimizing your own projects.

Foundations of Frontend Performance

Frontend performance describes how quickly and smoothly a web application loads, renders, and responds to a user's actions. It is not just about raw speed measured in the lab; it shapes user satisfaction, accessibility, search ranking, conversion rates, and the overall perception of product quality. Because users typically judge a site by how fast it feels rather than how long every byte took to download, the discipline cares about perceived performance: how soon the interface becomes useful, how stable it remains while loading, and how snappily it reacts to clicks, taps, and scrolls. This mindset leads naturally to progressive enhancement, where a useful core experience arrives first and richer behavior layers on as device and network conditions allow.

The modern measurement vocabulary centers on Core Web Vitals, a small set of signals Google treats as proxies for user experience. Largest Contentful Paint (LCP) marks when the largest visible element finishes rendering, giving a number for perceived load; a good LCP is 2.5 seconds or faster. Interaction to Next Paint (INP) measures how responsive the page feels by timing the delay between an interaction and the next visual update, replacing First Input Delay in 2024; a good INP is 200 milliseconds or less. Cumulative Layout Shift (CLS) sums unexpected movement of page elements during the lifetime of the page, capturing visual instability; a good score is 0.1 or lower.

Underneath these vitals sit several supporting metrics that explain them. Time to First Byte (TTFB) measures the round trip from request to first byte of the response and exposes network plus server-side latency. First Contentful Paint (FCP) records the first time any text, image, or non-white canvas appears, often used as an early signal that something is happening. Total Blocking Time (TBT) quantifies the total time the main thread was blocked by long tasks during load and correlates strongly with INP in lab conditions. Speed Index and the perceptual speed index add visual completeness into the picture. Together, these metrics form a shared language for performance that any team can monitor and improve.

The most powerful performance habit is treating performance as a product feature rather than a one-time cleanup project. This means setting performance budgets for bundle size, key metrics, and script weight early, and enforcing them during development rather than only auditing at the end. Shipping less code, less media, and deferring anything not immediately needed is the simplest general lever, and it tends to move every metric at once.

The Critical Rendering Path and Resource Loading

To act on performance you have to understand the path the browser walks from bytes to pixels. The critical rendering path is the sequence in which the browser parses HTML, builds the CSSOM, combines it with the DOM into a render tree, lays the tree out, paints pixels, and finally composites layers onto the screen. Anything that delays any of these steps delays first paint. Synchronous JavaScript in the head of the document and stylesheets that the page must use both block this path. Moving scripts to the end of the document or marking them with the defer or async attributes keeps the browser from waiting on them.

The two script attributes behave differently and choosing between them is a frequent performance lever. With defer, the browser downloads scripts in parallel with HTML parsing and executes them in document order after parsing finishes, which is ideal for scripts that depend on one another or on the DOM. With async, downloads still happen in parallel, but execution happens as soon as each script is ready, out of order, which suits independent scripts such as analytics. Beyond script tags, resource hints let you shape what the browser fetches and when. Preload fetches a specific critical resource early without blocking parsing and is best reserved for assets the current page needs immediately. Preconnect opens DNS, TCP, and TLS to a domain you will use soon, while dns-prefetch is a cheaper hint that does only the DNS lookup. Prefetch grabs likely next-navigation resources at low priority, and modulepreload extends preload to capture an ES module's dependency graph.

Priority Hints give the browser more direct guidance. The fetchpriority attribute on a tag such as a hero image, combined with preload, ensures the LCP image starts downloading immediately. The HTTP layer also plays a role: HTTP/2 server push historically let servers send resources before the client requested them, but it has largely been deprecated in favor of 103 Early Hints, which let the server share preload links ahead of the main response without locking the client into receiving them.

Reducing the request waterfall is often as important as any individual optimization. Chained requests, where the browser discovers a needed resource only after parsing the response to a previous one, force sequential latency. Preloading the LCP image, preconnecting to third-party origins, and shrinking the dependency graph between the document and its critical assets collapse this chain and shorten the time to a fully usable page.

Network, Compression, and Delivery

The transport layer shapes perceived performance as much as anything in your application code. HTTP/2 introduced multiplexing so many requests share a single connection without head-of-line blocking at the HTTP layer, along with header compression via HPACK that meaningfully shrinks cookie-heavy traffic. HTTP/3 goes further by replacing TCP with QUIC over UDP, eliminating TCP head-of-line blocking entirely and shrinking handshakes with zero round-trip resumption. TLS 1.3 reduces the handshake to a single round trip and zero round trips for known sessions, and the alt-svc header lets servers advertise HTTP/3 so browsers can upgrade future connections. HTTP/2 prioritization still matters: the browser hints which resources are critical, and a well-configured server delivers them in the right order.

Compression matters as much as the protocol version. Brotli produces payloads 15 to 25 percent smaller than gzip on typical text, which often translates to hundreds of milliseconds on a slow connection. The modern strategy is to pre-compress static assets with Brotli at build time, since compression is CPU intensive, and fall back to gzip for dynamic content where per-request CPU matters. The browser negotiates encoding via Content-Encoding, and care must be taken to avoid double-compression by intermediary caches that occasionally mangle headers.

A Content Delivery Network serves static assets from edge locations close to users, cutting both latency and origin load, and is one of the highest-leverage performance investments for many sites. Beyond edges, the cost of a single connection matters: connection coalescing lets a browser reuse one HTTP/2 connection for multiple domains that share an IP and certificate, and keep-alive avoids repeated TCP setup. DNS itself is not free; the first request to a new domain typically costs 20 to 100 milliseconds, so preconnecting to every critical origin in the document head compounds into noticeable savings.

Caching policies keep performance from drifting as users return. Cache-Control sets freshness and lifetime, while ETag enables conditional revalidation when files do change. Long-lived hash-based filenames such as app.[contenthash].js paired with a Cache-Control directive of public, max-age=31536000, immutable allow the browser to skip revalidation entirely. For assets that change more often, stale-while-revalidate serves the cached version immediately while fetching a fresh copy in the background, balancing freshness against latency. The Vary header should be used sparingly, since each unique value creates a separate cache entry and erodes hit rates.

Images, Fonts, and Media

Images are usually the heaviest assets on a page, and they are also the assets most directly tied to perceived speed and layout stability. Three choices matter: format, size, and loading timing. AVIF now offers the best compression for most photographic content, with WebP as the broadly compatible fallback and JPEG or PNG reserved for legacy systems. The picture element lets the browser pick among sources by media query or file type, while srcset combined with sizes lets the browser choose a resolution that matches the device and viewport. Setting width and height attributes on the tag, or an aspect-ratio in CSS, reserves space so the image cannot push other content around as it arrives, which directly improves CLS.

Beyond format and resolution, loading behavior shapes both speed and stability. Native lazy loading defers off-screen images and iframes until they are near the viewport. The decoding async attribute on an image hints the browser to decode off the main thread, protecting input latency. A hero image meant to be the LCP element benefits from being preloaded and flagged with fetchpriority set to high so the browser knows to start it immediately. Image CDNs such as Cloudinary, Imgix, and Cloudflare Images transform and optimize assets on demand via URL parameters, which removes manual resizing and format conversion from the build. Progressive JPEGs that load in passes can feel faster than baseline JPEGs for the same total size, and animated GIFs should generally be replaced by muted autoplay loop video, which is one or two orders of magnitude smaller for the same visual effect. Long pages with embedded YouTube iframes should use a facade such as lite-youtube-embed, deferring the iframe until the user actually clicks.

Fonts deserve the same care. Slow web fonts can delay the first useful paint or trigger layout shifts when a fallback is replaced by the real face. The font-display property controls this swap: optional avoids any blocking at the cost of missing the font for some users, while swap shows fallback text immediately and replaces it when the web font arrives, trading a Flash of Unstyled Text for avoiding a Flash of Invisible Text. A Flash of Faux Text shows a synthetic weight or style until the real font loads. Matching metrics with size-adjust and other descriptors further reduces shift on swap.

WOFF2 uses Brotli internally and is typically thirty percent smaller than plain WOFF; subsetting keeps only the glyphs the product actually uses; and variable fonts can replace several weight files with a single one. Self-hosting fonts has largely overtaken third-party font CDNs, because cross-site cache partitioning means a CDN hit is unlikely in modern browsers. Preconnecting to the font origin closes the connection setup gap. SVG icons should be run through SVGO to strip editor metadata, small icons inlined directly, and larger icons served via a single SVG sprite referenced through use. Every one of these choices trades a small amount of build complexity for substantial end-user wins.

JavaScript Bundle Optimization

The fastest JavaScript is the JavaScript you never ship, and the rest of the bundle should still be smaller than you think. A practical strategy splits code into three categories: a vendor bundle that changes rarely and benefits from long cache lifetimes, an application bundle that changes more often, and route-level chunks that load only when needed. Code splitting implements this with dynamic import() or framework equivalents such as React.lazy wrapped in a Suspense boundary, both of which return a promise and split the chunk at the call site. Below the splitting layer, tree shaking removes unused exports from each bundle, but it relies on ES module syntax and side-effect-free declarations. CommonJS dynamic require calls cannot be statically analyzed, so they force bundlers to include the whole module.

The package.json sideEffects field tells bundlers which files have side effects on import, allowing them to delete exports more aggressively. Minification with Terser, esbuild, or swc typically reduces size by 30 to 60 percent. esbuild and swc trade a leaner feature set for much faster build times; Terser is the most mature and configurable but slowest. Source maps should be generated for debugging but excluded from production HTTP responses so that minified code is not trivially deobfuscated in production. Hash-based filenames such as app.[contenthash].js pair perfectly with long-lived caching because the URL changes whenever content changes. A bundle analyzer visualizes what is actually inside each chunk and quickly exposes duplicate dependencies, large libraries, and accidental imports of entire icon sets or full lodash.

Some specific packages are notorious bundle villains. Moment.js is orders of magnitude larger than date-fns, day.js, or luxon. Full lodash defeats tree shaking, but lodash-es or named imports from lodash/debounce preserve it. Massive icon libraries should be replaced with modular imports. Browser support for native APIs such as fetch and IntersectionObserver means most polyfills can be dropped entirely, and remaining polyfills should be served only to legacy browsers via differential serving, so the modern bundle stays lean. Library size audits via tools like bundle-phobia and package-size make cost visible before a dependency ships, and the general rule is that smaller libraries with focused APIs beat large all-in-one packages for performance.

Even with aggressive caching, smaller bundles still help on first visits and after cache eviction, and parse plus compile cost scales with bundle size every time the script runs, which is why shipping less code improves INP. Build tooling choices matter in development: Vite uses esbuild for fast dev startup and Rollup for production, while Webpack is more configurable but slower. Hot Module Replacement preserves state during edits and tightens the feedback loop. Bundle splitting per route ensures single-page applications only ship the code the current view needs, plus shared chunks for code reused across routes.

CSS Performance

CSS affects performance in two distinct ways: bytes over the wire and work on the main thread. The bytes come from unused rules shipped because removing them by hand is tedious. Tools such as PurgeCSS and the Tailwind JIT engine analyze actual usage in markup files and strip selectors that never match, often cutting stylesheet size dramatically. Minification reduces whitespace and shortens identifiers, and CSS-in-JS frameworks should ideally compile-time generate styles rather than inject them at runtime, which keeps both bundle size and CPU work low. Runtime CSS-in-JS frameworks add overhead that compounds during hydration and is one of the easiest stylesheet wins to overlook.

Main-thread cost comes from style calculation and layout cascading. The contain property is one of the cleanest mitigations: layout, paint, size, and content containment each tell the browser that a subtree's work is independent, so updates inside it cannot affect the rest of the page. content-visibility set to auto goes further, skipping rendering of off-screen content entirely until it scrolls into view; on long pages with many sections, this single property can be a major win because the browser stops paying layout and paint cost for content the user has not reached. Critical CSS inlined in the document head lets the browser paint above-the-fold content without waiting on the full stylesheet, shrinking FCP and LCP. Tools such as critical and critters automate extraction at build time based on actual page render.

Avoiding specificity wars and !important rules is mostly a hygiene concern rather than a performance one, but cascades become unpredictable, which slows future optimization work. Following the standard rule that id selectors are stronger than class selectors, which are stronger than element selectors, keeps calculations fast and reasoning easy. As with JavaScript, the simplest CSS performance advice is to ship less of it, prioritize what is needed for first paint, and isolate subtrees whose changes should not ripple outward to the rest of the page.

For replaced elements such as images and video, an aspect-ratio rule ensures layout stability even before the asset arrives, reducing CLS to zero for that element. background-image remains useful for decorative tiling and pattern fills, but it cannot respect loading lazy semantics or srcset, so content images should almost always live in img or picture. Combined, these CSS techniques — containment, content-visibility, critical inlining, and unused-rule elimination — produce measurable wins on real devices, especially on mid-tier mobile hardware where style recalculation is comparatively expensive.

Runtime Performance and Responsiveness

Once the page is loaded, the metric that matters is INP, which captures how long users wait between an interaction and the visible response. INP is bottlenecked by main-thread work, because the same thread parses HTML, runs JavaScript, computes style and layout, paints pixels, and dispatches events. A long task is any JavaScript work that blocks the main thread for more than 50 milliseconds, and every long task during a user interaction extends INP. The Long Animation Frames API captures frames where scripting plus style plus layout plus paint exceed a threshold, giving an even richer signal than long tasks alone. PerformanceObserver exposes long tasks, paint timings, and layout shifts from JavaScript so teams can monitor them in their own analytics.

Three patterns break up blocking work: yield to the browser, run work off-thread, and defer non-urgent updates. The scheduler API provides scheduler.postTask with explicit priorities and scheduler.yield() to voluntarily return control so pending input events can be processed mid-task. Web Workers run JavaScript on a background thread, with Comlink wrapping the postMessage protocol in a promise-based API to make heavy computations feel like normal async calls. requestIdleCallback schedules low-priority work during browser idle periods, while requestAnimationFrame synchronizes visual updates with the repaint cycle and is the right hook for any animation that touches the DOM.

Layout thrashing is a common source of forced synchronous layout: when code writes to styles and then immediately reads layout properties such as offsetWidth or scrollTop, the browser must compute layout to return a value, costing much more than either operation alone. Batching all reads before all writes eliminates this, as does moving measurements into animation frames. The DevTools performance tab exposes these patterns in the main-thread flame chart and identifies which scripts and styles triggered layout, and the will-change CSS hint promotes a layer in advance but should be used sparingly because promoting too many layers hurts memory and paint cost.

Framework-specific tools amplify these primitives. React.memo skips re-rendering when props are referentially equal and is useful for expensive leaves. useDeferredValue marks non-urgent updates so urgent input stays snappy, and useTransition signals that an update may be deferred while the previous UI remains interactive. Automatic batching in React 18 and later consolidates state updates inside event handlers, promises, and timeouts into a single render, removing the need for setTimeout(0) tricks that older code used to escape batching. useLayoutEffect runs synchronously before paint and should be reserved for cases where useEffect would cause visible flicker, since heavy synchronous work there blocks the very update it is meant to support. The general pipeline summary is JavaScript to style to layout to paint to composite, and animations that touch only transform and opacity are compositor-friendly because they skip layout and paint entirely.

Rendering Architecture, Tooling, and Long-Term Strategy

Where you render matters as much as how you render. Server-side rendering produces meaningful HTML before client-side JavaScript finishes, which improves LCP and SEO at the cost of hydration work on the client. Static-site generation pushes that further by precomputing pages at build time, so the server only needs to serve static HTML at request time. Incremental static regeneration blends the two by re-rendering stale pages on demand after a configurable period. Streaming SSR ships HTML chunks as soon as each is ready, and produces earlier LCP for slow connections. React Server Components run only on the server, ship no JavaScript for their output, and can drastically reduce bundle size when used for non-interactive parts of a page.

Where interactivity is concentrated in small pieces, the islands architecture renders mostly static HTML with independently hydrated interactive widgets. Partial hydration takes this further by only shipping JavaScript for components the user actually interacts with. Qwik extends the idea with resumability, serializing application state into HTML and deferring all JavaScript execution until the user interacts with the page, yielding near-zero upfront JS. The trade-off is complexity: each strategy requires careful thought about data fetching, caching, and hydration boundaries, and the cost of hydration for heavy pages should be measured explicitly before committing to one approach.

Tooling closes the loop between local work and production reality. Lighthouse gives a synthetic lab score across performance, accessibility, best practices, SEO, and PWA, simulating a slow 4G connection and a throttled CPU to approximate a mid-tier mobile. WebPageTest offers a gold-standard waterfall and filmstrip across many locations. CrUX, in contrast, is real-user data drawn from Chrome users in the field. Capturing both lab and field data is essential, because lab tests cannot reproduce device diversity, network variance, and the long tail of page states real users encounter. web-vitals.js is the standard lightweight library for sending Core Web Vitals to analytics, and commercial tools such as SpeedCurve, Calibre, and Treo combine synthetic and RUM with alerting against performance budgets.

Long-term performance is a continuous practice rather than a campaign. Setting a performance budget for bundle size, key metrics, and third-party weight, then failing CI when it is exceeded, catches regressions before they ship. Auditing third-party scripts quarterly is critical because they often dominate page weight and main-thread time outside the team's direct control, and outdated tags from marketing or analytics can linger for years. Self-hosting the most critical small third-party assets mirrors them to the origin for predictable cache behavior and version control. Service Workers and Workbox unlock offline-first behavior through strategies such as cache-first, network-first, and stale-while-revalidate, and the Beacon API lets analytics leave during unload without blocking navigation. The most reliable quick wins remain: compress images to AVIF or WebP, preconnect to critical origins, lazy-load below-the-fold media, code-split per route, defer non-critical JavaScript, inline critical CSS, and set proper long-lived cache headers on hashed assets. Combined with continuous monitoring, these habits make performance durable rather than accidental.

Frequently asked questions

What is frontend performance?

Frontend performance is how quickly and smoothly a web application loads, renders, and responds to user input.

Why should fonts be optimized?

Slow web fonts can delay text rendering or cause layout shifts unless loaded and swapped carefully.

What is the critical rendering path?

Sequence of steps the browser takes to convert HTML/CSS/JS into pixels: parse HTML → CSSOM → render tree → layout → paint.

Avoid layout shifts from images?

Always set width+height attributes OR aspect-ratio CSS.

Service Worker for offline?

Intercept requests, serve from cache, sync when online — basis of PWA offline support.

Common React bundle bloat?

Moment.js, full lodash, large icon libraries — replace with date-fns/luxon, lodash-es per-method, modular icons.

Optimize React rendering?

useMemo/useCallback when profiling shows benefit; React.memo for pure children; key stability; avoid unnecessary state in parents.

3G simulation in Lighthouse?

Slow 4G simulated network — represents median field conditions for many users.

Connection coalescing?

Browser may reuse one HTTP/2 connection for multiple domains sharing IP+cert — reduces handshakes.

Webpack vs Vite for build perf?

Vite uses esbuild for dev and rollup for prod — significantly faster dev startup.

Drill this topic

217 flashcards on Frontend Performance — free, no signup needed to start.

Study Frontend Performance flashcards

LearnWiki pages are generated with AI assistance from LearnCoachAssist's reviewed study catalog and may contain errors — verify anything critical against your course materials.