Measuring In Browser User Experience With Performance API
Ever felt like your beautifully crafted application was running slower than a sloth in a sunbeam; but you couldn’t quite prove it?
You are not alone my friend. Frontend performance can be slippery to measure and even trickier to explain to non-technical teammates. Enter the Performance API your built-in browser friend that helps you measure how fast (or not) your app is running in the wild, from the perspective of real users.
What is the Performance API?
#At its core, the Performance API is a set of tools exposed by modern browsers that help you measure the timing of events in your web application from page loads to custom code executions.
Think of it as a stopwatch for your site, letting you measure things like, understanding page loading time, hen did the user actually see content and find out how long did a particular user interaction take to process.
The Performance API provides a standardised way to get accurate timings, right from the browser, no extra dependencies required- yay!
Despite what you may have heard. Performance isn't just about numbers, it’s about user experience. Slow pages drive users away. But with performance measurements, you’re not just guessing. You're making data-informed decisions to improve your app.
Without following best practices, it’s easy for your code to be deceptively expensive and users expect apps to feel fast, not just look polished (although that can help).
The Core Parts of the Performance API
#Tip
Remember: real users don’t care about how clever your code is. They care about how fast they can interact with your product.
Measuring Performance Timing
#performance.now()
gives you a high-resolution timestamp in milliseconds**,** relative to the page load.
_10const start = performance.now();_10_10// Do something expensive..._10for (let i = 0; i < 1e6; i++) {}_10_10const end = performance.now();_10console.log(`Loop took ${end - start}ms`);
Which is super handy for measuring the execution time of functions or user interactions.
Labelling Performance Timing Measurements
#performance.mark('startFetch')
lets you define named markers in your code and measure the time between them.
_11performance.mark('startFetch');_11_11fetch('/api/dogs')_11 .then(res => res.json())_11 .then(data => {_11 performance.mark('endFetch');_11 performance.measure('fetchDuration', 'startFetch', 'endFetch');_11_11 const measure = performance.getEntriesByName('fetchDuration')[0];_11 console.log(`API call took ${measure.duration}ms`);_11 });
This is great for tracking custom performance events — like API calls, React re-renders, or third-party script loads.
Getting Performance Entries By Type
performance.getEntriesByType()
allows you to get built-in performance timings from the browser.
_10const resources = performance.getEntriesByType('resource');_10_10resources.forEach(entry => {_10 console.log(`${entry.name} loaded in ${entry.duration}ms`);_10});
Perfect for auditing how your fonts, images, or scripts are performing.
Using the Performance Buffer
#When working with the Performance API, it’s important to understand that all your marks, measures, and other performance entries are stored in something called the performance buffer; think of it like a temporary, in-memory log of everything you're measuring.
The buffer holds different types of entries, marks, measures, resource timings, and amongs other things and you can access them using performance.getEntriesByType()
or performance.getEntriesByName()
.
For example:
_10const measures = performance.getEntriesByType('measure');_10measures.forEach((entry) => {_10 console.log(`${entry.name}: ${entry.duration}ms`);_10});
However, the performance buffer is not infinite. Most browsers limit the number of stored entries. Chrome, for example, limits it to 150 entries per type.
Once that limit is hit, older entries are silently dropped, which can make debugging tricky if you're not aware of it.
To manage the buffer effectively you can use performance.clearMarks()
and performance.clearMeasures()
to remove entries you no longer need.
Cleaning Up Performance Buffer
#Here’s a practical cleanup example:
_10useEffect(() => {_10 performance.mark('DogModal-open');_10_10 return () => {_10 performance.mark('DogModal-close');_10 performance.measure('DogModal-visible', 'DogModal-open', 'DogModal-close');_10 performance.clearMarks('DogModal-open');_10 performance.clearMarks('DogModal-close');_10 };_10}, []);
This ensures your measurements are accurate and your buffer stays tidy, a small step that goes a long way in keeping your performance data useful and your debugging experience smooth.
Monitoring Performance Buffer Updates
#What if you want to react to new performance entries in real time? That’s where PerformanceObserver
comes in. This handy API lets you subscribe to the performance buffer and get notified whenever new entries are added, such as marks, measures, or even long tasks.
The PerformanceObserver
works a bit like MutationObserver
or IntersectionObserver
. You create an instance, specify the types of entries you want to observe, and provide a callback to handle them.
Here’s a basic setup:
_10const observer = new PerformanceObserver((list) => {_10 const entries = list.getEntries();_10 entries.forEach((entry) => {_10 console.log(`[${entry.entryType}] ${entry.name}: ${entry.duration?.toFixed(2)}ms`);_10 });_10});_10_10observer.observe({ entryTypes: ['measure', 'mark'] });
This observer listens for any new measure
or mark
entries and logs them immediately when they're added to the buffer.
Isolate Long Running Tasks
You can observe 'longtask'
entries to identify jank and responsiveness issues.
_10observer.observe({ entryTypes: ['longtask'] });
Long tasks are anything that blocks the main thread for over 50ms, super useful for diagnosing those "the UI feels stuck" moments.
Don’t Forget to Disconnect
Like any good observer, you should clean up when you're don
_10observer.disconnect();
Especially in SPA environments or during tests, disconnecting prevents memory leaks and keeps your app running efficiently.
Using the Performance API in a React App
#Let’s say you want to measure how long it takes for a component to mount and render. Here's how you could do it with React hooks.
Tip
Don’t forget to clean up markers if you’re doing multiple measurements in a SPA. Otherwise, things can get cluttered fast.
_17import React, { useEffect } from 'react';_17_17export function DogGallery() {_17 useEffect(() => {_17 performance.mark('DogGallery-start');_17_17 return () => {_17 performance.mark('DogGallery-end');_17 performance.measure('DogGallery-mount', 'DogGallery-start', 'DogGallery-end');_17_17 const measurement = performance.getEntriesByName('DogGallery-mount')[0];_17 console.log(`DogGallery mounted in ${measurement.duration}ms`);_17 };_17 }, []);_17_17 return <div>{/* your dog pictures here */}</div>;_17}
Diagnosing Slow Interactions
#Let’s say you’ve got a button that loads a modal, and users are reporting a delay. Instead of guessing, you can use performance.mark()
_13function openModal() {_13 performance.mark('openModal-start');_13_13 setShowModal(true); // triggers modal UI to appear_13_13 requestAnimationFrame(() => {_13 performance.mark('openModal-end');_13 performance.measure('openModal', 'openModal-start', 'openModal-end');_13_13 const measure = performance.getEntriesByName('openModal')[0];_13 console.log(`Modal appeared in ${measure.duration.toFixed(2)}ms`);_13 });_13}
Using requestAnimationFrame()
here ensures you're measuring after the next paint, giving you a clearer picture of perceived performance.
Tips For Using Performance API Effectively
#Using the Performance API effectively comes down to being intentional with what and how you measure. Start by labelling your marks clearly. For example, names like "data-fetch-start"
or "dog-gallery-render"
will save you loads of debugging time later.
Be mindful to measure only what matters; tracking every single render or minor event can create unnecessary noise and make it harder to focus on real performance issues.
It’s also a good idea to use these measurements in both development and production, but be sure to strip or filter logs in production environments so you’re not cluttering the console or leaking internal details.
Finally, for a more complete view of your app’s performance, combine the Performance API with user-centric tools like Web Vitals which this gives you insight not just into what your app is doing, but how it feels to the user.
Use the Performance API to get real, actionable insights into what your users are experiencing. It’s a core part of building user-friendly, delightful frontend experiences.
Whether you’re debugging a janky animation or optimising API calls, measuring is the first step to improving.
Conclusion
#The Performance API is one of the most underutilised tools in the frontend toolbox, but once you know how to use it, it becomes a powerful ally in building faster, smoother applications.
Understanding how your app feels to your users and improving that with real data is one of the most crucial impacts you can have as frontend engineer.
Keep on coding in the free world,
Reinforce Your Learning
One of the best ways to reinforce what your learning is to test yourself to solidify the knowlege in your memory.
Complete this 7 question quiz to see how much you remember.
Performance
Thanks alot for your feedback!
The insights you share really help me with improving the quality of the content here.
If there's anything you would like to add, please send a message to:
[email protected]Was this article this helpful?

About the author
Danny Engineering
A software engineer with a strong belief in human-centric design and driven by a deep empathy for users. Combining the latest technology with human values to build a better, more connected world.