Web performance optimization
Next.js Performance Engineering
Core Web Vitals work on existing Next.js applications - measured against field data, fixed at the architectural cause, and defended in CI so the numbers stay fixed.
Performance work has a failure mode: a team spends a sprint on optimisation, the score jumps, and six weeks later it is back where it started. Nothing was wrong with the fixes. What was missing was a mechanism to keep them.
How an engagement runs
Measure first, from the field. We begin with Chrome UX Report data and your RUM, segmented by device class and route template. A p75 LCP of 3.4s on mobile product pages is an actionable statement; "our Lighthouse score is 62" is not.
Find the cause, not the symptom. A slow LCP is a rendering decision, a font strategy, an image pipeline or a blocking third party. A poor INP is almost always the size and shape of the JavaScript that hydrates above the fold. We trace each metric to the specific commit-level decision that produced it.
Fix at the architectural level. Moving a component across the server boundary, removing a client-side data fetch that should have been a server render, replacing a 300KB date library, splitting a route that ships the entire admin bundle to anonymous visitors. These are the changes that hold.
Defend the result. Every engagement ends with a performance budget enforced in CI. A pull request that pushes the route bundle past its ceiling fails the check. The budget is the deliverable; the score is a side effect.
Why the score comes back
Because a score is an outcome and nobody owns an outcome. A budget is a number in a file, checked by a machine, on every pull request, and it fails the build of whoever exceeded it while they still remember what they changed.
That difference is the whole reason the second engagement usually does not
happen. Optimisation without a budget is a loan against the next quarter: the
same component gets a use client directive it does not need, the same
library gets imported at the top level, and the regression arrives one
reasonable commit at a time from people who had no way of knowing.
The budget has to measure the right thing to work. A ceiling on the total build output tells you nothing, because the build contains code no route loads. What we set is a ceiling per route on the gzipped JavaScript that route's HTML actually requests - which is the number a visitor pays.
Where the time actually goes
Across the engagements we run, the causes cluster:
A client boundary drawn too high. One directive on a layout ships that layout and everything beneath it to every visitor on every route below. It is the single most common cause of a poor INP in a Next.js application, and it is usually one line. Where the boundary belongs.
An image pipeline that is optimised everywhere except the one that matters. The LCP image lazy-loaded, unsized, or served through a transform that adds 300ms before the first byte. What that costs, and the fix.
A route that became dynamic without anybody deciding it should. One
cookies() call in a shared component opts every route beneath it out of
static rendering, and the CDN stops answering. The page did not get slower
because of the code in it; it got slower because it stopped being cached.
Third parties loaded as though they were free. A tag manager that pulls in four more scripts, a chat widget that hydrates before the content, a consent banner blocking the render it was supposed to gate. These are not application code, which is exactly why nobody profiles them.
Fonts that block the text they are for. A self-hosted font without
font-display, or three weights loaded where one is used above the fold.
What we do not do
We do not optimise against a lab score you chose in advance. If your field data says visitors are fine and a tool says 62, the honest answer is that the tool is measuring a device and a network your audience does not have, and we will tell you that rather than selling a sprint.
We also do not work around a cause we cannot fix by making something else faster to compensate. If the LCP problem is a third party your marketing team requires, the finding says so, and the decision about that trade-off belongs to you rather than being buried in a summary.
What you receive
A prioritised findings document with each issue traced to its cause and sized by effort, the implemented fixes as reviewable pull requests, the CI budget configuration, and a before/after comparison against field data once the change has been live long enough to move the 28-day window.
If the architecture itself is the ceiling - and on some applications it is - that is in the findings too, with the cost of the alternative next to it. That conversation belongs at the start of an engagement rather than at the end of one.
