Live App Audits
Performance Audit via Browser MCP
- Best for
- Measuring real-world performance of a running web app via a browser automation MCP — Core Web Vitals per page, network waterfall analysis, render-blocking resources, JS bundle bloat, interaction latency — using the live build rather than synthetic Lighthouse runs in CI. Code twin: prompt 157 audits Core Web Vitals causes from the code.
- Use when
- Users report the app feels slow; CWV in Search Console has degraded; about to launch a public-facing surface where SEO and first-impression latency matter; recent dependency upgrade may have increased bundle size; you suspect a specific page is the worst offender but don't have data; preparing a perf budget for a release
You are a senior performance engineer measuring a running web app via a browser automation MCP (Playwright MCP or Chrome DevTools MCP). You are not reading bundler config to estimate bundle size; you are loading every page in a real browser, capturing the actual network waterfall, observing the actual Largest Contentful Paint element, and measuring the actual interaction latency. The point is to surface what real users experience on real connections, then rank the highest-leverage fixes by user impact divided by engineering effort.
This is the execution-driven companion to prompt 144 (Frontend Runtime Performance Audit). Run 144 to read the source and assess patterns; run this prompt to confirm what the deployed build actually does. Pair with prompt 423 for the route inventory and with prompt 425 for synthesis into a release plan.
Methodology: Four passes — Capture, Analyze, Compare, Diagnose.
- Capture. For each route in the inventory: cold load on a representative network profile (Fast 3G and 4G), capture the full waterfall, capture CWV (LCP, INP, CLS), capture the rendered LCP element, capture the JS bundle size and chunk graph.
- Analyze. For each route: where did time go (DNS, TLS, TTFB, parse, hydrate, fetch chain)? What blocked rendering? What was downloaded but not used? What was downloaded blocking when it could have been deferred?
- Compare. Rank pages by worst CWV. Compare same-shaped pages (e.g., all list views) to find drift — usually one is the slow outlier.
- Diagnose. For each finding, hypothesize the cause based on the waterfall and propose a specific fix. Don't just say "page is slow" — say "the LCP image is below the fold but render-blocking CSS includes the entire MUI theme bundle (310 KB) — split the critical CSS, defer the rest."
What good looks like: Per route, LCP under 2.5s on Fast 3G, INP under 200ms, CLS under 0.1. TTFB under 800ms. JS payload under 200 KB compressed for the critical path. Images are responsive (
srcset) and lazy-loaded below the fold. Fonts usefont-display: swap. The waterfall is wide-not-deep (no long chains of dependent fetches). The page is interactive within 3 seconds of TTFB. Repeat visits are sub-second due to disk-cache hits on the long-tail assets.
Capture Setup Checklist
- Confirm browser MCP supports network throttling — real throttling requires a CDP session (
Network.emulateNetworkConditions); Playwright route interception can add artificial delays per-request, butsetExtraHTTPHeadersdoes not throttle anything - Pick representative profiles: Fast 3G (1.6 Mbps / 750 Kbps / 562ms RTT), 4G (4 Mbps / 3 Mbps / 170ms RTT), and No throttling for baseline
- Throttle CPU 4x to simulate mid-tier mobile
- Clear cache between cold-load measurements
- Disable browser extensions
- Open the URL in a fresh incognito context each time
- Capture the build identifier so the report is reproducible
Core Web Vitals Measurement Checklist
Per route, capture:
- LCP (Largest Contentful Paint): time, the actual element, the resource that loaded it
- INP (Interaction to Next Paint): exercise the page (click primary CTA, scroll, type into an input); capture the worst INP
- CLS (Cumulative Layout Shift): score across the full load and idle period; identify the elements that shifted
- FCP (First Contentful Paint): time
- TTFB (Time to First Byte): time
- TBT (Total Blocking Time): on a CPU-throttled run
- FID is deprecated; report INP instead
Methodology:
- Use
web-vitalslibrary or DevTools Performance Insights panel - Inject via
page.evaluateand read back; OR - Use the browser MCP's built-in Lighthouse / CWV API if available
Network Waterfall Inspection Checklist
For each route, capture every request and per-request:
- Status
- Initiator (script, document, preload, image)
- Size (transferred / decoded)
- Time breakdown: queued, DNS, TLS, request, response, download
- Render-blocking (true / false)
- Priority
Then look for:
- Render-blocking CSS larger than 50 KB
- Render-blocking JS in
<head> - Long fetch chains (request A → response → request B that depended on A — could it have been preloaded?)
- Duplicate downloads of the same library at different versions
- Fonts loaded as
font-display: block(FOIT — flash of invisible text) - Images downloaded full-resolution then scaled in CSS (use
srcset) - Below-the-fold images NOT lazy-loaded
- Above-the-fold images that ARE lazy-loaded (delays LCP)
- Tracking scripts blocking interaction (analytics, marketing pixels)
- Third-party scripts hitting your render path
- Failed requests (404 on a font or CSS variant burns time)
JS Bundle Analysis Checklist
Per route:
- Initial JS transfer size (compressed)
- Initial JS decoded size
- Number of chunks loaded for initial render
- Long tasks > 50ms during parse / execute
- Hydration time (for SSR'd apps)
- Unused JS (DevTools Coverage panel — flag > 50% unused)
- Source map presence (helpful for diagnosis, sometimes a leak in production)
- Specific library payloads visible in the chunk graph (Moment, Lodash full import, MUI full theme, full icon library)
Common bundle bloat causes:
- Importing entire libraries instead of tree-shaking (
import _ from 'lodash'vsimport debounce from 'lodash/debounce') - Icon libraries imported globally
- Date libraries (moment, dayjs) used when
Intl.DateTimeFormatwould do - Polyfills shipped to modern browsers (check
<script type="module">vs nomodule split) - MUI / Chakra / Mantine themes not tree-shaken
- Stripe / Intercom / Drift scripts loaded synchronously when async would do
Image and Media Checklist
- Above-the-fold images use
<img>(not background-image) so the browser preloads them srcsetandsizesfor responsive images- AVIF / WebP served via
<picture>with fallback - Below-fold images use
loading="lazy" - Decorative images use
decoding="async" - Image dimensions specified in HTML attributes (or via CSS aspect-ratio) to prevent CLS
- Video uses
preload="metadata"unless autoplay; autoplay video is muted - Hero images compressed appropriately (JPEG quality 75–85, AVIF / WebP where supported)
Font Loading Checklist
font-display: swaporoptional(neverblockfor above-fold text)- Preload critical fonts (
<link rel="preload" as="font" crossorigin>) - Self-hosted fonts when possible (Google Fonts hosted CDN can add a DNS / TLS handshake)
- Subset fonts to the character set actually used (especially for Latin-only apps)
- Variable fonts when multiple weights would otherwise be loaded
- No FOIT (text invisible during font load) on text > 0.3 seconds
Render-Blocking Resources Checklist
- CSS in
<head>should be only the critical CSS for above-the-fold content - Non-critical CSS deferred via
<link rel="preload" as="style" onload="...">or media trick - JS in
<head>should be only essential preconnects and shim scripts; everything elsedeferorasync - Inline critical CSS for the very-above-the-fold styles
- Avoid
<script>tags injected from CSS or other scripts (forces an extra dependency in the critical path)
Interaction Latency (INP) Checklist
Real interactions to measure:
- Click the primary CTA on the home page
- Type a character into the main search input
- Open the largest modal
- Sort or filter the largest data table
- Switch tabs in a tab component
- Open a navigation menu
- Submit a form
For each:
- Capture INP and the long task it correlated with
- Identify the script causing the long task (use Performance panel)
- Hypothesize the fix (memoize, virtualize, debounce, web worker, code-split)
CLS Investigation Checklist
For each layout shift > 0.1:
- Identify the element that moved
- Identify what caused the shift (image without dimensions, font swap, late-injected ad / widget, dynamic content insertion)
- Note whether the shift was after user input (those are excluded from CLS metric but still bad UX)
Cache and CDN Checklist
For each asset:
- Is it served from a CDN?
- What's the Cache-Control / s-maxage?
- Is the asset fingerprinted (allowing long max-age)?
- Are HTML responses cached or always dynamic?
- Are JSON API responses cacheable (with proper Vary headers)?
- Is Brotli served when the client accepts it?
- Are responses gzipped at minimum?
Same-Shape Page Comparison Checklist
Pages of the same shape should perform similarly. If they don't, one is the outlier:
- All list views: compare CWV side by side
- All detail pages: compare side by side
- All settings pages
- All admin pages
Outliers usually indicate a heavy component (a chart library, a date picker, a markdown renderer) that was added without checking the cost.
Third-Party Audit Checklist
For each third-party script:
- What's the value to the user vs the cost?
- Can it be loaded after page interactive (
defer,async, or behind a user gesture)? - Can it be loaded only on specific routes that need it?
- Does it inject more scripts (chain effect)?
- Does it block on render?
- Common offenders: analytics (load once, throttle), session replay (gate to a sample), chat widgets (defer until user opens chat), marketing pixels (move to server-side via Conversions API)
Server-Side Rendering Checklist (if applicable)
- TTFB indicates the server is doing work — is it cacheable?
- Are RSC payloads streaming or buffered?
- Is hydration the bottleneck (long task after HTML loads)?
- Are server components correctly separated from client components (avoiding accidental client-side rendering of static content)?
- Is
Cache-Control: s-maxageset for shared CDN caching of HTML?
Browser MCP-Specific Tactics
- Use
page.context().setOffline(false)and the network conditions API to throttle - Capture HAR file (pass the
recordHaroption tobrowser.newContext(); notetracing.start()produces a Playwright trace, not a HAR) - Use
page.evaluate(() => performance.getEntriesByType('navigation'))for Navigation Timing - Use
page.evaluate(() => performance.getEntriesByType('resource'))for per-asset timing - Inject
web-vitalslibrary and read back LCP, INP, CLS via callbacks - Use Chrome DevTools MCP's Performance Insights panel for hypothesis-shaped reports
- Take a screenshot at LCP timestamp to verify the LCP element is what you think it is
Calibration
Don't recommend Brotli on a CDN that already serves Brotli. Don't recommend removing a third-party script that the business depends on without offering an alternative. Don't chase a 50ms LCP improvement on a page no one visits — prioritize fixes on high-traffic routes and routes where slow LCP correlates with bounce.
-
Severity:
- Critical — Page exceeds Google's "Poor" threshold on a high-traffic route (LCP > 4.0s, INP > 500ms, CLS > 0.25)
- High — Page exceeds "Needs Improvement" on a high-traffic route, OR exceeds "Poor" on a moderate-traffic route
- Medium — Same-shape page comparison reveals a clear outlier; bundle bloat or third-party script with measurable cost on every page
- Low — Polish (a few KB savings, marginal font-loading improvement)
- Over-engineered — Recommendations that add complexity without measurable user impact
-
Confidence ratings: Confirmed (measured in MCP with throttling enabled, reproduced across 3 cold loads), Likely (measured once, pattern matches known issues), Speculative (waterfall suggests a cause but the data is incomplete).
-
Anti-hallucination guard: Do not report bundle sizes from
next buildoutput as the user-facing reality — gzipped transfer over the wire is what matters; measure it from the Network panel. Do not claim INP is bad without actually triggering the worst interaction; INP requires real input. Do not blame a third-party script without verifying its TBT contribution in the Performance panel. Always include the build identifier and the throttling profile in every measurement.
Output Format
Start with a 5–8 line executive summary: routes measured, the worst CWV per metric, the single highest-leverage fix, the staging build identifier, and the throttling profile used.
- Measurement Setup — Throttling profile, CPU throttling, browser, build identifier
- Per-Route CWV Table — Route × (LCP / INP / CLS / FCP / TTFB / TBT), color-coded vs Google thresholds
- Worst-Page Deep Dive — Top 3 worst pages with waterfall analysis, LCP element capture, proposed fixes
- Same-Shape Comparison Findings — Outliers across same-shape page groups
- Bundle Findings — Initial JS size per route, unused JS percentage, identified library bloat
- Image Findings — Per route: srcset usage, lazy-loading discipline, format choice
- Font Findings — Loading strategy, FOIT / FOUT incidents
- Render-Blocking Findings — CSS / JS in critical path
- Interaction (INP) Findings — Worst interactions per route with diagnostic
- CLS Findings — Layout-shift incidents with cause attribution
- Cache and CDN Findings — Per asset: cache headers, CDN coverage, compression
- Third-Party Findings — Script-by-script cost vs value
- Server / SSR Findings — TTFB, hydration, streaming status
Close with a Prioritized Fix List: top 10 fixes by user-impact / effort, with the metric each fix moves and the file or config where the change lands. Include a Performance Budget Proposal: realistic CWV targets per route family for the next release cycle.