Performance in the Age of AI, Part 2
DiveJourney was getting more traffic from Google, and I was starting to notice how slow it felt on my phone. Great combination.
I'd spent months using coding agents to build out the site: destination guides, more languages, maps, nearby spots, shop information, analytics, and all the little interactions that make those features useful. I was happy with how much I could ship. But when I opened the product as a user, the accumulated weight was becoming pretty obvious.
Search Console agreed. Both mobile and desktop Core Web Vitals had gone red.
On July 13, I measured seven representative production pages. Their mobile PageSpeed scores ranged from 38 to 59. Lighthouse’s average Largest Contentful Paint, or LCP, was 8.86 seconds, with the worst page at 14.2 seconds. Those were synthetic test results, not a stopwatch on every visitor, but they gave me plenty to investigate.
In Part 1, I wrote about making DiveJourney’s tests and development tools fast enough to keep up with the agents. Meanwhile, I’d let the actual website get heavy. This was the other half of that work.
I used to live in Chrome DevTools
Performance optimization has always been one of my favorite parts of frontend engineering. I like having something concrete to investigate: a late image request, too much JavaScript running on load, a page that jumps around while you’re trying to use it. Change something, reload, and find out whether it helped.
So discovering Chrome DevTools MCP was particularly exciting for me. It lets a coding agent control and inspect a Chrome browser, including performance traces, network requests, and console output.
I used to live in Chrome DevTools for this stuff. Now I could hand Chrome DevTools to an agent.
That meant I didn't have to inspect every waterfall myself and translate the findings into instructions. The agent could open a slow page, investigate what was happening, connect it to the implementation, make a change, and run the page again. I still directed the work and checked the product, but much more of the investigation could happen without me sitting between the browser and the code.
It brought back a part of engineering I really enjoy. I was still working through the performance problem, just with agents handling the implementation and much of the repetitive checking.
And this was a particularly good task for them. There was a specific page to inspect and a measurement to compare after each change. An answer like “this should reduce the bundle size” wasn’t the end of the task. The agent could go find out whether it actually did.
Giving it something useful to measure
I didn't want a fast homepage and a slow rest of the site. A country guide, a destination planning page, and a dive-spot detail page load different data and have different things competing to appear first.
The original seven-page test set included the homepage, a Spanish destination guide, country and destination-section pages, and dive spots. Later comparisons also covered wildlife pages. I added extra pages from the same categories to check beyond the URLs being used for day-to-day tuning.
Google PageSpeed Insights supplied the external baseline. When the public API hit its quota, I had the agents build an importer for the raw Lighthouse results embedded in the generated report pages. That preserved the actual measurements rather than leaving me with screenshots and numbers copied into a chat.
For the repeated runs, I used Lighthouse CI against production builds running locally, preview deployments, and the deployed site. The main test runs used three cold samples per page, with the browser, device profile, and test settings recorded. Chrome DevTools MCP supplied the traces for diagnosis; Supabase evidence helped explain the data work; Vercel identified the deployed build and exposed runtime errors.
There was some very unglamorous checking involved. One set of valid results came from an older production deployment than the release I thought I was testing. Another run measured a Vercel protection page instead of DiveJourney.
Neither mistake would necessarily produce an obviously broken report. The numbers can look perfectly reasonable while describing the wrong thing. So checking the final URL, real page content, and deployment identity became part of the process, not something to remember afterward.
A local production build let the agents iterate without needing a new deployment for every small change. Deployed checks were a separate step: confirm what had actually reached production.
Two rounds of getting less in the browser's way
There were some obvious fixes. Innerkip Quarry had an approximately 1.1 MiB Mapbox image in its initial loading path. Devana Kandu was loading charts and other interaction before anyone needed them.
But similar problems showed up across unrelated pages. Public visitors were downloading shared client code for sessions, analytics, consent, and features they hadn't touched. Some pages carried more translation data than they needed. Announcements, headers, footers, and content arriving from the server could shift the layout as the page loaded.
The first round was mostly about sorting out what really needed to happen before the main content appeared.
The agents reduced shared client code, kept nonessential analytics and interaction out of the initial load, fixed image sizing and priority, and made the layout more stable. Independent server reads could start together rather than waiting on each other. Content farther down the page could arrive without holding up the heading or hero.
These were familiar frontend optimizations. What changed was how much of the investigation, implementation, and retesting I could hand over. I could ask the agent to investigate a shared cause, have it make the change, and then look at what happened across several different page types.
I came back for a second round in late August. The product had continued growing, and there was more to remove from the initial load.
One useful change was keeping a lightweight server-rendered header visible while delaying the heavier interactive version. The footer, notifications, feedback controls, and other secondary UI could wait until the page settled or someone needed them. The agents also removed an extra display-font request and moved more optional destination, dive-spot, and wildlife work away from the main content.
Some experiments went backward. One made the homepage hero image get discovered later; that was corrected. An inline-CSS experiment didn’t justify its tradeoffs and came back out.
I wasn't trying to remove functionality. Login still had to work. Privacy choices still had to work. A translated page still needed the right copy. Getting the main content onto the screen sooner was useful; quietly turning the rest of the product off wasn't.
What improved
By the end of this work, the recorded gains were substantial. These are the original baseline and the latest captured deployed-site results for JavaScript, blocking, and layout shift, followed by the later field outcome I saw in Search Console.
| Measurement | Starting point | Recorded result |
|---|---|---|
| JavaScript transferred | 1,190 KiB | 493 KiB, roughly 59% less |
| Total Blocking Time | 969ms average | 0ms median |
| Cumulative Layout Shift | 0.114 average | 0.0058 median |
| Search Console Core Web Vitals | Mobile and desktop red | Mobile and desktop green |
The payload, blocking, and layout figures come from tests against the deployed site. The baseline and later tests used different Lighthouse versions, and the table labels averages and medians separately. This is the recorded recovery, not a controlled benchmark of one isolated change.
The last controlled local comparison during the second round was also encouraging. Across the homepage, a destination, a dive spot, and a wildlife page, directly throttled Chrome produced LCP medians of 1.72 to 1.96 seconds. All 12 runs were below 2.5 seconds and met the performance-score, blocking, and layout-shift targets.
Those sub-two-second numbers came from a production build running locally with mobile viewport, network, and CPU constraints. They aren't timings from real visitors, and I wouldn't put them opposite the original hosted PageSpeed LCP and call that a percentage improvement. The controlled comparison is below.
Search Console was the separate real-user check. Its Core Web Vitals report uses rolling CrUX data, so it doesn’t instantly reflect a release. Both mobile and desktop later turned green. That’s the field result behind the headline, not a claim that every Lighthouse score became perfect or that the performance work caused a ranking increase.
Why I stopped chasing the Lighthouse score
There was one part of the second round that took more investigation. The browser could paint the main content quickly while Lighthouse’s normal simulation still predicted a much slower LCP.
I had the agent compare two throttling methods on the same local production build. Each method ran three times on each of four pages, using the same configured mobile viewport, network, and CPU settings.
| Page | Lighthouse simulated LCP | Direct DevTools-throttled LCP |
|---|---|---|
| Homepage | 3.53s | 1.72s |
| Aruba destination | 4.74s | 1.91s |
| Devana Kandu dive spot | 3.95s | 1.88s |
| Tiger shark page | 3.75s | 1.96s |
Lighthouse's default Lantern mode records an initially unthrottled load, then models how it would behave under slower conditions. DevTools throttling applies constraints while the browser loads the page. Both are approximations; the directly throttled result isn't a measurement from an actual phone either. Google's own throttling documentation discusses limitations in both methods.
In this investigation, the traces pointed to how the simulation handled the streamed content and image-loading dependencies. It was attributing seconds of additional delay that didn’t show up in the directly throttled run. The agent could inspect that disagreement instead of treating the score as a complete explanation of the problem.
I didn't take the smaller number and declare everything fixed. Some First Contentful Paint and Speed Index checks still missed the stricter targets. The local fixtures also limited parts of the visual-completeness testing. There was remaining work; there just wasn't evidence that another risky change to consent or session loading would solve the modeled LCP gap.
So I stopped that round there. I wanted the site to be faster, not to spend indefinitely changing working behavior because one test still wanted another number.
I still had to use the site
Moving things out of the initial load creates its own bugs. During this work, review caught missing safety callouts and public dive-log images. The later round caught a deferred header that could replace an open menu, incorrect German fallback copy in delayed controls, and a gallery that could restart a request already in progress.
Those are exactly the sorts of things that can disappear behind a nice performance result. A page with less content can load faster. That doesn't make it the page I meant to ship.
The numbers can say one thing, but the page could just be empty.
I still opened the pages, checked the content, tried the interactions, and used the site on my phone. The automated checks helped with requests, console errors, and regressions, but I was still responsible for deciding whether the change made sense for the product.
What I’d repeat is giving the agent access to the browser early, rather than waiting until it had finished coding to find out what the code did. That was the enjoyable part of this project: a familiar performance problem, the tools I already liked using, and much less manual work between finding something worth changing and seeing the result.
Try it on your own site
Here's a starting prompt for a coding agent in your own repository. It asks the agent to check its access, set up the measurements, and work through a bounded first pass. Replace the bracketed values. It may still need you to approve an MCP configuration change, restart the client, or supply API access.
Improve this site's browser performance without removing useful behavior.
Production URL: [URL]
Important page types: [HOME, PRODUCT, ARTICLE, SEARCH, ETC.]
Critical interactions: [LOGIN, CHECKOUT, LANGUAGE SWITCHING, ETC.]
Work in the current repository. Inspect its stack and existing tooling first.
1. Connect the official ChromeDevTools/chrome-devtools-mcp server using the current instructions for this coding client. Use an isolated test profile, not my personal browser session. Prove access by opening a page and collecting a performance trace, network requests, and console messages. Never claim a tool is connected until a call succeeds.
2. Use authorized PageSpeed Insights API v5 access for mobile and desktop audits of public URLs. Keep keys out of source control and saved reports. If access or quota is unavailable, continue with local Lighthouse CI and label the external check as unavailable. Don't create cloud resources, change billing, deploy, or submit private URLs to external services without approval.
3. Choose representative pages from the main page types. Reserve additional pages to check at the end rather than tuning against every known URL. Establish a baseline on a production build, not the development server. Run three cold samples per page with fixed browser, Lighthouse, viewport, network, and CPU settings. Record those settings, build identity, final URL, and whether the run is local, preview, or production.
4. Inspect LCP and its delay breakdown, FCP, Speed Index, TBT, CLS, JavaScript and total transfer, failed requests, and console errors. Confirm the expected content exists. Exclude login challenges, empty pages, and soft 404s from performance comparisons, but report them as failures to investigate.
5. Start with the largest shared bottleneck supported by the traces. Propose a small change, implement it, and rerun the same tests. Verify content, accessibility, privacy, and critical interactions independently of the score. Don't special-case test URLs, detect audit tools, remove features, or add unjustified caching to improve a benchmark.
6. Recheck the affected page types and reserved pages. If simulated LCP disagrees materially with the browser trace, compare simulation and directly applied DevTools throttling on the same build. Keep both results and their limitations. Never compare a local after result with a hosted before result as though conditions matched.
Finish one bounded pass, then report the changes, comparable before/after table, individual failures, regressions, and recommended next step. Retain raw evidence without secrets. Stop when another change needs more evidence or approval. Don't keep iterating just to force a green score.
Keep lab results, directly throttled browser measurements, and CrUX/Search Console field data separate. A local pass is not proof of production or real-user performance.
Setup references: Chrome DevTools MCP, PageSpeed Insights API, and Lighthouse CI.