Every 100ms of latency can cost ecommerce applications up to 8% in sales conversion. At scale, that gap is worth millions in revenue, and it rarely announces itself as an error. It shows up as a slower Time to First Byte (TTFB), a larger usage bill, or a deploy pipeline that developers quietly stop trusting.
Our Forward Deployed Engineering team has delivered hundreds of technical audits across code review, web performance, and platform usage. The pattern is consistent: applications launch fast, then personalization, experimentation, and scale erode that baseline one feature at a time. The architecture usually isn't broken. The measurements are missing.
This guide covers the three audit types we run, the diagnostic-first process behind them, the four patterns we find in almost every engagement, and the diagnostics you can run yourself.
Copy link to headingKey takeaways
- Audits pay off at inflection points, not on a schedule. Framework migrations, pre-launch reviews, and any month where usage grows faster than traffic are the moments to measure.
- Start from the gap between intended behavior and production behavior, not from the code. Field data from Speed Insights and usage data from Observability tell you where to read the code.
- Dynamic features don't have to erase static performance. Cache the shell, push dynamic reads down the tree, and move experimentation server-side so the two coexist.
- Sequential data fetching is the most expensive line of code in most apps. Waterfalls multiply both latency and Active CPU time.
- Your build time is a business metric. Slow builds cap deployment frequency, and deployment frequency caps how fast you can respond during revenue-critical windows.
- Check the recurring offenders first: request waterfalls, unoptimized images, oversized React Server Components (RSC) payloads, and middleware that runs on traffic it doesn't need to touch.
Copy link to headingThe tipping point: when performance meets scale
One of our enterprise ecommerce customers hit a common inflection point. Their Next.js application launched with strong performance metrics, but as A/B testing evolved into user recommendations and dynamic pricing, the cost of that complexity landed all at once: TTFB rose 300%, conversions dropped 15%, and compute costs doubled in three months.
The issue wasn't technical debt or poor architecture. It was the inherent tension between static optimization and dynamic features that was left unmeasured. Through a technical audit, the team kept every new feature while cutting TTFB by 70%, returning conversions to baseline, and reducing compute costs by 40%. More importantly, they left with patterns and diagnostics to maintain stable performance as they ship.
Audits deliver the most value at moments like this: before a launch, during a framework migration, or when usage growth outpaces traffic growth.
Copy link to headingThe three audit types
Each audit type targets a different failure mode. Choose based on the symptom, not the calendar:
| Audit type | Run it when | What it examines |
|---|---|---|
| Code review | Framework migrations or architecture changes | Rendering strategy, caching layers, code patterns |
| Web performance | Conversions drop, a launch approaches, or field and lab data disagree | Core Web Vitals, third-party scripts, request waterfalls |
| Usage | Usage grows faster than traffic | Compute, bandwidth, and cache writes per route |
Copy link to headingCode review audits: when architecture needs validation
Code review audits validate rendering and caching strategies during major transitions. When a large ecommerce platform migrated from Pages Router to App Router, our review revealed RSC payloads 3x larger than necessary, data-fetching waterfalls on key pages, and cache configurations that conflicted across layers.
The deliverable is a prioritized action plan, framework-optimized code examples, and a migration roadmap. Following it, the team completed their migration 40% faster than estimated while improving their Core Web Vitals.
Copy link to headingWeb performance audits: when performance impacts revenue
A major retailer came to us with falling conversion rates and no clear cause. The Web Performance Audit isolated three issues: third-party scripts degrading Core Web Vitals, unoptimized images slowing Largest Contentful Paint (LCP), and an API waterfall in the checkout flow.
Fixing those three issues resulted in a 45% shorter Time to Interactive (TTI), a 6% higher conversion rate, and improved Core Web Vitals across the board. Web Performance Audits are most valuable before launches, during framework migrations, and whenever field data and lab data disagree.
Copy link to headingUsage audits: when scale drives costs
What works for thousands of users can break down for millions. One enterprise customer's function costs rose sharply due to inefficient async patterns. The Usage Audit pinpointed async waterfalls causing redundant executions, static pages regenerating far more often than anyone read them, and data caching patterns that produced unnecessary API calls.
Targeted fixes cut function executions by 60% through parallel data fetching, raised cache hit rates by extending Incremental Static Regeneration (ISR) lifetimes and moving to on-demand revalidation, and reduced API calls by 40%. Caching-specific findings from these audits run deep enough that we've documented them separately in Caching audits: five antipatterns that quietly cost performance and money.
Copy link to headingStart with the gap, not the code
Effective audits begin by measuring the distance between intended behavior and production behavior. Reading code first anchors you to what the team meant to build. We assess three dimensions before opening a single file:
- User experience: Field performance from Speed Insights and Web Analytics, friction points in critical flows, and the routes where p75 metrics degrade first
- Resource efficiency: How the application consumes compute, bandwidth, and cache writes, using the Observability dashboard and the
vercel metricsCLI to find routes where cost and traffic have decoupled - Development velocity: Build times, deployment frequency, and the roadblocks that make developers batch changes instead of shipping them
This analysis reliably surfaces the same families of issues in each dimension:
| Dimension | Recurring findings |
|---|---|
| User experience | High TTFB, slow navigations and interactions, redundant resource preloads |
| Resource efficiency | Overactive page generation, bandwidth spikes from unoptimized images, oversized RSC payloads |
| Development velocity | Slow builds, misapplied framework patterns, declining deploy frequency |
Anchoring each finding to its business impact is what makes the fix list a priority list.
Copy link to headingThe four patterns we find in almost every audit
Copy link to heading1. Dynamic features quietly disable static optimization
Teams achieve strong initial performance through static rendering, then watch it degrade as they add personalization. The pattern is most acute in ecommerce, where A/B tests, geo-based pricing, and recommendations each pull another route into dynamic rendering.
The common implementation mistakes:
- Static optimization disabled across entire routes to serve one dynamic element
- Page-level data fetches that block rendering
- Client-side A/B testing that ships layout shift to every visitor
The fix is structural, not a rollback. Keep as much of the page as possible static or incrementally revalidated, and opt into dynamic rendering only for the components that need it:
- Cache the page shell and stream the dynamic parts. In Next.js, wrap only the personalized component in a Suspense boundary so the header, hero, and footer render from cache. With Cache Components in Next.js 16, mark cacheable work explicitly with
use cache. - Push data fetching down to the component that needs it, rather than blocking the route on a page-level fetch.
- Move experimentation server-side with Edgenivo Flags so variants resolve before the response, eliminating client-side flicker and layout shift. Scope any flag evaluation in Routing Middleware to the flags a route actually reads, because precomputing every flag on every request multiplies cache variants until nothing is reused.
Typical outcomes from restructuring rather than removing features:
- 40 to 60% lower compute costs
- Conversion rates back at baseline
- TTFB improvements across all regions
The diagnostic: curl your highest-traffic routes and read the response. If a route renders dynamically, ask which specific input forced it there. If the answer is a cookie or flag the page never renders, the boundary is in the wrong place.
Copy link to heading2. Resource waste compounds at scale
Small inefficiencies are invisible at thousands of requests and dominant at millions. Fluid compute with Active CPU pricing has removed the cost of idle time (you pay CPU rates only while code executes), which makes the remaining waste easier to see: it's almost always work your code didn't need to do.
The offenders we find most often:
- Sequential API calls where parallel calls would work. A waterfall of three 200ms requests holds the response for 600ms when it could finish in 200ms, multiplying both latency and instance lifetime.
- Unoptimized media. Images without dimensions cause layout shift and bandwidth spikes; autoplay video served from the
publicfolder loads in full on page load instead of streaming from Edgenivo Blob or a CDN with lazy loading. - A second CDN in front of Edgenivo's CDN, which strips cache-control fidelity and adds a hop.
- Middleware running on all traffic when it only needs to protect specific routes, including static assets and files with extensions.
The waterfall fix is often one line:
const product = await getProduct(slug);const reviews = await getReviews(slug);const related = await getRelated(slug);Each request waits for the previous one (600ms total)
For images, define dimensions and let the Next.js Image component serve optimized formats automatically. For middleware, tighten the matcher so static files never invoke compute.
Typical outcomes:
- 30 to 40% less bandwidth
- 50% fewer function invocations
- Cache hit rates around 90% globally
The diagnostic: In the Observability dashboard, sort routes by Active CPU and bandwidth, not by request count. The routes where cost rank and traffic rank diverge are where the waste lives.
Copy link to heading3. App Router migrations stall on Pages Router patterns
The App Router unlocks server components, streaming, and granular caching, but bringing Pages Router habits into it results in applications that are slower than what they replaced.
The issues we find in migration audits:
- Oversized RSC payloads sending entire objects across the server/client boundary when the client needs two fields
- Incorrect
use clientboundaries that pull whole subtrees into client JavaScript - Async work left in components that could move to the server
- Missing cache configuration on data fetches that were implicitly cached before
The approach that works:
- Migrate incrementally. Pages Router and App Router run side by side in the same application, so you can move route by route instead of betting a quarter on a big-bang cutover.
- Profile RSC payload size in Chrome DevTools and pass specific fields across boundaries instead of whole objects, which also prevents unnecessary re-renders.
- Place Suspense boundaries deliberately so streaming delivers real content, not a route-wide fallback.
- Adopt the current caching primitives:
use cachewithcacheTagandcacheLifefor reads, paired withrevalidateTagorupdateTagon every write. Give the cache a single owner in the data access layer rather than scattering fetch options across components.
Typical outcomes:
- Migrations completed 40% faster than estimated
- Client-side JavaScript reduced by 50%
- Improved Core Web Vitals across all metrics
The diagnostic: Open a migrated route in DevTools and inspect the RSC payload and client bundle. If the payload contains fields no component renders, or the bundle contains components with no interactivity, the boundaries need to move.
Copy link to heading4. Slow builds cap how fast you can respond
Developer velocity is a production concern. When builds take 20 minutes, teams batch changes, deploy less often, and can't ship a fix during the windows that matter most, like Black Friday through Cyber Monday. Deploy cadence also interacts with caching: as we cover in the caching audits guide, each production deployment starts a fresh ISR cache, so both too many deploys and too-slow deploys carry a cost.
The build-time issues we find map directly to their fixes:
| Issue we find | Fix |
|---|---|
| Bundling with webpack when Turbopack (the default in Next.js 16) builds 2 to 5x faster | Upgrade to Turbopack builds |
| Barrel files forcing the bundler to trace entire libraries for one import | Import from specific module paths |
| Monorepos rebuilding unchanged packages | Enable Turborepo remote caching so unchanged packages replay from cache in seconds |
| Third-party dependencies weighing down builds and client bundles | Audit dependencies and remove anything unused |
Typical outcomes:
- Build times cut in half
- Client bundles reduced by 33%
- A measurable increase in deployment frequency
The diagnostic: Compare your median build time this quarter to two quarters ago, then overlay deployment frequency. If builds got slower and deploys got rarer, velocity is decaying and the trend line tells you how fast.
Copy link to headingHow we audit
Every finding follows the same sequence: measure, then move. The teams we audit are almost never missing effort; they're missing the process to see what their application is actually doing.
| Pattern | Symptom | Diagnostic |
|---|---|---|
| 1. Dynamic features erase static wins | TTFB climbs as features ship | Check which input forces each high-traffic route dynamic |
| 2. Resource waste at scale | Usage grows faster than traffic | Sort routes by Active CPU and bandwidth, not request count |
| 3. Stalled App Router migration | New routes slower than old ones | Inspect RSC payload size and client bundle per route |
| 4. Slow builds | Deployment frequency declining | Trend build time against deploy count per week |
Copy link to headingTry with an AI coding agent
You can run a first-pass version of this audit yourself by giving this prompt to a coding agent such as Claude Code or Cursor. Before you start, install the Edgenivo CLI and link your project with vercel link. The prompt instructs the agent to work read-only: unless you edit it, it won't deploy, purge caches, or change configuration.
# Technical audit: performance, cost, and velocity first pass You are auditing this repository and its linked Edgenivo project. Work read-only: never deploy, never purge caches, never call revalidation endpoints. Prefer `--format json` on all `vercel` commands and cite evidence (file:line, metric values, response headers) for every finding. ## Preflight 1. Confirm CLI auth and project link: `vercel whoami`, check `.vercel/project.json`. Record the production domain. 2. Check metrics access: `vercel metrics list --format json`. If request or ISR metrics are unavailable, say so and run the code + HTTP checks only. Never guess numbers you can't read. 3. Detect the framework version and bundler (Turbopack vs webpack), and whether `cacheComponents` is enabled in next.config. ## Pattern 1 — Dynamic rendering where static would work - List the 10 highest-traffic routes: `vercel metrics vercel.request.count --group-by route -a sum --since 7d --prod --format json` - For each, `curl -s -D - https://<prod-domain>/<route>` and record `x-vercel-cache`. For dynamic routes, find in code which input forces dynamic rendering (cookies(), headers(), searchParams read high in the tree, force-dynamic). - Flag any dynamic route where the forcing input isn't rendered in the document. ## Pattern 2 — Resource waste - Rank routes by compute, not traffic, and flag routes whose cost rank far exceeds their traffic rank. - Code check: sequential awaits that could be Promise.all, images without dimensions or next/image, video served from /public, middleware matchers that include static assets or files with extensions. ## Pattern 3 — App Router migration debt - Flag: whole objects passed across server/client boundaries, `use client` on components without interactivity, fetches missing cache configuration, and route-wide Suspense boundaries with spinner-only fallbacks. - Count distinct cache tags; flag write paths (Server Actions, webhooks) that don't pair with revalidateTag or updateTag. ## Pattern 4 — Build and deploy velocity - `vercel ls --prod`: count production deploys over 7d and 30d. - From build logs, record median build time. Flag webpack usage where Turbopack is available, barrel-file imports of large libraries, and missing Turborepo remote caching in monorepos. ## Report format Rank findings by Impact (High/Med/Low) x Effort (Low/Med/High), highest impact-per-effort first. For each: pattern number, evidence, the specific fix with a code sketch, and expected effect (TTFB, Active CPU, build time). Put anything unproven under "Needs more evidence" instead of guessing.
An agent pass catches the systematic issues. A full audit adds what the data alone can't: judgment about which fixes matter for your business, patterns validated across hundreds of production applications, and four 30-minute weekly office hours sessions to guide implementation.
Copy link to headingNext steps
- Read the companion guide on caching antipatterns for the five cache-specific findings from our audits
- Set up Speed Insights to get the field data these diagnostics depend on
- Explore Observability to trend usage per route over time
- Learn how fluid compute bills Active CPU and provisioned memory
- If you're approaching a migration or launch, talk to our team about a technical audit