You have already picked the platform. The open question is who keeps it predictable once real traffic, real data and a real invoice arrive. This page covers what running Next.js on Vercel looks like in production: build and runtime limits, the caching layers, previews and secrets, cost control, and the cases where self hosting is the better answer.
The limits you meet first, and the error codes that name them
Vercel stays comfortable until an application grows a shape the platform did not plan for. The first wall is usually the build: a monorepo that rebuilds every package on every push drifts towards the build duration ceiling (45 minutes at the time of writing), and the fix is remote caching plus a per project ignored build step. The second is bundle size. Node functions inherit the underlying Lambda limit of 250 MB uncompressed, and one dependency (a headless browser, a PDF toolkit, several database engines built for targets you never deploy to) can push a single route over it. The third is time. Default durations are short and can be raised on paid plans, but no plan turns a request handler into a batch worker. A thousand generated PDFs, a nightly export or a long chain of model calls belong in a queue with a worker you control. The fourth is payload: a request body above roughly 4.5 MB is rejected outright, so uploads go straight to object storage through a presigned URL and the function only ever handles the key. There are also no long lived connections, so chat or presence needs a realtime provider or a small always on service beside the app, never a socket server inside a function. When FUNCTION_INVOCATION_TIMEOUT or FUNCTION_PAYLOAD_TOO_LARGE shows up in the logs, the platform is not failing. It is telling you that a piece of the workload is in the wrong place.
Edge runtime or Node runtime: really a question about where your data lives
The runtime choice looks like a performance decision and is actually a data locality decision. The edge runtime runs in V8 isolates close to the visitor with cold starts near zero, but it gives you Web APIs only: no filesystem, no raw TCP sockets, no native modules, a small bundle ceiling and a CPU budget per invocation measured in milliseconds. That rules out the usual database drivers. Data at the edge means an HTTP based driver or an HTTP pooler in front of Postgres, and even then physics wins: a function in Sydney querying a database in Paris pays that round trip on every query, so pushing rendering outward while the data stays in one region usually makes the page slower, not faster. Edge suits work that needs no origin data: verifying a signed token, geographic redirects, bucketing a visitor into an experiment, rewriting headers. Node suits everything that touches an ORM, a real driver, image processing or native crypto, and the single most effective setting on many projects is pinning function regions next to the database instead of leaving the default. Middleware deserves its own audit: it runs on every matched request, so a loose matcher taxes static assets and analytics beacons too. Keep the matcher explicit, and never put a database call in it.
Four caches plus a CDN, and the disagreement between two of them
Next.js keeps four caches (request memoization, the Data Cache, the Full Route Cache and the client side Router Cache) and the CDN sits above all of them. Most incidents on this stack come down to two of those layers disagreeing. Version 15 flipped defaults people had internalized: fetch is no longer cached unless you ask, GET route handlers are dynamic by default, and client navigation revalidates page segments rather than serving a stale tree. Two failure modes are worth naming. One cookies() or headers() call inside a shared component quietly makes a whole route dynamic, so a page you believed was static now runs a function on every hit and both the bill and the p95 move. The opposite case is worse: a personalized page that stayed cacheable gets served from the CDN to the next visitor, which is a data leak, not a stale render. Control it explicitly. Use Cache-Control for the browser and CDN-Cache-Control or Vercel-CDN-Cache-Control when the edge should hold something longer than the client, prefer tag based invalidation fired by a CMS webhook over aggressive time windows, and turn on skew protection so a visitor holding old chunks does not call a new server contract mid session. Then verify: the x-vercel-cache header (HIT, MISS, STALE, PRERENDER) on your critical URLs belongs in a post deploy smoke check, not in somebody's memory.
Where the bill actually comes from, and the observability to explain it
The invoice is rarely driven by traffic. It is usually driven by one misconfiguration. Billing is seats plus metered usage: function invocations and compute time, edge requests, data transfer out, image optimization transformations, incremental regeneration reads and writes, log retention. The recurring culprits: a middleware matcher that also matches static assets, an Image component pointed at loosely allowed remote patterns so every crawler query string mints a fresh transformation, a heavy page set to revalidate every second, routes that went dynamic by accident. The corrections are cheap: a tight matcher, restricted remote patterns with fixed sizes and quality values, longer revalidation windows with tag invalidation, and a firewall rule on the few endpoints that cost real compute. Then make it observable before you need it: log retention is short, so send a drain somewhere you already read, export OpenTelemetry traces if the app talks to more than one service, and set spend alerts with a hard cap instead of meeting the number at month end. Treat every published figure as perishable: Vercel bills Pro per team member per month (around 20 USD at the time of writing) on top of usage, and the usage model has been reshaped more than once. The durable habit is reading the usage tab monthly and knowing which route owns which line.
Two people, one time zone, and every decision written down
We are two: Robin Monteiro on the code (Next.js and React) and Florian Loppion on the marketing side, working from Dijon in France. No office abroad, no account manager sitting between you and the person editing the config. Everything runs in writing and asynchronously: no calls, no video, no scheduled slots. That is policy, not preference. On a Vercel project the written loop has an obvious shape. Every change arrives as a pull request with its own preview deployment, so reviewing means opening the running thing on your own device, against a preview environment with its own variables and its own branch database, never production credentials. Comments live on the pull request or in email, so a decision taken in March is still searchable in August with the diff attached. We answer within 24 business hours. What you do differently: you write the brief instead of talking it, you open with specifics (repository or read only access, the scope you can grant on your Vercel team, the symptom with a deployment ID and a timestamp), and you accept that a question sent at 6pm in California gets its answer the next working day. In exchange nobody takes a 3am call, the time zone stops being a scheduling problem, and nothing important survives only as somebody's memory of a meeting. The French SMEs we work with (Chouchou Ribeyre, Au Petit Detail, LB Athletic, Mediavocats, Vectosolve) run on the same loop.
When Vercel is the wrong host, staying portable, and when not to hire us
Vercel is the wrong host more often than its advocates admit. Sustained CPU work (video transcoding, large image pipelines, model inference), jobs measured in minutes, persistent connections, heavy egress where a flat monthly server beats per gigabyte billing, strict data residency, or a database with no public endpoint: in each of those cases a container on a VPS, or a cluster you already operate, is the honest answer. Next.js self hosts properly with next start or the standalone output in a Docker image, but you take back the work the platform was doing for you: a Redis backed cache handler so instances agree on what is fresh, image optimization, a CDN, TLS, log aggregation, rollbacks. Because that day can come, we keep projects portable by default: next start stays green in CI, secrets stay in environment variables rather than a platform only client, storage stays S3 compatible and the database stays plain Postgres, platform specific pieces sit behind an interface with a second implementation, and the domain, the repository and the accounts stay in your name. Do not hire us if you need a 24 hour on call rotation, if you want someone thinking out loud with you on a call, if the application is Django or Rails rather than JavaScript, if you need a vendor with a compliance team to answer a long security questionnaire, or if the real problem is the data model and the platform is only where it became visible. We will say so before you spend anything.