Vercel bill too high on a Next.js site? What drives it and how to cut it
By Nayan Kyada · · 6 min read
Part of The Sanity + Next.js Guide
If your Vercel bill is too high for a Next.js site that does not get much traffic, the problem is almost never the traffic. It is a handful of configuration choices that make every request, crawl, and content edit cost more than it should. This post walks through the usual culprits on Sanity-backed sites, how to confirm each one, and the order I would fix them in.
Symptoms that point to a config problem, not growth
The pattern is consistent. Usage climbs while analytics visitors stay flat. A month with one big content import costs more than a month with a campaign. The invoice has line items nobody on the team can explain. If any of that sounds familiar, you are paying for waste.
Before touching code, open the Usage page in the Vercel dashboard and note which metric is largest: data transfer, edge requests, function duration and invocations, image optimization, or ISR reads and writes. Plan names and included amounts change, so confirm limits on Vercel's pricing page rather than trusting any blog, including mine. For background on how the plans fit together, see what you actually pay for a real Next.js project on Vercel.
What it costs to ignore
Usage-based costs compound quietly. A site that costs a modest amount at launch can drift upward every month as the content library grows, more pages get crawled, and more editors publish. Teams then react by downgrading features or blaming the framework, when the fix is usually a day or two of targeted work. Ignoring it also hides risk: the same settings that inflate cost often cause slow pages and stale content.
Cause 1: image optimization on oversized originals
Every unique combination of source image, width, and quality is a transformation that gets cached and billed in some form. Sanity images are often uploaded at 4000 pixels wide straight from a designer, and a careless sizes attribute makes Next generate many variants of each.
Sanity's CDN already resizes and converts formats on the fly. Running those images through Vercel's optimizer as well means you pay twice for the same job. I covered the quality side of that in serving WebP and AVIF from Sanity without double-encoding. The cost fix is to hand resizing to Sanity with a custom loader:
// lib/sanity-image-loader.ts
import type { ImageLoaderProps } from 'next/image'
export default function sanityImageLoader({ src, width, quality }: ImageLoaderProps) {
const url = new URL(src)
url.searchParams.set('w', String(width))
url.searchParams.set('q', String(quality || 75))
url.searchParams.set('auto', 'format')
return url.toString()
}// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
loader: 'custom',
loaderFile: './lib/sanity-image-loader.ts',
},
}
export default configThis applies to every next/image on the site, so if you also serve non-Sanity images, either pass full URLs your loader can handle or set the loader per component. Also fix the sizes attribute, so the browser asks for 400 pixels on a card instead of 1600.
Cause 2: revalidation that rebuilds too much
A webhook that calls a path revalidation for the homepage, the blog index, and every listing on each publish creates a burst of regenerations. Ten editors saving drafts all day makes it worse if drafts trigger the webhook at all.
The fix is to filter the webhook so it fires only on published documents, and revalidate by cache tag so only affected pages regenerate. I wrote up the batching approach in Sanity webhook revalidation: batching tags. Confirm the problem by looking at function invocations right after an editing session.
Cause 3: the proxy runs on everything
In Next.js 16 the old middleware file is the proxy file. If its matcher is missing or too broad, it executes on every request: images, fonts, scripts, and bot probes included. That inflates edge requests and adds latency to pages that could have been served straight from cache.
// proxy.ts
import { NextResponse, type NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// locale or redirect logic only
return NextResponse.next()
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)'],
}Better still, move redirects into next.config.ts where they are static, and keep the proxy for logic that genuinely needs per-request code.
Cause 4: bots and dynamic routes
Crawlers hit your site constantly. If a route reads cookies, headers, or search params, or uses an uncached fetch, the whole page becomes dynamic and every bot visit runs a function. Faceted filters and calendar-style URLs can give a crawler effectively infinite pages. Check Observability for the top paths and user agents. Typical fixes: make routes static with generateStaticParams, push filtering into client state instead of URL variants, block junk paths in robots.txt, and use Vercel's firewall rules for abusive traffic.
Cause 5: over-fetching from the CMS
Fat GROQ queries and fetches without cache tags make pages slower to render, which raises function duration, and they also drive up your Sanity bill. If both invoices are rising, read why Sanity API requests explode on Next.js sites next.
Fix order I would follow
- Read the Usage page and pick the single biggest line item.
- Set a spend limit so a runaway month cannot surprise you.
- Fix images first, since it is usually the cheapest win.
- Tighten the proxy matcher and static redirects.
- Filter webhooks and move to tag-based revalidation.
- Audit dynamic routes and bot traffic.
- Recheck usage after one full week, not one day.
What to ask a developer
Ask which metric they would attack first and why. Ask how they will verify the saving, and whether they will show before-and-after usage from the dashboard. Be wary of anyone who proposes moving hosts before they have looked at the usage breakdown. A move can be right, but the same waste usually follows you. The hosting comparison in Next.js hosting cost across Vercel, Netlify, Railway and a VPS helps judge when it genuinely is.
Work I do
Tracing a bloated Vercel or Sanity bill back to its causes is work I do as part of a fixed-price performance and Sanity audit, from $800. Details are on the services page.
Frequently asked questions
01Why is my Vercel bill so high for a small Next.js site?
On small marketing sites the cause is rarely real traffic. It is usually bot crawling hitting dynamic routes, image optimization on large originals, over-eager ISR revalidation, or a proxy file that runs on every request including static assets.
02Does ISR cost money on Vercel?
ISR is metered through cache reads and writes plus function invocations when pages regenerate. A webhook that revalidates the whole site on every edit multiplies those writes, so tag-based revalidation is cheaper.
03How do I find out what is driving my Vercel usage?
Open the Usage page in your Vercel dashboard and sort by metric, then compare against Observability for top paths and bots. Fix the single biggest line item first rather than tuning everything.