Skip to main content

JavaScript Web Performance APIs

Performance optimization is a crucial aspect of modern web development. To effectively optimize your applications, you need tools to measure performance metrics. Fortunately, browsers provide several Web Performance APIs that help you measure, analyze, and optimize your web applications.

Introduction to Web Performance APIs

The Web Performance APIs are a collection of browser interfaces that enable you to access performance-related information about your web application. These APIs allow you to:

  • Measure page load performance
  • Track resource loading times
  • Create custom performance markers and measures
  • Monitor long tasks that might block the main thread
  • Observe performance entries as they occur

Let's explore the key Web Performance APIs and learn how to use them effectively.

The Performance Interface

The performance object is the entry point to most performance-related functionality in the browser. It's available in the global scope (window.performance).

javascript
console.log(performance);
// Output: Performance {timeOrigin: 1234567890.123, onresourcetimingbufferfull: null, ...}

This object provides methods and properties that give you access to various performance measurements and metrics.

The Navigation Timing API provides detailed timing information about the current page's load performance.

Getting Navigation Timing Data

javascript
// Get timing information about page navigation
const pageNavigation = performance.getEntriesByType('navigation')[0];

console.log(pageNavigation);
/* Output example:
PerformanceNavigationTiming {
name: "https://example.com/",
entryType: "navigation",
startTime: 0,
duration: 985.2000000476837,
initiatorType: "navigation",
nextHopProtocol: "h2",
workerStart: 0,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 2.3000000119209,
domainLookupStart: 2.3000000119209,
domainLookupEnd: 2.3000000119209,
connectStart: 2.3000000119209,
connectEnd: 2.3000000119209,
secureConnectionStart: 2.3000000119209,
requestStart: 13.300000011921,
responseStart: 132.60000002384,
responseEnd: 133.10000002384,
transferSize: 5527,
encodedBodySize: 5227,
decodedBodySize: 17450,
serverTiming: [],
workerTiming: [],
unloadEventStart: 0,
unloadEventEnd: 0,
domInteractive: 305.90000003576,
domContentLoadedEventStart: 305.90000003576,
domContentLoadedEventEnd: 306.00000003576,
domComplete: 985.0000000477,
loadEventStart: 985.1000000477,
loadEventEnd: 985.2000000477,
type: "navigate",
redirectCount: 0
}
*/

Common Navigation Timing Metrics

Here are some useful metrics you can calculate:

javascript
function calculatePageLoadMetrics() {
const navEntry = performance.getEntriesByType('navigation')[0];

// Time to First Byte (TTFB)
const ttfb = navEntry.responseStart - navEntry.startTime;

// Domain lookup time
const dnsTime = navEntry.domainLookupEnd - navEntry.domainLookupStart;

// Connection time
const connectionTime = navEntry.connectEnd - navEntry.connectStart;

// Document download time
const downloadTime = navEntry.responseEnd - navEntry.responseStart;

// DOM processing time
const domProcessingTime = navEntry.domComplete - navEntry.responseEnd;

// Total page load time
const pageLoadTime = navEntry.loadEventEnd - navEntry.startTime;

return {
ttfb,
dnsTime,
connectionTime,
downloadTime,
domProcessingTime,
pageLoadTime
};
}

console.log(calculatePageLoadMetrics());
/* Output example:
{
ttfb: 130.30000001192,
dnsTime: 0,
connectionTime: 0,
downloadTime: 0.5000000000000001,
domProcessingTime: 851.9000000238419,
pageLoadTime: 985.2000000476837
}
*/

Resource Timing API

The Resource Timing API provides detailed timing information for each resource loaded by your page, such as images, scripts, stylesheets, and XHR/fetch requests.

Getting Resource Timing Data

javascript
// Get timing information about all resources
const resources = performance.getEntriesByType('resource');

// Find a specific resource
const imageResource = resources.find(r => r.name.includes('logo.png'));

console.log(imageResource);
/* Output example:
PerformanceResourceTiming {
name: "https://example.com/images/logo.png",
entryType: "resource",
startTime: 145.30000001192,
duration: 85.59999999403954,
initiatorType: "img",
nextHopProtocol: "h2",
workerStart: 0,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 145.30000001192,
domainLookupStart: 145.30000001192,
domainLookupEnd: 145.30000001192,
connectStart: 145.30000001192,
connectEnd: 145.30000001192,
secureConnectionStart: 145.30000001192,
requestStart: 146.90000001192,
responseStart: 228.80000001192,
responseEnd: 230.90000000596,
transferSize: 4518,
encodedBodySize: 4218,
decodedBodySize: 23401,
serverTiming: []
}
*/

Analyzing Resource Loading Performance

This example shows how to analyze the performance of all resources loaded by category:

javascript
function analyzeResourcePerformance() {
const resources = performance.getEntriesByType('resource');

// Group resources by type
const categorized = {
script: [],
css: [],
image: [],
font: [],
other: []
};

resources.forEach(resource => {
const type = resource.initiatorType;

if (type === 'script') categorized.script.push(resource);
else if (type === 'css' || type === 'link') categorized.css.push(resource);
else if (type === 'img') categorized.image.push(resource);
else if (resource.name.match(/\.(woff|woff2|ttf|otf|eot)/)) categorized.font.push(resource);
else categorized.other.push(resource);
});

// Calculate total time and size for each category
const analysis = {};

for (const [category, items] of Object.entries(categorized)) {
const totalTime = items.reduce((sum, item) => sum + item.duration, 0);
const totalSize = items.reduce((sum, item) => sum + (item.transferSize || 0), 0);

analysis[category] = {
count: items.length,
totalTime: Math.round(totalTime),
totalSize: Math.round(totalSize / 1024), // KB
avgTime: items.length ? Math.round(totalTime / items.length) : 0
};
}

return analysis;
}

console.log(analyzeResourcePerformance());
/* Output example:
{
script: { count: 5, totalTime: 320, totalSize: 256, avgTime: 64 },
css: { count: 2, totalTime: 115, totalSize: 45, avgTime: 57 },
image: { count: 8, totalTime: 642, totalSize: 872, avgTime: 80 },
font: { count: 3, totalTime: 225, totalSize: 78, avgTime: 75 },
other: { count: 2, totalTime: 42, totalSize: 12, avgTime: 21 }
}
*/

User Timing API

The User Timing API allows you to create custom performance entries to measure specific operations in your code.

Creating Marks and Measures

javascript
// Create a mark to start measuring
performance.mark('startProcess');

// Simulate some work
for (let i = 0; i < 1000000; i++) {
// Heavy calculation
Math.sqrt(i);
}

// Create another mark to end measuring
performance.mark('endProcess');

// Create a measure between the two marks
performance.measure('processTime', 'startProcess', 'endProcess');

// Get the measure
const measure = performance.getEntriesByName('processTime')[0];
console.log(`Process took ${measure.duration.toFixed(2)} milliseconds`);
// Output example: Process took 15.43 milliseconds

// Clear marks and measures when done
performance.clearMarks();
performance.clearMeasures();

Practical Example: Measuring Function Execution Time

Here's a helper function to easily measure any function's execution time:

javascript
function measureFunctionPerformance(fn, fnName, ...args) {
const markStart = `${fnName}-start`;
const markEnd = `${fnName}-end`;
const measureName = `${fnName}-execution`;

performance.mark(markStart);
const result = fn(...args);
performance.mark(markEnd);

performance.measure(measureName, markStart, markEnd);

const measure = performance.getEntriesByName(measureName)[0];
console.log(`${fnName} took ${measure.duration.toFixed(2)} milliseconds`);

// Clean up
performance.clearMarks(markStart);
performance.clearMarks(markEnd);
performance.clearMeasures(measureName);

return result;
}

// Example usage
function findPrimes(max) {
const primes = [];
outer: for (let i = 2; i < max; i++) {
for (let j = 2; j <= Math.sqrt(i); j++) {
if (i % j === 0) continue outer;
}
primes.push(i);
}
return primes;
}

const primes = measureFunctionPerformance(findPrimes, 'findPrimes', 10000);
// Output example: findPrimes took 12.54 milliseconds

Performance Observer

The PerformanceObserver interface allows you to observe performance entry types and get notified when new entries are created.

Basic Usage

javascript
// Create an observer that will watch for specific performance entry types
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
for (const entry of entries) {
console.log(`${entry.name} (${entry.entryType}): ${entry.startTime.toFixed(2)}ms - duration: ${entry.duration.toFixed(2)}ms`);
}
});

// Start observing navigation, resource, and measure entries
observer.observe({ entryTypes: ['navigation', 'resource', 'measure'] });

// Later when you're done observing
// observer.disconnect();

Tracking Long Tasks

Long tasks (tasks that block the main thread for more than 50ms) can lead to poor user experience. You can use PerformanceObserver to detect these:

javascript
// Create an observer for long tasks
const longTaskObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();

for (const entry of entries) {
console.log(`Long task detected: ${entry.duration.toFixed(2)}ms at ${entry.startTime.toFixed(2)}ms`);
console.log('Attribution:', entry.attribution);
}
});

// Start observing long tasks
try {
longTaskObserver.observe({ entryTypes: ['longtask'] });
console.log('Long task observer started');
} catch (e) {
console.log('Long task observer not supported in this browser');
}

Paint Timing API

The Paint Timing API provides timing information about key rendering events.

javascript
// Get First Paint and First Contentful Paint metrics
const paintMetrics = performance.getEntriesByType('paint');

paintMetrics.forEach(metric => {
console.log(`${metric.name}: ${metric.startTime.toFixed(2)}ms`);
});

/* Output example:
first-paint: 124.55ms
first-contentful-paint: 124.55ms
*/

Practical Application: Performance Dashboard

Let's build a simple performance dashboard that collects and displays various metrics:

javascript
function createPerformanceDashboard() {
// Wait for load event
window.addEventListener('load', () => {
setTimeout(() => {
const dashboard = {};

// Navigation metrics
const navEntry = performance.getEntriesByType('navigation')[0];
dashboard.navigation = {
pageLoadTime: navEntry.loadEventEnd,
domContentLoaded: navEntry.domContentLoadedEventEnd,
timeToFirstByte: navEntry.responseStart,
domInteractive: navEntry.domInteractive
};

// Paint metrics
const paintEntries = performance.getEntriesByType('paint');
paintEntries.forEach(entry => {
dashboard[entry.name] = entry.startTime;
});

// Resource metrics
const resources = performance.getEntriesByType('resource');
dashboard.resources = {
count: resources.length,
totalSize: resources.reduce((sum, r) => sum + (r.transferSize || 0), 0) / 1024, // KB
totalTime: resources.reduce((max, r) => Math.max(max, r.responseEnd), 0)
};

// Display dashboard (in a real app, you'd display this in the UI)
console.table(dashboard);

// You could send this data to your analytics service
// sendToAnalytics(dashboard);
}, 0);
});
}

// Initialize dashboard
createPerformanceDashboard();

Summary

The JavaScript Web Performance APIs provide powerful tools for measuring and monitoring the performance of your web applications. Here's what we've covered:

  • Navigation Timing API: Measure page load metrics like TTFB, DNS lookup, and total load time.
  • Resource Timing API: Track individual resource loading performance.
  • User Timing API: Create custom performance marks and measures.
  • Performance Observer: Subscribe to performance events as they happen.
  • Paint Timing API: Get information about when content is painted to the screen.

By leveraging these APIs, you can identify bottlenecks, measure the impact of optimizations, and ensure a smooth user experience.

Additional Resources

Exercises

  1. Create a function that measures the time it takes to sort an array of 10,000 random numbers using different sorting algorithms.
  2. Build a visual dashboard that shows resource loading times by category (scripts, styles, images) in a bar chart.
  3. Use the PerformanceObserver to detect long tasks and display a warning in the console when tasks take more than 100ms.
  4. Implement a monitoring system that logs navigation metrics to localStorage every time a user visits your site.

Remember that performance optimization is an ongoing process. Regular monitoring and measurement will help you identify issues early and maintain a fast, responsive application.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)