28 KiB
Web Performance Reference
A consolidated reference guide covering web performance concepts, optimization techniques, and the Performance API, sourced from the Mozilla Developer Network (MDN).
Table of Contents
- Web Performance Overview
- Performance Fundamentals
- Performance Best Practices
- HTML Performance
- JavaScript Performance
- CSS Performance
- Performance API
- Performance Data
- Server Timing
- User Timing
1. Web Performance Overview
Source: https://developer.mozilla.org/en-US/docs/Web/Performance
Definition
Web performance encompasses:
- Objective measurements (load time, frames per second, time to interactive)
- Perceived user experience of load and response times
- Smoothness during user interactions (scrolling, animations, button responsiveness)
Recommended Timings
| Target | Threshold |
|---|---|
| Page load indication | 1 second |
| Idling | 50ms |
| Animations | 16.7ms (60 FPS) |
| User input response | 50-200ms |
Users abandon sites that respond slowly. The goal is to minimize loading and response times while adding features that conceal latency by maximizing availability and interactivity as soon as possible.
Key Performance Metrics
| Metric | Full Name | Definition |
|---|---|---|
| FCP | First Contentful Paint | First time any content appears |
| LCP | Largest Contentful Paint | Largest content element visible |
| CLS | Cumulative Layout Shift | Visual stability during interactions |
| INP | Interaction to Next Paint | Responsiveness to user input |
| TTFB | Time to First Byte | Server response time |
| TTI | Time to Interactive | Page becomes fully interactive |
| Jank | -- | Non-smooth animation or scrolling |
Performance API Categories
- High-precision timing: Sub-millisecond monitoring via stable monotonic clock
- Navigation Timing: Metrics for page navigation (DOMContentLoaded, load time)
- Resource Timing: Detailed network timing for individual resources
- User Timing: Custom marks and measures
- Long Animation Frames (LoAF): Identifies janky animations
- Server Timing: Backend performance metrics
Related Browser APIs
- Page Visibility API: Track document visibility state
- Background Tasks API (
requestIdleCallback()): Queue non-blocking tasks - Intersection Observer API: Asynchronously monitor element visibility
- Network Information API: Detect connection type for adaptive content
- Battery Status API: Optimize for power-constrained devices
- Beacon API: Send performance data to analytics
- Media Capabilities API: Check device media support
Resource Loading Hints
- DNS-prefetch: Pre-resolve domain names
- Preconnect: Establish early connections
- Prefetch: Load resources before needed
- Preload: Load critical resources early
Monitoring Approaches
- Real User Monitoring (RUM): Long-term trend analysis from actual users
- Synthetic Monitoring: Controlled regression testing during development
2. Performance Fundamentals
Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Performance
Why Web Performance Matters
- Promotes accessibility and inclusive design
- Enhances user experience and retention
- Directly impacts business goals and conversions
Core Components
- Web page loading performance
- Content rendering in browsers
- User agent capabilities and constraints
- Performance across different user groups
Perceived Performance
Metrics focused on user perception rather than raw milliseconds:
- Page load time -- initial content availability
- Responsiveness -- interaction feedback speed
- Animation smoothness -- visual fluidity
- Scrolling smoothness -- scroll interaction quality
Optimization Areas
| Area | Focus | Impact |
|---|---|---|
| Multimedia (Images) | Media optimization based on device capability, size, pixel density | Reduces bytes per image |
| Multimedia (Video) | Video compression, audio track removal from background videos | Reduces file size |
| JavaScript | Best practices for interactive experiences | Improves responsiveness, battery life |
| HTML | DOM node minimization, optimal attribute ordering | Improves load and render time |
| CSS | Feature-specific optimization | Prevents negative performance impact |
Performance Strategy
- Performance budgets: Set limits on asset sizes
- Performance culture: Organizational commitment
- Regression prevention: Avoid bloat over time
- Mobile-first approaches: Responsive images and adaptive media delivery
3. Performance Best Practices
Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Performance/Best_practices
Core Best Practices
- Learn the Critical Rendering Path -- Understand how browsers render pages to optimize performance
- Use Resource Hints --
rel=preconnect,rel=dns-prefetch,rel=prefetch,rel=preload - Minimize JavaScript -- Only load JavaScript needed for the current page
- Optimize CSS -- Address CSS performance factors, load CSS asynchronously when possible
- Use HTTP/2 -- Deploy HTTP/2 on your server or CDN
- Use a CDN -- Significantly reduces resource load times
- Compress Resources -- Use gzip, Brotli, or Zopfli compression
- Optimize Images -- Use CSS animations or SVG when possible
- Implement Lazy Loading -- Load content outside viewport lazily; use the
loadingattribute on<img>elements - Focus on User Perception -- Perceived performance matters as much as actual timing
Asynchronous CSS Loading
<link
id="my-stylesheet"
rel="stylesheet"
href="/path/to/my.css"
media="print" />
<noscript><link rel="stylesheet" href="/path/to/my.css" /></noscript>
const stylesheet = document.getElementById("my-stylesheet");
stylesheet.addEventListener("load", () => {
stylesheet.media = "all";
});
Critical CSS Inlining
- Inline CSS for above-the-fold content using
<style>tags - Prevents Flash of Unstyled Text (FOUT)
- Improves perceived performance
JavaScript Loading
- Use
asyncordeferattributes on script tags - JavaScript only blocks rendering for elements after the script tag in the DOM
Web Font Best Practices
- Font Format Selection: Use WOFF and WOFF2 (built-in compression); compress EOT and TTF with gzip or Brotli
- Font Loading Strategy: Use
font-display: swapto prevent rendering blocks; optimizefont-weightto match the web font closely - Avoid Icon Fonts: Use compressed SVG instead; inline SVG data in HTML to avoid additional HTTP requests
Tools and Measurement
- Firefox Dev Tools
- PageSpeed Insights
- Lighthouse
- WebPageTest.org
- Chrome User Experience Report
window.performance.timing(native Performance API)
Practices to Avoid
- Downloading everything unnecessarily
- Using uncompressed media files
4. HTML Performance
Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Performance/HTML
Main HTML-Related Performance Bottlenecks
- Image and video file sizes (replaced elements)
- Embedded content delivery (
<iframe>elements) - Resource loading order
Responsive Image Handling
Using srcset and sizes for different screen widths:
<img
srcset="480w.jpg 480w, 800w.jpg 800w"
sizes="(width <= 600px) 480px, 800px"
src="800w.jpg"
alt="Family portrait" />
Using srcset for different device resolutions:
<img
srcset="320w.jpg, 480w.jpg 1.5x, 640w.jpg 2x"
src="640w.jpg"
alt="Family portrait" />
Using the <picture> element:
<picture>
<source media="(width < 800px)" srcset="narrow-banner-480w.jpg" />
<source media="(width >= 800px)" srcset="wide-banner-800w.jpg" />
<img src="large-banner-800w.jpg" alt="Dense forest scene" />
</picture>
Lazy Loading
Images:
<img src="800w.jpg" alt="Family portrait" loading="lazy" />
Video (disable preload):
<video controls preload="none" poster="poster.jpg">
<source src="video.webm" type="video/webm" />
<source src="video.mp4" type="video/mp4" />
</video>
Iframes:
<iframe src="https://example.com" loading="lazy" width="600" height="400"></iframe>
Iframe Best Practices
Avoid embedded <iframe> elements unless absolutely necessary. Problems include:
- Extra HTTP requests required
- Creates a separate page instance (expensive)
- Cannot share cached assets
- Separate CSS and JavaScript handling required
Alternative: Use fetch() and DOM scripting to load content into the same page.
JavaScript Loading in HTML
async attribute -- fetches in parallel with DOM parsing, does not block rendering:
<script async src="index.js"></script>
defer attribute -- executes after document parsing but before DOMContentLoaded event.
Module loading -- split code into modules and load parts as needed.
Resource Preloading
<link rel="preload" href="sintel-short.mp4" as="video" type="video/mp4" />
Other rel attributes for performance:
rel="dns-prefetch"-- prefetch DNS lookupsrel="preconnect"-- pre-establish connectionsrel="modulepreload"-- preload JavaScript modulesrel="prefetch"-- load resources for future navigation
Resource Loading Order
- HTML is parsed first in source order
- CSS is parsed; linked assets (images, fonts) start fetching
- JavaScript is parsed and executed (blocks subsequent HTML parsing by default)
- Styling is computed for HTML elements
- Rendering of styled content to the screen
Key Takeaway
HTML is simple and fast by default. Focus on:
- Minimizing bytes downloaded (images and videos)
- Controlling asset loading order (async, defer, preload)
- Reducing unnecessary embedded content (iframes)
- Responsive serving of replaced elements (srcset, picture, media queries)
HTML file size minification provides negligible benefits compared to optimizing media assets.
5. JavaScript Performance
Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Performance/JavaScript
Core Principle
Measure first. Use browser network and performance tools to identify what actually needs optimizing before implementing techniques.
Optimizing Downloads
- Use minimal JavaScript -- avoid frameworks for static experiences
- Remove unused code -- delete functionality not being used
- Leverage built-in browser features: built-in form validation, native
<video>players, CSS animations instead of JavaScript libraries - Minification -- reduces file character count and bytes
- Compression -- gzip (standard) or Brotli (generally outperforms gzip)
- Module bundlers -- use webpack for optimization and code splitting
Loading Critical Assets Early
<!-- Preload standard JavaScript -->
<link rel="preload" href="important-js.js" as="script" />
<!-- Preload JavaScript module -->
<link rel="modulepreload" href="important-module.js" />
Deferring Non-Critical JavaScript
Async:
<script async src="main.js"></script>
Defer:
<script defer src="main.js"></script>
Dynamic loading:
const scriptElem = document.createElement("script");
scriptElem.src = "index.js";
scriptElem.addEventListener("load", () => {
init();
});
document.head.append(scriptElem);
Dynamic module import:
import("./modules/myModule.js").then((module) => {
// Use the loaded module
});
Breaking Down Long Tasks
Tasks taking more than 50ms are "long tasks" that block the main thread. Use task yielding:
function yieldFunc() {
if ("scheduler" in window && "yield" in scheduler) {
return scheduler.yield();
}
return new Promise((resolve) => {
setTimeout(resolve, 0);
});
}
async function main() {
const tasks = [a, b, c, d, e];
while (tasks.length > 0) {
const task = tasks.shift();
task();
await yieldFunc();
}
}
Animation Best Practices
- Reduce non-essential animations
- Provide an opt-out for users on low-power devices
- Prefer CSS animations over JavaScript (much faster and more efficient)
- For canvas animations, use
requestAnimationFrame():
function loop() {
ctx.fillStyle = "rgb(0 0 0 / 25%)";
ctx.fillRect(0, 0, width, height);
for (const ball of balls) {
ball.draw();
ball.update();
}
requestAnimationFrame(loop);
}
loop();
Event Performance
- Remove unnecessary event listeners with
removeEventListener() - Use event delegation (single listener on parent instead of multiple on children):
parent.addEventListener("click", (event) => {
if (event.target.matches(".child")) {
// Handle child click
}
});
Efficient Code Patterns
Batch DOM changes:
const fragment = document.createDocumentFragment();
for (let i = 0; i < items.length; i++) {
const li = document.createElement("li");
li.textContent = items[i];
fragment.appendChild(li);
}
ul.appendChild(fragment); // Single DOM operation
Exit loops early:
for (let i = 0; i < array.length; i++) {
if (array[i] === toFind) {
processMatchingArray(array);
break;
}
}
Move work outside loops:
// Fetch once, iterate in-memory
const response = await fetch(`/results?number=${number}`);
const results = await response.json();
for (let i = 0; i < number; i++) {
processResult(results[i]);
}
Offload Computation
- Asynchronous JavaScript --
async/awaitfor non-blocking I/O - Web Workers -- offload heavy computation to a separate thread
- WebGPU -- use the system GPU for high-performance computations
6. CSS Performance
Source: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Performance/CSS
Rendering and CSSOM Optimization
Remove unnecessary styles:
- Parse only used CSS rules
- Clean up unused styles added during development
Split CSS into separate modules:
<!-- Render-blocking -->
<link rel="stylesheet" href="styles.css" />
<!-- Non-blocking with media queries -->
<link rel="stylesheet" href="print.css" media="print" />
<link rel="stylesheet" href="mobile.css" media="screen and (width <= 480px)" />
Minify and compress CSS as part of the build process with gzip compression on servers.
Simplify selectors:
/* Avoid overly complex selectors */
body div#main-content article.post h2.headline {
font-size: 24px;
}
/* Prefer simple selectors */
.headline {
font-size: 24px;
}
Avoid universal over-application:
/* Problematic */
body * {
font-size: 14px;
display: flex;
}
Reduce HTTP requests with CSS sprites -- combine multiple small images into one file and use background-position.
Preload critical assets:
<link rel="preload" href="style.css" as="style" />
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
Animation Performance
Properties that cause reflow/repaint (avoid animating):
- Dimensions:
width,height,border,padding - Position:
margin,top,bottom,left,right - Layout:
align-content,align-items,flex - Visual effects:
box-shadow
Safe properties to animate (GPU-accelerated):
transformopacityfilter
Respect user preferences:
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
Advanced Optimization
will-change property (use as a last resort only):
.element {
will-change: opacity, transform;
}
CSS Containment:
article {
contain: content;
}
content-visibility (skip rendering until needed):
article {
content-visibility: auto;
contain-intrinsic-size: 1000px;
}
Font Performance
Load important fonts early:
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
Load only required glyphs:
@font-face {
font-family: "Open Sans";
src: url("font.woff2") format("woff2");
unicode-range: U+0025-00FF;
}
Define font display behavior:
@font-face {
font-family: "someFont";
src: url("font.woff") format("woff");
font-display: fallback;
}
Font tips:
- Use only 2-3 fonts maximum
- Prefer web-safe fonts when possible
- Consider
rel="preconnect"for third-party font providers
7. Performance API
Source: https://developer.mozilla.org/en-US/docs/Web/API/Performance_API
Overview
The Performance API is a group of standards used to measure web application performance. It provides built-in metrics and enables developers to add custom measurements to the browser's performance timeline with high-precision timestamps.
Available in both Window and Worker global scopes via Window.performance and WorkerGlobalScope.performance.
Core Concepts
Each performance metric is represented by a PerformanceEntry with: name, duration, startTime, and type.
Most entries are automatically recorded and accessible via:
Performance.getEntries()PerformanceObserver(preferred method)
Main Interfaces
Performance Management:
Performance-- main interface for accessing performance measurementsPerformanceEntry-- base interface for all performance metricsPerformanceObserver-- listens for new performance entries as they are recorded
Custom Measurements:
PerformanceMark-- custom markers on the performance timelinePerformanceMeasure-- custom measurements between two entries
Built-in Metrics:
| Interface | Purpose |
|---|---|
PerformanceNavigationTiming |
Document navigation timings (load time, etc.) |
PerformanceResourceTiming |
Network metrics for resources (images, scripts, CSS, fetch calls) |
PerformancePaintTiming |
Render operations during page construction |
PerformanceEventTiming |
Event latency and Interaction to Next Paint (INP) |
LargestContentfulPaint |
Render time of largest visible content |
LayoutShift |
Page layout stability metrics |
PerformanceLongTaskTiming |
Long-running tasks blocking rendering |
PerformanceLongAnimationFrameTiming |
Long animation frame metrics |
PerformanceServerTiming |
Server metrics from Server-Timing HTTP header |
Usage Pattern
// Using PerformanceObserver (recommended)
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`);
}
});
observer.observe({ entryTypes: ["navigation", "resource", "paint"] });
// Custom measurements
performance.mark("start-operation");
// ... perform work ...
performance.mark("end-operation");
performance.measure("operation-duration", "start-operation", "end-operation");
8. Performance Data
Source: https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Performance_data
Types of Performance Entries
| Entry Type | Interface | Purpose |
|---|---|---|
"element" |
PerformanceElementTiming | Load and render time for specific DOM elements |
"event" |
PerformanceEventTiming | Browser response time to event triggers |
"first-input" |
PerformanceEventTiming | First Input Delay measurement |
"largest-contentful-paint" |
LargestContentfulPaint | Largest paint during page load |
"layout-shift" |
LayoutShift | Page layout shift metrics |
"longtask" |
PerformanceLongTaskTiming | Tasks taking 50ms or more |
"mark" |
PerformanceMark | Custom developer timestamps |
"measure" |
PerformanceMeasure | Custom measurements between timestamps |
"navigation" |
PerformanceNavigationTiming | Navigation and initial page load metrics |
"paint" |
PerformancePaintTiming | Key rendering moments during page load |
"resource" |
PerformanceResourceTiming | Resource fetch duration |
"visibility-state" |
VisibilityStateEntry | Tab visibility state changes |
Accessing Performance Data
Method 1: PerformanceObserver (preferred)
function logEventDuration(entries) {
const events = entries.getEntriesByType("event");
for (const event of events) {
console.log(
`Event handler took: ${event.processingEnd - event.processingStart} milliseconds`
);
}
}
const observer = new PerformanceObserver(logEventDuration);
observer.observe({ type: "event", buffered: true });
Advantages of PerformanceObserver:
- Automatically filters duplicate entries
- Asynchronous delivery during idle time
- Required for some entry types
- Lower performance impact
Method 2: Direct query methods
performance.getEntries(); // All entries
performance.getEntriesByType(type); // Entries of specific type
performance.getEntriesByName(name); // Entries with specific name
Performance Entry Buffer Sizes
| Entry Type | Max Buffer Size |
|---|---|
"resource" |
250 (adjustable) |
"longtask" |
200 |
"element" |
150 |
"event" |
150 |
"layout-shift" |
150 |
"largest-contentful-paint" |
150 |
"visibility-state" |
50 |
"mark" |
Infinite |
"measure" |
Infinite |
"navigation" |
Infinite |
"paint" |
2 (fixed) |
"first-input" |
1 (fixed) |
Handling Dropped Entries
function perfObserver(list, observer, droppedEntriesCount) {
list.getEntries().forEach((entry) => {
// process entries
});
if (droppedEntriesCount > 0) {
console.warn(
`${droppedEntriesCount} entries were dropped because the buffer was full.`
);
}
}
const observer = new PerformanceObserver(perfObserver);
observer.observe({ type: "resource", buffered: true });
JSON Serialization
All performance entries provide a toJSON() method:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(entry.toJSON());
});
});
observer.observe({ type: "event", buffered: true });
Opt-In Metrics
Some metrics require explicit configuration:
- Element Timing -- add
elementtimingattribute to elements - User Timing -- call Performance API methods at relevant points
- Server Timing -- server sends
Server-TimingHTTP header
9. Server Timing
Source: https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Server_timing
What is Server Timing?
Server Timing is part of the Performance API and allows servers to communicate metrics about the request-response cycle to the user agent. It surfaces backend server timing metrics such as database read/write times, CPU time, and file system access.
Server-Timing HTTP Header Examples
// Single metric without value
Server-Timing: missedCache
// Single metric with value
Server-Timing: cpu;dur=2.4
// Single metric with description and value
Server-Timing: cache;desc="Cache Read";dur=23.2
// Two metrics with values
Server-Timing: db;dur=53, app;dur=47.2
// Server-Timing as trailer
Trailer: Server-Timing
--- response body ---
Server-Timing: total;dur=123.4
Retrieving Server Metrics in JavaScript
Server timing metrics are stored as PerformanceServerTiming entries, accessed within "navigation" and "resource" performance entries via the PerformanceResourceTiming.serverTiming property.
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
entry.serverTiming.forEach((serverEntry) => {
console.log(
`${serverEntry.name} (${serverEntry.description}) duration: ${serverEntry.duration}`
);
// Logs "cache (Cache Read) duration: 23.2"
// Logs "db () duration: 53"
// Logs "app () duration: 47.2"
});
});
});
["navigation", "resource"].forEach((type) =>
observer.observe({ type, buffered: true })
);
Privacy and Security Considerations
- The
Server-Timingheader may expose sensitive application and infrastructure information; metrics should only be returned to authenticated users PerformanceServerTimingis restricted to the same origin by default- Use the
Timing-Allow-Originheader to specify allowed cross-origin domains - Only available in secure contexts (HTTPS) in some browsers
- There is no clock synchronization between server, client, and intermediate proxies; server timestamps may not meaningfully map to the client timeline
startTime
10. User Timing
Source: https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/User_timing
Overview
User Timing is part of the Performance API and allows you to measure application performance using high-precision timestamps. It consists of two main components:
PerformanceMarkentries -- named marks at any location in an applicationPerformanceMeasureentries -- time measurements between two marks
Adding Performance Markers
// Basic marks
performance.mark("login-started");
performance.mark("login-finished");
// Advanced mark with options
performance.mark("login-started", {
startTime: 12.5,
detail: { htmlElement: myElement.id },
});
Measuring Duration Between Markers
const loginMeasure = performance.measure(
"login-duration",
"login-started",
"login-finished"
);
console.log(loginMeasure.duration);
Advanced measurement from an event timestamp to a mark:
loginButton.addEventListener("click", (clickEvent) => {
fetch(loginURL).then((data) => {
renderLoggedInUser(data);
const marker = performance.mark("login-finished");
performance.measure("login-click", {
detail: { htmlElement: myElement.id },
start: clickEvent.timeStamp,
end: marker.startTime,
});
});
});
Observing Performance Measures
function perfObserver(list, observer) {
list.getEntries().forEach((entry) => {
if (entry.entryType === "mark") {
console.log(`${entry.name}'s startTime: ${entry.startTime}`);
}
if (entry.entryType === "measure") {
console.log(`${entry.name}'s duration: ${entry.duration}`);
}
});
}
const observer = new PerformanceObserver(perfObserver);
observer.observe({ entryTypes: ["measure", "mark"] });
Retrieving Markers and Measures
// All entries
const entries = performance.getEntries();
// Filter by type
const marks = performance.getEntriesByType("mark");
const measures = performance.getEntriesByType("measure");
// Retrieve by name
const debugMarks = performance.getEntriesByName("debug-mark", "mark");
Removing Markers and Measures
// Clear all marks
performance.clearMarks();
// Remove specific mark
performance.clearMarks("myMarker");
// Clear all measures
performance.clearMeasures();
// Remove specific measure
performance.clearMeasures("myMeasure");
Advantages Over Date.now() and performance.now()
- Meaningful names for better organization
- Integrates with browser developer tools (Performance Panels)
- Works seamlessly with other Performance APIs like
PerformanceObserver - Better tooling integration overall