Hidden tricks and gotchas — Vercel
Things the docs mention briefly or surface only via support tickets that consistently bite or empower Vercel deployments. Sources: official docs page footnotes, Vercel changelog, Vercel engineering blog posts, AI SDK PRs, and observed behavior in production. Tags: [official] = documented but easy to miss, [observed] = consistent behavior not in primary docs as of 2026-05-25.
See also
- vercel-edge-network-and-functions — runtime details these tricks operate on
- vercel-platform-and-deployments — deployment-level operational features
- hidden-tricks-and-gotchas-cloudflare-workers — analogous tricks on the other major edge platform
1. The Edge runtime’s 1 MB code-size cap is real [official]
Per vercel.com/docs/functions/limitations. Compressed code size limit on Edge runtime: 1 MB on Hobby, 4 MB on Pro. Many popular packages exceed this when bundled — LangChain alone is ~3-5 MB. Surprise: you don’t find out until deploy time, when the build fails with EDGE_FUNCTION_INVOCATION_FAILED or “function exceeds size limit”.
Workarounds:
- Use lighter libraries — Vercel AI SDK (< 50 KB) instead of LangChain.
- Lazy import — dynamic
await import()doesn’t help; everything ends up bundled. - Move to Node.js runtime — 50 MB compressed limit. Add
export const runtime = "nodejs";. - Externalize — move heavy logic to a separate Function (Node.js runtime) and call it from the Edge function via fetch.
2. Middleware runs on every matched request, even cached ones [official, expensive]
Per vercel.com/docs/routing-middleware. Middleware runs before the CDN cache check. If your matcher is too broad (matcher: "/(.*)"), middleware fires on every static asset request — every .css, .js, image, font. At 100 req/s sustained traffic, that’s > 8M middleware invocations per day.
Edge Requests are metered (Pro: 10M included, then $0.50 per million). Easy way to blow past the cap is a broad matcher.
Tight matcher pattern:
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)"],
};The double-negative regex excludes static asset paths.
3. ISR cache hits inflate Edge Request counts [observed]
Each ISR-cached page hit registers as an Edge Request, even though the page didn’t run any function. Easy to think “the site is cheap to run” until the Edge Request bill arrives.
For high-traffic ISR pages, the bill is bandwidth + Edge Requests; functions are basically free. Plan capacity based on the Pro plan’s 10M-Edge-Request allowance / 30 days = ~3.8 req/sec sustained.
4. Fluid Compute changes the cost math dramatically [official]
Per vercel.com/docs/fluid-compute. Legacy serverless billed per-invocation × max-duration — a function with 60s max-duration that ran for 5s still got billed for 60 GB-seconds of provisioned memory.
Fluid Compute bills:
- Active CPU — only when the JS thread is executing (not awaiting I/O)
- Provisioned Memory — wall-clock × memory while instance is alive
- Invocations — entry counts
For AI workloads (most of the time waiting on the model), Fluid is 5-10× cheaper than legacy. Migrate existing projects in Project Settings → Functions → Fluid Compute → Opt in.
Quirk: Fluid Compute requires the function to be idempotent + concurrency-safe. If you have global mutable state inside a function module (let counter = 0 at module level), multiple concurrent requests share it — usually fine, occasionally a footgun.
5. Serverless cold start mitigation [observed]
Cold starts on Node.js runtime are 200-500ms typical, occasionally 1-2s on first request after deploy. Fluid Compute reduces this by keeping instances warm ~5 minutes, but the first request after a quiet period is still cold.
Mitigations:
- Smaller bundles — every MB of code adds ~50ms of cold start. Strip unused deps.
- Pre-warming via cron — schedule a
/api/keep-warmroute every 4 minutes (cron limits, see #11 below). - Pin to one region — multi-region functions multiply cold starts (each region cold-starts independently).
- Use Edge runtime where possible — V8 isolates have ~5-50ms cold starts.
6. output: "standalone" vs default Next.js build [observed]
Next.js has a output: "standalone" build mode that creates a minimal Node.js-compatible bundle. Vercel does not need this — Vercel’s build pipeline uses the regular output format. Setting standalone on Vercel is a no-op at best, broken at worst (the bundle layout doesn’t match what Vercel expects).
Reserve output: "standalone" for non-Vercel deployments (self-hosting, Docker).
7. The 4.5 MB request body limit [official, frustrating]
Per vercel.com/docs/functions/limitations. Both Edge and Node.js functions have a 4.5 MB request body limit. This bites:
- File uploads — use Vercel Blob client uploads (see vercel-storage-and-data).
- Large form submissions — chunk client-side.
- AI vision uploads — base64-encoded images push past the limit at ~3 MB original image. Convert to URLs first.
- Webhook ingestion of large JSON — split into chunks or use Blob as ingest staging.
Not configurable. The limit is at the Vercel edge router, not your function.
8. Image Optimization defaults to WebP + 90% quality [official]
Per vercel.com/docs/image-optimization. Defaults:
- Output format: WebP if browser supports it, else original.
- Quality: 90 (overkill — 75-80 looks identical).
- Caching: 60 days by default.
Lower the quality for ~30% bandwidth savings with no visible difference:
<Image src="/photo.jpg" width={1200} height={800} quality={80} />Or globally in next.config.js:
module.exports = {
images: {
formats: ["image/avif", "image/webp"], // AVIF is smaller but slower to encode
minimumCacheTTL: 31536000, // 1 year
},
};9. ISR + On-demand revalidation = best of both [official]
Pattern: serve cached HTML, regenerate on relevant events (CMS publish, DB write, scheduled cron).
// 1. Page caches indefinitely (very long revalidate)
export const revalidate = 31536000; // 1 year
// 2. Trigger revalidate on CMS webhook
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from "next/cache";
export async function POST(req: Request) {
await verifyWebhookSignature(req);
const { path, tag } = await req.json();
if (path) revalidatePath(path);
if (tag) revalidateTag(tag);
return Response.json({ revalidated: true });
}For CMS-driven sites, this combo is the right answer: static-speed serving + freshness when content changes. Avoid time-based revalidation in most cases — it wastes regenerations.
10. Preview deployments need auth for staging-with-real-data [official]
Per vercel.com/docs/deployment-protection. By default on Pro, preview deployments require Vercel team login to view. If you want to share with non-team-member stakeholders:
- Password protection — single password for all previews.
- Bypass tokens — per-deployment shareable tokens.
- Comments + Toolbar — invite stakeholders as “viewer” team members; they can comment but not deploy.
For preview environments hitting production data, always use protection + a separate read-only DB user — preview URLs are guessable enough that you don’t want anonymous access.
11. Cron job limits per plan [official]
Per vercel.com/docs/cron-jobs/usage-and-pricing:
| Plan | Cron jobs | Min frequency |
|---|---|---|
| Hobby | 2 | 1 / day (24h) |
| Pro | 40 | 1 / minute |
| Enterprise | 100 | 1 / minute |
Hobby’s “1 / day” frequency cap surprises people. To run hourly, you need Pro. For sub-minute scheduling, use external schedulers (Upstash QStash, Inngest, BullMQ on Upstash Redis, GitHub Actions cron).
Also: cron jobs are best-effort — Vercel doesn’t guarantee millisecond accuracy. Skews of 1-5 minutes from the configured time are normal.
12. Free tier vs Pro economics [observed]
Hobby vs Pro break-even points (approximate, varies):
| Metric | Hobby | Pro | Pro break-even |
|---|---|---|---|
| Bandwidth | 100 GB | 1 TB included | Above 1 TB |
| Function invocations | 100k | 1M | Above ~700k |
| Edge Requests | 1M | 10M | Above ~7M |
| Build minutes | 6000 (100 hrs) | 24000 (400 hrs) | Above ~16k |
For a hobby site with 5000 visitors/month, Hobby is fine forever. For a real product, you’ll hit one of these caps within months. The $20/seat/month Pro plan is well-priced — don’t try to stretch Hobby past its design.
13. Multi-region functions multiply costs [observed]
Per vercel.com/docs/functions/configuring-functions/region. Configure functions to run in multiple regions:
{
"regions": ["iad1", "fra1", "syd1"]
}Each request runs in the nearest region only — not all of them. But the cold-start surface multiplies — each region cold-starts independently after idle. Build sizes are evaluated per region. For most apps with a single primary DB region, multi-region is a net cost increase.
Only use multi-region when:
- Your DB is also multi-region replicated.
- Latency budget is < 100ms p99 worldwide.
- You’re on Enterprise (where multi-region is supported best).
14. v0 vs Cursor + Claude vs vibe-coding [observed]
v0 (Vercel’s AI design + code tool) is positioned for design-first prototyping. Compared to:
- Cursor / Claude Code — better for “edit my existing repo” workflows. v0 is more “start from a design”.
- GitHub Copilot Spaces — better integration with existing GitHub repos.
- Bolt.new / Lovable — competitor with similar “prompt to deployed app” workflow.
v0’s killer feature: direct deployment integration. Generate UI → push to a Vercel deployment → claim deployment to take ownership. Useful for design exploration; the generated code quality is decent but not production-grade. Typical workflow: v0 to scaffold, then move to Cursor/Claude to refine.
15. AI Gateway is free for now [observed]
AI Gateway adds caching, fallbacks, analytics over provider APIs at no markup. Vercel pays for the infrastructure; you pay only the underlying provider costs. This is unusual — competitors (Helicone, Portkey, Langtrace) charge for similar services.
Reason: Vercel wants to lock in AI workloads, and Gateway becomes the central point of measurement + optimization. Take advantage now while pricing is favorable.
16. Skew Protection’s hidden cost [observed]
Per vercel.com/docs/skew-protection. Skew Protection holds old deployment server-code warm for 24h after a new deploy is promoted. That means two full sets of functions are active during the skew window. Function invocations counted against your quota are summed across both.
For high-churn deployments (10+ per day), skew protection can double your function-invocation bill during active rollouts. Mitigations:
- Disable for Hobby projects (always do anyway — not available there).
- For Pro, set the Skew Protection TTL to a shorter window (e.g. 30 minutes) when client-skew tolerance is low.
17. Environment variables don’t sync to Edge Functions at runtime [observed]
Edge Function env vars are baked into the bundle at build time. Changing an env var requires a new deployment to take effect on Edge functions. Node.js functions read env vars at runtime, so they pick up changes via redeploy without rebuild.
This affects:
- Feature flags via env var — use Edge Config instead (true runtime updates).
- Rotating API keys — Edge functions need redeploy to use new keys.
18. Build cache invalidation gotchas [observed]
The build cache is keyed on:
package.json+ lockfile contents- Build command
- Framework + Node version
- Branch (cache is per-branch)
Cache misses on first build of a new branch. Cache hits on subsequent builds of the same branch. If your .env file changes (locally) but you don’t push it, the cache still hits — your local builds may behave differently from Vercel’s.
Force a clean build: vercel build --force or “Redeploy without build cache” in dashboard.
19. process.env in client-side code is replaced at build [official]
Next.js / Vite / SvelteKit only inline env vars prefixed with NEXT_PUBLIC_ / VITE_ / PUBLIC_ into client bundles. Trying to use process.env.SECRET_KEY in a client component silently fails (returns undefined).
Common mistake: pasting a “secret” API key into a client-rendered component. Use server actions / API routes for anything sensitive.
20. Vercel ignores .gitignore for build inputs [observed]
Files in your repo but .gitignored still get uploaded to Vercel for the build (because the build context is the entire repo). Adding .next/cache to gitignore doesn’t keep it out of the upload.
Use .vercelignore (same syntax as .gitignore) to exclude files from the upload step.
21. Hobby commercial-use enforcement [official, easy to forget]
Per Vercel ToS: Hobby is for personal, non-commercial use only. Running a paid product or business on Hobby violates the TOS. Vercel does enforce this — accounts have been suspended for clear commercial use on Hobby. Limits would catch you anyway (100GB/mo bandwidth, 100k function invocations/mo).
For any real product: Pro from day one. The $20/seat/month is fair.