JavaScript Performance API: Measure Page and App Performance
The Performance API gives you a standard way to measure timing in the browser. You can inspect page load timing, mark custom events, measure code sections, and read resource timing data to understand how your app behaves in real users' browsers.
Quick answer: The Performance API is a browser Web API for high-resolution timing and performance entries. Use performance.now() for accurate elapsed time, performance.mark() and performance.measure() for named spans, and performance.getEntriesByType() or PerformanceObserver to read timing data.
Difficulty: Beginner
You'll understand this better if you know: basic JavaScript functions, how the browser runs web pages, and the idea of measuring time with timestamps.
1. What Is the Performance API?
The Performance API is a set of browser methods and objects that let you measure timing information in a web page. Instead of guessing whether something is fast, you can record exact timestamps and inspect built-in timing entries.
- It is part of the browser Web APIs, not a JavaScript library you install.
- It provides high-resolution time measurements for better accuracy than Date.now().
- It stores timing data as performance entries such as marks, measures, resources, and navigation events.
- It is useful for debugging, profiling, and collecting user-facing performance metrics.
In simple terms, the API helps you answer questions like: How long did this request take? When did my code finish rendering? Which resources slowed down the page?
2. Why the Performance API Matters
Speed affects user experience, conversions, and debugging. The Performance API matters because it turns invisible timing issues into measurable data.
You can use it to:
- Measure how long a function, render step, or API call takes.
- Track page-load and resource-loading behavior.
- Compare changes before and after an optimization.
- Collect performance metrics from real browsers during development or in production.
It is especially useful when a page feels slow but you cannot tell why. Timing data helps you separate network delays, rendering delays, and script work.
3. Basic Syntax or Core Idea
The core idea is simple: create a mark at one point, create another mark later, then measure the distance between them.
Using performance.now()
This method returns a high-resolution timestamp in milliseconds. It is ideal for measuring elapsed time inside one page session.
const start = performance.now();
// Code you want to measure
for (let i = 0; i < 1000000; i++) {
// work
}
const end = performance.now();
const duration = end - start;
console.log(`Took ${duration.toFixed(2)} ms`);This example measures elapsed time by subtracting the start timestamp from the end timestamp.
Using marks and measures
Marks name important points in time, and measures compute the span between them.
performance.mark("fetch-start");
// Do something you want to time
performance.mark("fetch-end");
performance.measure("fetch-duration", "fetch-start", "fetch-end");
const entries = performance.getEntriesByName("fetch-duration");
console.log(entries[0].duration);The measure entry stores the duration in a reusable object you can inspect later.
4. Step-by-Step Examples
Example 1: Measuring a function
Start with a simple synchronous function. This is the easiest place to learn the API.
function buildList(count) {
const items = [];
for (let i = 0; i < count; i++) {
items.push(`Item ${i}`);
}
return items;
}
const start = performance.now();
const result = buildList(50000);
const duration = performance.now() - start;
console.log(`Created ${result.length} items in ${duration.toFixed(2)} ms`);This pattern is useful when you want to compare two implementations of the same logic.
Example 2: Measuring async work with marks
Marks are convenient when the work starts now and ends later, such as after a request finishes.
async function loadUser() {
performance.mark("user-load-start");
const response = await fetch("/api/user");
const data = await response.json();
performance.mark("user-load-end");
performance.measure("user-load", "user-load-start", "user-load-end");
return data;
}
loadUser().then(user => {
console.log("User loaded:", user);
});This example measures total user-loading time, including network and JSON parsing.
Example 3: Reading navigation timing
The browser records built-in navigation timing data for page loads. You can inspect the current page entry to learn how the load progressed.
const [navigationEntry] = performance.getEntriesByType("navigation");
if (navigationEntry) {
console.log("DOM content loaded:", navigationEntry.domContentLoadedEventEnd);
console.log("Page load complete:", navigationEntry.loadEventEnd);
}This is useful for debugging slow loads without manually timing every step.
Example 4: Tracking resource loading
You can inspect network resources such as scripts, stylesheets, images, and API calls after they load.
const resources = performance.getEntriesByType("resource");
for (const resource of resources) {
console.log(resource.name, resource.duration);
}This gives you a resource-by-resource view of what the browser downloaded and how long each item took.
5. Practical Use Cases
The Performance API is most useful when you need numbers instead of guesses. Common use cases include:
- Measuring how long a search, sort, or render function takes.
- Checking whether a new API call is faster or slower than the old one.
- Profiling page startup work such as hydration, initialization, or rendering.
- Inspecting large resource downloads that block the page.
- Sending performance metrics to analytics or monitoring tools.
For example, if a dashboard feels slow, you can measure initial data loading, chart rendering, and image loading separately instead of treating the whole page as one problem.
6. Common Mistakes
Mistake 1: Using Date.now() for precise timing
Date.now() is fine for rough timestamps, but it is less precise and can be affected by clock changes. For short measurements, the Performance API is a better choice.
Problem: This can produce noisy results for small tasks and is not ideal for reliable elapsed-time measurement.
const start = Date.now();
// work
const elapsed = Date.now() - start;Fix: Use performance.now() for elapsed time measurements in the browser.
const start = performance.now();
// work
const elapsed = performance.now() - start;The corrected version gives you high-resolution timing that is much better for performance checks.
Mistake 2: Measuring before async work finishes
If you start a timer before an async task and stop it immediately after calling the task, you only measure the setup, not the full operation.
Problem: The measured value is too small because the promise has not resolved yet.
const start = performance.now();
const promise = fetch("/api/report");
const elapsed = performance.now() - start;Fix: Wait for the async operation to complete before stopping the timer, or use marks around the full awaited flow.
const start = performance.now();
await fetch("/api/report");
const elapsed = performance.now() - start;The fixed version measures the real end-to-end duration instead of only the request start-up cost.
Mistake 3: Forgetting to clear old marks and measures
Marks and measures stay in the browser's performance timeline until you clear them. If you reuse names in repeated tests, old entries can confuse your results.
Problem: The performance timeline can accumulate entries, so later measurements may include old data when you inspect them by name.
performance.mark("task-start");
// work
performance.mark("task-end");
performance.measure("task", "task-start", "task-end");
const sameNamedEntries = performance.getEntriesByName("task");Fix: Clear entries when you are done, especially in repeated benchmarks or tests.
performance.mark("task-start");
// work
performance.mark("task-end");
performance.measure("task", "task-start", "task-end");
performance.clearMarks("task-start");
performance.clearMarks("task-end");
performance.clearMeasures("task");Clearing entries keeps your measurements clean and repeatable.
7. Best Practices
Practice 1: Measure one meaningful segment at a time
Short, focused measurements are easier to interpret than one huge timer around the whole page.
performance.mark("render-start");
// render chart
performance.mark("render-end");
performance.measure("chart-render", "render-start", "render-end");This helps you identify the exact step that needs improvement.
Practice 2: Use clear, descriptive names
Meaningful names make performance data easier to search and debug later.
performance.mark("search-input-start");
// update search UI
performance.mark("search-input-end");
performance.measure("search-input-update", "search-input-start", "search-input-end");Good labels tell future you what was measured without reading the code again.
Practice 3: Clean up temporary measurements
Removing benchmark-only marks and measures prevents the timeline from filling with unused entries.
performance.clearMarks();
performance.clearMeasures();This matters most in loops, test suites, and repeated manual benchmarks.
8. Limitations and Edge Cases
- The Performance API is a browser API, so it is not available in the same way in Node.js code.
- Some timing values are relative to page navigation, not absolute wall-clock time.
- Resource timing may be restricted by browser privacy rules or server headers, especially across origins.
- Older browsers may support only part of the modern Performance API.
- Performance entries are not a full profiler; they measure timing, but they do not explain every CPU hotspot.
- Very small measurements can still vary because browsers share time with other work on the system.
If you want to understand why code is slow at a deeper level, combine Performance API measurements with the browser DevTools performance profiler.
9. Practical Mini Project
Let's build a small performance logger for a page that loads data, renders items, and measures each step. This gives you a realistic pattern you can adapt in your own app.
async function loadAndRenderProducts() {
performance.mark("products-load-start");
const response = await fetch("/api/products");
const products = await response.json();
performance.mark("products-data-ready");
performance.measure("products-fetch", "products-load-start", "products-data-ready");
const list = document.querySelector("#product-list");
list.innerHTML = products
.map(product => `<li>${product.name}</li>`)
.join("");
performance.mark("products-rendered");
performance.measure("products-render", "products-data-ready", "products-rendered");
const fetchTime = performance.getEntriesByName("products-fetch")[0].duration;
const renderTime = performance.getEntriesByName("products-render")[0].duration;
console.log(`Fetch: ${fetchTime.toFixed(2)} ms`);
console.log(`Render: ${renderTime.toFixed(2)} ms`);
performance.clearMarks();
performance.clearMeasures();
}
loadAndRenderProducts();This mini project measures two real parts of a page flow: data loading and DOM rendering. It is a practical pattern for diagnosing slow screens.
10. Key Points
- The Performance API measures timing in the browser with better precision than basic date timestamps.
- performance.now() is best for elapsed time measurements.
- performance.mark() and performance.measure() help you name and compare spans of work.
- Built-in entries like navigation and resource timing reveal how the page loaded.
- Clearing temporary entries keeps repeated measurements accurate and readable.
11. Practice Exercise
Measure two parts of a page interaction: a simulated data fetch and a DOM update. Your goal is to record both durations separately and log the results.
- Create a function that waits briefly using setTimeout to simulate a fetch.
- Add marks before and after the wait.
- Update the page with a list of 10 items.
- Measure both the wait time and the render time.
- Log both durations to the console.
Expected output: Two console lines showing one duration for loading and one for rendering.
Hint: Use different mark names for each segment, and call performance.getEntriesByName() after measuring.
Solution:
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function runExercise() {
performance.mark("exercise-load-start");
await delay(300);
performance.mark("exercise-load-end");
performance.measure("exercise-load", "exercise-load-start", "exercise-load-end");
performance.mark("exercise-render-start");
const list = document.createElement("ul");
for (let i = 1; i <= 10; i++) {
const item = document.createElement("li");
item.textContent = `Item ${i}`;
list.appendChild(item);
}
document.body.appendChild(list);
performance.mark("exercise-render-end");
performance.measure("exercise-render", "exercise-render-start", "exercise-render-end");
console.log("Load:", performance.getEntriesByName("exercise-load")[0].duration);
console.log("Render:", performance.getEntriesByName("exercise-render")[0].duration);
}
runExercise();12. Final Summary
The JavaScript Performance API is the browser's built-in timing toolkit. It helps you measure work with high precision, record named spans with marks and measures, and inspect built-in navigation and resource timings.
For everyday development, start with performance.now() when you need a simple elapsed-time measurement. Use marks and measures when you want readable named spans, and use resource or navigation entries when you need to understand page loading behavior.
As you optimize real pages, keep your measurements focused, clear temporary entries when needed, and interpret the numbers in context. If you want to go further, the next step is learning browser DevTools performance profiling alongside the Performance API so you can measure and explain slow behavior more effectively.