The metrics that matter.
| Metric | What it measures | Good (p75) | Poor (p75) |
|---|---|---|---|
| LCP | Time to render the largest visible element. | ≤ 2.5 s | > 4.0 s |
| INP | Worst interaction-to-next-paint delay across the session. Replaced FID in March 2024. | ≤ 200 ms | > 500 ms |
| CLS | Cumulative layout shift — how much the page jumps around. | ≤ 0.1 | > 0.25 |
| TTFB | Time to first byte. Floors LCP — not measured directly by CWV but worth tracking. | ≤ 0.8 s | > 1.8 s |
Google scores you on the p75 of real user data, not your dev machine. "Fast on localhost" is a vanity metric. The agent needs your field data to do anything useful — either CrUX (free, lagging by 28 days) or your own RUM (live, what you actually want).
Data in, fixes out.
The single biggest difference between agents that fix performance and agents that hand-wave is the inputs in the prompt. The minimum kit:
- Lighthouse JSON for the route, generated locally or from PageSpeed Insights. Save as
lighthouse.json; the agent can read it. - Bundle report. For Next.js,
@next/bundle-analyzer's JSON output. For Vite,rollup-plugin-visualizerwithjson: true. For everything else,esbuild --metafile. - RUM snapshot. P75 LCP / INP / CLS, ideally broken down by route and device class. CSV is fine.
- The route tree.
tree app -L 4output for Next or your equivalent. Without this, the agent doesn't know what's a server component and what's not.
Drop these in a perf/ folder in the repo (gitignored), reference them in the prompt. The agent will read them. Without them, you're getting generic advice.
LCP: the prompt that works.
LCP is the most fixable metric. The agent's success rate is high if you point it at the right inputs and the right culprits.
The route /products/[slug] has p75 LCP of 3.8s on mobile (target: <2.5s). Inputs: - perf/lighthouse-product.json - perf/bundle-report.json - The route file: app/products/[slug]/page.tsx Find the LCP element. Check, in order: 1. Is it an <img>? Does it have width/height, priority, and a sized srcset? 2. Is it blocked by a render-blocking request? (CSS or sync JS) 3. Is it inside a client component that gates on JS? Promote to server. 4. Is there a font swap that delays it? Add font-display: swap and preload. 5. Are we waiting on a third-party script? Defer or remove. Propose the smallest diff that wins. Do not refactor. Show before/after of the suspected element only.
Note what the prompt does not do: it doesn't ask "make LCP better." It hands the agent a fixed checklist in priority order. The model is good at executing a checklist against real data; it's poor at picking the right priority unprompted.
INP: the harder one.
INP is harder because the cause is almost always main-thread work, and main-thread work is harder to inspect statically. The trick is to pair the agent with the right trace.
- Capture a real interaction trace. Open DevTools Performance, record, do the slow interaction, save the JSON profile (Chrome > Performance > Save profile).
- Hand it to the agent. "Read perf/interaction-trace.json. Find the longest task on the main thread between the user input and the next paint. Identify the function and propose a fix."
- Common culprits. Synchronous state updates that cascade, layout thrashing in scroll handlers, big chunks of JSON.parse, hydration of huge client trees, third-party tag managers.
P75 INP on /dashboard is 540ms (poor; target <200ms).
Inputs:
- perf/interaction-trace.json (Chrome DevTools profile, recorded during the search-typing interaction)
- The component: app/dashboard/SearchInput.tsx
Find the longest task on the main thread between the user input and the next paint. Likely candidates:
1. A synchronous setState cascade that re-renders the whole list on each keystroke.
2. A useEffect that runs a heavy computation per change.
3. A controlled input with no debounce or transition.
Propose a fix using useDeferredValue, useTransition, or a debounce — whichever matches the actual cause. Do not introduce a new state manager.CLS: usually trivial, usually overlooked.
90% of CLS comes from four causes. An agent fixes any of them in one PR if you ask the right question.
- Images without explicit dimensions.
widthandheightattributes, oraspect-ratio. - Web fonts swapping in.
font-display: optionalfor marketing pages,swapwith a metrics-matched fallback for app shells. - Banners / cookie consent / sponsors injected after layout. Reserve the space; never let content push down.
- Embeds (YouTube, tweets, ads) without a placeholder. Wrap in a fixed-aspect container.
P75 CLS on /article/[slug] is 0.18 (poor).
Audit these surfaces, in order:
1. <img> / <Image> tags missing width/height or aspect-ratio.
2. @font-face declarations without font-display or with mismatched x-height fallback.
3. Late-injected DOM nodes (analytics, consent, sponsor slot) above the fold.
4. Embed wrappers with no reserved space.
Propose the minimum changes that bring CLS under 0.1. Do not migrate to next/image; we are on plain <img>.The unnecessary-client-component hunt.
The single highest-impact Next.js / React Server Components prompt in 2026. Most apps that migrated to App Router have a few dozen accidental 'use client' at the top of components that don't need it — each one pulls its whole subtree into the client bundle.
Scan app/**/*.tsx for files that start with 'use client' but do NOT use any of: - React hooks (useState, useEffect, useRef, useReducer, useMemo, useCallback, useContext, useId) - Event handlers (onClick, onChange, onSubmit, onInput, etc.) - Browser APIs (window, document, localStorage, navigator) - Third-party hooks that require client (useChat, useRouter from next/navigation, etc.) For each match: 1. List the file path. 2. Show the imports — confirm none are client-only. 3. Propose removing 'use client'. 4. Identify which children of this component, if any, also become server. Then estimate the client-bundle reduction in KB using perf/bundle-report.json. Order results by reduction, descending.
Some components legitimately need the boundary even when they don't directly use a hook — e.g., a server component that imports a third-party library that breaks under SSR. Removing every one will introduce hydration errors. Always run the build and a smoke test after the change; let the build tell you which ones were load-bearing.
Build the prompt for your worst metric.
Drop in your p75 numbers and the route; the tool picks the metric to fix first and gives you the prompt to hand the agent. The thresholds are Google's Core Web Vitals — the prompt is the work.
Fix the worst metric first. Don't spread effort across all three. A 30% LCP win on a poor-LCP route moves more p75 users into "good" than 5% improvements on all three combined.
Pitfalls.
Lighthouse runs once, on a simulated mid-tier device, in a clean profile. Your users run a thousand times, on real devices, with extensions and tabs and a cold cache. A 5-point Lighthouse jump that doesn't move RUM is a vanity win.
Lazy-loading the LCP element is how you go from "slow" to "broken." Agents will happily do this if you ask them to "reduce bundle size" without constraints. Be specific: "lazy-load only components below the fold; preserve everything in the initial viewport."
The agent will, if asked, propose a Remix → Next migration on perf grounds. Don't. Migrations are 100× the work of fixing the actual bottleneck, and the new framework has its own surprises. Fix the metric in the framework you're already on.
Without Lighthouse-CI (or Vercel Speed Insights budget alerts, or your equivalent), your win will silently regress in three sprints when someone adds a chart library. The agent can write the assertion; it can't write the discipline.