JavaScript is simultaneously the most powerful and most performance-damaging technology in modern web development. It's the primary cause of poor INP, poor TBT, poor TTI, and increasingly large page sizes. Monitoring JavaScript performance requires covering both the static dimension (bundle size) and the dynamic dimension (runtime behavior) — they fail in different ways and require different monitoring strategies.
JavaScript's Performance Impact
Before monitoring anything, understand the performance cost of JavaScript:
Parse cost: The browser must parse JavaScript before executing it. A 300KB JavaScript bundle takes 150-300ms to parse on a mid-tier mobile device — before a single line has executed.
Execution cost: Running JavaScript takes CPU time. Long scripts block the main thread, directly worsening INP and TBT.
Memory cost: JavaScript objects occupy memory. Applications that accumulate objects over time (memory leaks) cause increasing GC pressure, leading to UI jank during garbage collection.
Network cost: JavaScript must be downloaded before it can run. On a 4G connection (4 Mbps), a 300KB gzipped bundle takes about 600ms to download.
Bundle Size Monitoring
What to Track
Total JavaScript size: The sum of all JavaScript downloaded for a page (gzipped, as delivered over the network).
Initial bundle size: JavaScript required to render the initial page (before any lazy loading).
Chunk sizes: Individual JavaScript chunks and their sizes.
Third-party script size: Contribution of third-party scripts to total JavaScript.
Tree-shaking effectiveness: Are unused code paths being eliminated at build time?
Bundle Size Tracking in CI/CD
// package.json
{
"size-limit": [
{
"name": "Initial bundle",
"path": "dist/js/main.*.js",
"limit": "200 kB",
"gzip": true
},
{
"name": "Vendor bundle",
"path": "dist/js/vendors.*.js",
"limit": "300 kB",
"gzip": true
},
{
"name": "All JavaScript",
"path": "dist/js/*.js",
"limit": "600 kB",
"gzip": true
}
]
}
Run npx size-limit in your CI pipeline. It fails the build if any budget is exceeded and shows what changed:
Package size: 287.4 kB with all dependencies, minified and gzipped
Time limit (LTE): 5.7 s
Size limit: 300 kB
main.js: 156.2 kB
vendors.js: 131.2 kB
Limit: 300 kB
Size: 287.4 kB
Status: passed
Time: 5.7 s
Bundle Analysis for Size Debugging
When bundle size grows unexpectedly, identify the culprit:
# webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
npx webpack-bundle-analyzer dist/js/
# Or for Next.js
ANALYZE=true next build
Common bundle bloat culprits:
- Importing entire lodash (
import _ from 'lodash') instead of individual functions - Date libraries (moment.js is 64KB gzipped; use date-fns or day.js)
- Untreeshakeable dependencies
- Duplicate dependencies (two packages that each include the same sub-dependency)
Runtime JavaScript Monitoring
Long Task Detection
Long tasks (> 50ms) block the main thread and worsen INP. Monitor for long tasks in production:
// Monitor long tasks with PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn('Long task detected:', {
duration: entry.duration,
startTime: entry.startTime,
scripts: entry.attribution
.map(a => a.name)
.filter(Boolean)
});
// Send to monitoring
analytics.track('long_task', {
duration: entry.duration,
url: window.location.pathname,
scripts: entry.attribution.map(a => a.name),
});
}
}
});
observer.observe({ entryTypes: ['longtask'] });
Memory Monitoring
Memory leaks cause gradual performance degradation during a user session:
// Monitor memory usage over time (Chrome only)
function monitorMemory() {
if (!performance.memory) return; // Chrome only
const { usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit } = performance.memory;
analytics.track('memory_usage', {
usedMB: usedJSHeapSize / 1024 / 1024,
totalMB: totalJSHeapSize / 1024 / 1024,
limitMB: jsHeapSizeLimit / 1024 / 1024,
utilizationPct: usedJSHeapSize / jsHeapSizeLimit * 100,
});
}
// Sample every 30 seconds
setInterval(monitorMemory, 30000);
Alert if heap utilization exceeds 80% — it indicates memory pressure that will cause GC pauses.
Error Monitoring
JavaScript errors (uncaught exceptions, promise rejections) are a form of performance failure — they break functionality and degrade experience:
// Global error handler
window.addEventListener('error', (event) => {
analytics.track('js_error', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
url: window.location.href,
});
});
// Unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
analytics.track('unhandled_promise_rejection', {
reason: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
url: window.location.href,
});
});
Framework-Specific JavaScript Monitoring
React
React's key performance issues are unnecessary re-renders and large component trees:
// React DevTools Profiler for development
// In production: use React Profiler API for critical paths
import { Profiler } from 'react';
function onRender(id, phase, actualDuration, baseDuration) {
if (actualDuration > 16) { // Slower than 60fps
analytics.track('slow_render', {
component: id,
phase,
actualDuration,
baseDuration,
});
}
}
<Profiler id="CheckoutForm" onRender={onRender}>
<CheckoutForm />
</Profiler>
Next.js
Next.js provides built-in performance metrics via its Analytics API:
// pages/_app.js
export function reportWebVitals(metric) {
// Includes both Web Vitals and Next.js-specific metrics
// (Next.js-TTFB, Next.js-Route-Change-to-render)
analytics.track('next_vitals', metric);
}
Monitoring JavaScript in CI vs Production
In CI/CD: Monitor bundle sizes and run static analysis. Catch regressions before deployment.
In production: Monitor runtime behavior — long tasks, memory usage, errors, INP. These require real user data.
Both together: Lab performance tests (Lighthouse) in CI catch many issues; real user monitoring in production catches the rest.
AzMonitor's performance monitoring tracks your pages' JavaScript performance metrics over time, alerting you to regressions caused by new JavaScript being added. Start monitoring JavaScript performance alongside your uptime monitoring for comprehensive coverage.
Related: performance regression detection in CI/CD for the full pipeline setup.
3 monitors free forever · No credit card needed · Set up in 2 minutes
Start monitoring free →