---
title: "Technical audits: Finding the bottlenecks that cost performance, money, and velocity"
description: How Vercel's Forward Deployed Engineering team runs code review, web performance, and usage audits, the recurring patterns we fix, and the diagnostics we use to find them.
url: "https://vercel.com/kb/guide/technical-audits"
published: 2026-10-22
last_updated: 2026-10-22
authors: Forward Deployed Engineering Team, Dom Sipowicz, Lorenzo Palmes Storni, Luis Alvarez
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Every 100ms of latency can cost ecommerce applications up to [8% in sales conversion](https://vercel.com/blog/the-user-experience-of-the-frontend-cloud). 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.

## Key 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](https://vercel.com/docs/speed-insights) and usage data from [Observability](https://vercel.com/docs/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.
  

## The 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.

## The 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           |

### Code 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.

### Web 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.

### Usage 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](https://vercel.com/kb/guide/caching-antipatterns).

## Start 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 metrics` CLI 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.

## The four patterns we find in almost every audit

### 1\. 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](https://nextjs.org/docs/app/getting-started/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 [Vercel Flags](https://vercel.com/docs/feature-flags) so variants resolve before the response, eliminating client-side flicker and layout shift. Scope any flag evaluation in [Routing Middleware](https://vercel.com/docs/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.

### 2\. Resource waste compounds at scale

Small inefficiencies are invisible at thousands of requests and dominant at millions. [Fluid compute with Active CPU pricing](https://vercel.com/docs/fluid-compute) 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 `public` folder loads in full on page load instead of streaming from [Vercel Blob](https://vercel.com/docs/vercel-blob) or a CDN with lazy loading.
  
- **A second CDN in front of Vercel'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:

**Sequential**

```typescript
const product = await getProduct(slug);
const reviews = await getReviews(slug);
const related = await getRelated(slug);
```

**Parallel**

```typescript
const [product, reviews, related] = await Promise.all([
  getProduct(slug),
  getReviews(slug),
  getRelated(slug),
]);
```

For images, define dimensions and let the [Next.js Image component](https://nextjs.org/docs/app/api-reference/components/image) 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.

### 3\. 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 client` boundaries 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 cache` with [`cacheTag`](https://nextjs.org/docs/app/api-reference/functions/cacheTag) and [`cacheLife`](https://nextjs.org/docs/app/api-reference/functions/cacheLife) for reads, paired with [`revalidateTag`](https://nextjs.org/docs/app/api-reference/functions/revalidateTag) or [`updateTag`](https://nextjs.org/docs/app/api-reference/functions/updateTag) on 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.

### 4\. 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](https://vercel.com/kb/guide/caching-antipatterns), 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](https://vercel.com/docs/builds/managing-builds) (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](https://vercel.com/docs/monorepos/turborepo) 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.

## How 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             |

### Try 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 [Vercel CLI](https://vercel.com/docs/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.

### Agent prompt

```txt
# Technical audit: performance, cost, and velocity first pass

You are auditing this repository and its linked Vercel 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.

## Next steps

- Read the companion guide on [caching antipatterns](https://vercel.com/kb/guide/caching-antipatterns) for the five cache-specific findings from our audits
  
- Set up [Speed Insights](https://vercel.com/docs/speed-insights) to get the field data these diagnostics depend on
  
- Explore [Observability](https://vercel.com/docs/observability) to trend usage per route over time
  
- Learn how [fluid compute](https://vercel.com/docs/fluid-compute) bills Active CPU and provisioned memory
  
- If you're approaching a migration or launch, [talk to our team](https://vercel.com/contact/sales) about a technical audit

## More Vercel CDN guides

- [Using Cloudflare and other reverse proxies with Vercel](/kb/guide/can-i-use-a-proxy-on-top-of-my-vercel-deployment): General information about using an external proxy to serve a Vercel Deployment.
- [How to revalidate Sitecore pages on publish with Next.js](/kb/guide/update-sitecore-content-manually): Learn how to use a Sitecore Experience Edge webhook and Next.js revalidatePath to refresh cached Sitecore Content SDK pages after each publish.
- [How to reduce Vercel Image Optimization costs](/kb/guide/reduce-image-optimization-costs-on-vercel): Learn how to reduce Vercel Image Optimization costs in Next.js by tuning cache TTLs, image sizes, formats, and quality. Diagnose high image transformation count and verify savings using the Vercel Dashboard.