Vercel, deployments and CPU time billing
Vercel is a platform that, once connected to a repository, builds and publishes an application on every push, giving each branch its own address to look at. On top of that it adds functions run on demand, a content delivery network, and caching for server rendered pages.
Its greatest strength is mundane and therefore effective: minutes pass between an empty repository and a working address, with nothing to configure. Its greatest weakness is equally simple: the bill stays unpredictable until you understand exactly what you pay for.
Two changes worth knowing about
Material describing this platform ages fast, and two recent changes invalidate a good share of what you find online.
The first concerns the split into two kinds of functions. For years you chose between an edge environment, fast to start and thin on capability, and an ordinary Node environment, slower to start and complete. The standalone edge functions product was retired in June 2025, and the recommended answer is one Node environment with full interface access.
That simplifies life more than it looks. It removes a class of bugs where a library worked locally and failed after deployment, because the edge environment lacked a module it used.
The second change concerns billing and matters more for the bill. Functions are billed for actual processor working time rather than call duration measured by a clock. Waiting for a database or a language model to answer is not billed, since the processor is idle then.
What that means for costs
The difference between those two models is fundamental and shifts the economics of whole classes of application.
Take a function that queries a database, waits eight hundred milliseconds for an answer, and spends fifty milliseconds processing the result. Under the old model you paid for eight hundred and fifty milliseconds. Under the new one you pay for fifty, since the rest was waiting.
The more your application waits, the greater the saving. A route calling a language model that waits five seconds for an answer costs a fraction of what it did. A route resizing images or sorting a large set in memory gains nothing, since the processor genuinely works there.
Concurrency adds to that. One instance serves several calls at once, so one request's waiting time gets spent serving another. That lowers the bill further and shortens cold starts, since fewer instances are needed.
The practical conclusion: if your sense of this platform's costs predates that change, compute it again. For an application that mostly waits on external services the difference is often several fold.
Preview deployments, the strongest thing in the set
Every branch gets its own address, and every merge request a link to a version containing those specific changes.
It sounds like a detail and is the most cited reason teams stay on this platform despite the cost. A designer reviews a change on a running site rather than a screenshot. A client clicks through the actual application before anything reaches production. Automated tests get an address serving exactly the code under review.
Two things need watching. Previews are public by default under a hard to guess address, so if the application in that state can reach real data, access protection has to be switched on. And: every preview uses environment variables assigned to that environment, so pointing them at the production database is easy and dangerous. A separate database for previews settles it once.
For local work similar address comfort comes from Portless, a tool from the same vendor's labs: instead of localhost:3000 you open a project under a name such as shop.localhost, over HTTPS and with cookies separated per application, so two services running at once stop logging each other out. The cost is an install on every machine in the team and adding a private certificate authority to the system trust store, which in some companies falls under security policy.
The cost structure in practice
The bill holds several lines and only one of them is obvious.
The free plan suffices for private projects and portfolios, with the caveat that commercial activity is not permitted on it. The paid plan starts at twenty dollars per person per month and includes a usage allowance, beyond which you pay for the overage.
The lines that surprise are usually not the functions. Data transfer, requests served by the content delivery network, storage and revalidation of incrementally generated pages, and image optimisation can each exceed the cost of running code.
Image optimisation deserves its own sentence, being the most common source of surprise. Every size and format generated for every image counts separately, so a gallery of two hundred photos displayed at five sizes is a thousand items rather than two hundred.
Before writing this platform into a budget, look at the usage tab after the first week of production traffic. The distribution of lines there usually differs from what the team assumed, and it is the only meaningful basis for a decision.
Worth knowing, too, is that the platform has a built in mechanism against exactly the bill described above, and teams rarely use it. New teams get a default usage budget of two hundred dollars, with notifications by email, text message, and in the dashboard, and the threshold can be changed. Projects can also be set to pause automatically once the budget reaches a hundred percent.
Settle that setting deliberately at the first production deployment, because both answers are defensible. Automatic pausing protects against a bill after a runaway loop or a traffic spike, but it takes the site down. An alert without pausing leaves the site up and moves the decision to a person who may happen to be asleep.
Caching and page regeneration
This is where the platform gives the most and where it is easiest to accidentally switch off the thing you pay for.
A page generated once and served from the delivery network costs a fraction of a page recomputed on every request, and answers in tens of milliseconds rather than hundreds. Incremental regeneration lets you keep it static while still refreshing it after a period or on demand once content changes.
The trouble is how easily you fall out of it without deciding to. Reading a request header, reaching for a cookie, or calling a function returning the current time turns a static route dynamic, and you see the result only in the bill and in response times.
Diagnosis is simple once you know where to look. The build output states for each route whether it is static or dynamic, and comparing that list against expectations takes a minute.
npx vercel buildDo it once after every larger change, since a single import pulled into a shared component can flip a dozen routes at once.
Invalidation is a separate matter. Time based refresh is simple and sufficient for content that may be stale for a few minutes.
export const revalidate = 600
export default async function Page() {
const posts = await fetchPosts()
return <List posts={posts} />
}On demand invalidation, triggered from a content management system after publishing, gives immediate freshness and needs one route to receive the signal. That second route is usually worth an hour of work, since it removes the trade off between freshness and cost.
import { revalidateTag } from 'next/cache'
import { NextRequest } from 'next/server'
export async function POST(req: NextRequest) {
if (req.headers.get('x-webhook-secret') !== process.env.WEBHOOK_SECRET) {
return new Response('Forbidden', { status: 401 })
}
revalidateTag('posts')
return Response.json({ revalidated: true })
}Checking the secret on the first line is not a formality. An invalidation route without it lets anyone force a rebuild of every page, and that is a cheap way to raise somebody else's bill.
Environment variables and secrets
Three environments, each with its own set of variables, and that distinction deserves setting up correctly from the start.
Variables marked public reach code running in the browser, so anyone opening developer tools sees them. That is obvious and still the most common place a key to an external service leaks: somebody added the public prefix to make it work in a client component, and it worked.
Variables without that prefix are available only server side, and everything meant to stay secret belongs there. The rule is simple: if a value lets somebody act on your behalf, it cannot be public, and the call using it must go through a server route.
Variables from the console are pulled down for local work with a single command, rather than maintaining a second copy by hand.
npx vercel env pull .env.local
npx vercel env add STRIPE_SECRET productionRemember too that changing a variable does not take effect by itself. Values are baked in at build time, so after editing you have to deploy again. That causes recurring confusion, since the console shows the new value while the application runs on the old one.
Vercel against the alternatives
| Option | Strength | Weakness | Pick it when |
|---|---|---|---|
| Vercel | Preview deployments, zero configuration | Cost at high transfer | A Next.js app, a team with no operators |
| Cloudflare | Cheap transfer, a global network | More configuration work | Heavy static traffic, budget under control |
| Railway | Ordinary containers, databases included | No previews of this class | A backend that does not fit functions |
| Your own server | Full control, predictable cost | Upkeep is yours | Steady traffic, somebody on the team knows how |
The first row wins on convenience rather than price, and that deserves saying plainly. A team with nobody dedicated to infrastructure gets deployments, previews, certificates, a delivery network, and rollbacks without configuring anything. That is a real time saving to weigh against the bill rather than ignore when comparing rates.
The last row deserves consideration at steady, predictable traffic. An application serving even load around the clock is cheaper on a fixed price machine than on usage based billing, and that gap widens with scale.
Where this platform does not fit
Settle this early, since migrating after a year is expensive.
Long running jobs do not fit. Functions carry an execution time limit, so processing a video file, generating a large report, or importing several hundred thousand records needs separate infrastructure or splitting into steps.
Background processes do not fit. A queue consumed continuously, a listener on database changes, or a held connection need something that simply runs rather than starting on demand.
An application with heavy data transfer gets expensive here. A video service, file hosting, or a site with heavy graphics generates transfer costs against which a self managed solution pays back quickly.
A backend composed of many services fits poorly too. A function based model suits routes handling requests and works less well for a system with several processes, queues, and scheduled jobs between them.
Practical guidance
A few things save money and nerves over longer work with this platform.
Set spending limits on day one. Usage based billing means a bug causing a request loop grows into a bill rather than into a quota exhausted message.
Watch the cache. A route that could have been static and became dynamic through an accidental header read executes on every request instead of once. That is the most common cause of a sudden jump in invocations with no jump in traffic.
Restrict image sizes to those you actually use. The default set of breakpoints generates variants the interface will never show.
Keep the database close to the functions. A query to a database across an ocean adds latency to every request, and under CPU time billing it does not raise the cost, only spoils how it feels.
The last item: do not bind business logic to this platform's concepts. Routes written as ordinary functions move elsewhere without a rewrite; those built on vendor specific mechanisms do not.
Common mistakes
The first is pointing the preview environment at the production database. Every branch then reaches real data, and a test deleting records deletes them for real.
The second is no spending limit. Under usage based billing an expensive mistake does not stop by itself.
The third is making a route dynamic without noticing. One header or cookie read disables static generation and multiplies the invocation count.
The fourth is relying on knowledge predating the billing change. Calculations based on call duration overstate the cost of applications that mostly wait on external services.
The fifth is running long jobs here. A function's execution time limit cuts them off mid way, and the result looks like a random failure.
The sixth is ignoring the cost of image optimisation. Every variant counts separately, so a gallery can generate more items than the rest of the site combined.
FAQ
Is Vercel free?
The free plan suffices for private projects, learning, and portfolios, while commercial activity is not permitted on it. The paid plan starts at twenty dollars per person per month and includes a usage allowance, beyond which overage is charged.
Do Edge Functions still exist?
The standalone edge functions product was retired in June 2025. The recommended answer is functions on the Node runtime, which give full interface access and use billing based on actual processor working time.
What exactly am I paying for with functions?
For the time the processor actually executes your code. Waiting for a database or an external interface to answer is not billed, so applications spending most of their time waiting cost considerably less than under clock based billing.
Does Vercel only work with Next.js?
No, it supports popular frameworks and static sites. The Next.js integration runs deepest, since both come from the same company, so some capabilities work there without configuration and need settings elsewhere.
When is something else the better pick?
With heavy data transfer, long running jobs, background processes, and steady predictable load. In those cases Railway, Cloudflare, or an ordinary server come out cheaper or simply fit better.
The billing change is described in the vendor changelog, and plan details on the pricing page.