I am a...
Learn more
How it worksPricingFAQ
Account
May 7, 2026 · 12 min read · Cadence Editorial

How to deploy a Next.js app to Vercel like a pro

deploy nextjs vercel — How to deploy a Next.js app to Vercel like a pro
Photo by [Donald Martinez](https://www.pexels.com/@donald-martinez-1951590) on [Pexels](https://www.pexels.com/photo/clouds-in-the-blue-sky-8648191/)

How to deploy a Next.js app to Vercel like a pro

To deploy a Next.js app to Vercel, push your repo to GitHub, import it at vercel.com/new, set environment variables, and click Deploy. Vercel auto-detects Next.js, builds with the right settings, and gives you a production URL plus a preview URL per pull request. The "pro" part is everything that comes after: ISR, on-demand revalidation, custom domains, cron, KV, and knowing when the bill is about to hurt.

Why this guide exists in 2026

Most "deploy to Vercel" tutorials stop at step three. They show the import flow and the green build log, then leave you to discover bandwidth overages and function-invocation pricing at 2 a.m. on launch day. This is the playbook a senior engineer hands a junior on day one: the things Vercel rewards, the four billing dimensions that bite, and the precise moment when Render, Cloudflare Pages, or Fly starts making sense.

Three things changed since 2023. Next.js 15 made the App Router and Server Actions the default, which reshapes caching. Vercel split function billing into three dimensions (invocations, CPU time, memory time) instead of one, so apps that used to cost $20 now cost $80 without warning. And Edge Config plus on-demand revalidation made redeploys for trivial changes obsolete.

Step 1: Project setup that doesn't fight you later

Push the repo to GitHub, GitLab, or Bitbucket. Import at vercel.com/new. Vercel detects Next.js automatically and sets:

  • Framework Preset: Next.js
  • Build Command: next build
  • Output Directory: .next
  • Install Command: pnpm install (or whatever your lockfile implies)

Two non-default things to set on day one. Pin the Node.js version under Project Settings > Node.js Version (use 22.x or whatever your package.json engines field declares; production drift is the most common build break we see). Then enable "Skip deployments for unaffected projects" if you're in a monorepo, because every PR rebuilding every app racks up Turbo build minutes fast.

A useful starter vercel.json:

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "framework": "nextjs",
  "regions": ["iad1"],
  "buildCommand": "pnpm turbo run build --filter=web",
  "installCommand": "pnpm install --frozen-lockfile",
  "git": {
    "deploymentEnabled": {
      "main": true
    }
  }
}

The regions field pins SSR functions to a single region. For a US-first product with a Postgres in us-east-1, locking to iad1 (Washington DC) cuts cold-start tail latency more than running globally and round-tripping to a single database does. Multi-region only helps if your data is also multi-region.

Step 2: Environment variables without the panic

Vercel ignores .env.local. You set values under Project Settings > Environment Variables, scoped to Production, Preview, and Development. Three habits make this painless.

Use vercel env pull .env.local to sync values down for local dev. Group secrets by environment so a Stripe test key never leaks into production. And never put NEXT_PUBLIC_ in front of anything you'd be embarrassed to see in view-source, because that prefix bakes the value into the client bundle at build time.

For multi-tenant apps, Vercel exposes VERCEL_ENV, VERCEL_URL, and VERCEL_GIT_COMMIT_SHA automatically. Pipe them into Sentry releases or a deploy webhook to Slack, the same pattern we use when wiring GitHub Actions to a Next.js app.

Step 3: Branch deploys and preview URLs

Every push to a non-production branch gets a unique preview URL of the form your-app-git-branch-team.vercel.app. Every PR gets a comment with that URL. This is the single best feature of the platform.

Two things to wire up. First, password-protect previews under Settings > Deployment Protection (Pro plan and up); without this, indexers and bots crawl your unfinished work. Second, add a READY deployment hook that pings your QA Slack channel when a preview is ready, because waiting for the "deployment succeeded" GitHub comment loses 90 seconds per cycle.

If you use Cypress or Playwright in CI, point E2E runs at the preview URL instead of localhost. You'll catch SSR, ISR, and edge bugs that local dev hides.

Step 4: Custom domains, the right way

Add the domain under Project Settings > Domains. Vercel issues a Let's Encrypt certificate within 60 seconds of DNS resolving. For apex domains (example.com), use an A record to 76.76.21.21. For subdomains, use a CNAME to cname.vercel-dns.com.

If you're proxying through Cloudflare, set the orange cloud to "DNS only" (gray cloud) on the Vercel records. Otherwise you double-CDN the request, fight over caching headers, and burn extra origin pulls. Vercel's KB article on this is unusually direct: most teams should pick one CDN, not both.

Always provision both example.com and www.example.com and set one as the canonical redirect. Splitting traffic between the two tanks SEO and confuses analytics.

Step 5: ISR and on-demand revalidation

Incremental Static Regeneration is the killer feature. You get static-page performance with dynamic content, and Vercel propagates updates globally in roughly 300ms.

Time-based, in the App Router:

// app/blog/[slug]/page.tsx
export const revalidate = 60; // seconds

export default async function Page({ params }: { params: { slug: string } }) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
    next: { revalidate: 60, tags: [`post:${params.slug}`] },
  }).then(r => r.json());
  return <Article post={post} />;
}

On-demand revalidation, triggered from a CMS webhook:

// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
import { NextRequest } from "next/server";

export async function POST(req: NextRequest) {
  const secret = req.headers.get("x-revalidate-secret");
  if (secret !== process.env.REVALIDATE_SECRET) {
    return new Response("unauthorized", { status: 401 });
  }
  const { slug } = await req.json();
  revalidateTag(`post:${slug}`);
  return Response.json({ revalidated: true });
}

Always tag-based, not path-based. Tags compose; paths don't. When a post update should also invalidate the index page and the RSS feed, you call revalidateTag('post:slug') plus revalidateTag('post:list') and you're done. Path-based revalidation would have you maintain a list of every URL that mentions that post, which falls apart the moment marketing adds a new module.

Step 6: Edge Config for the things you'd otherwise redeploy

Edge Config is a globally replicated read-only key-value store that reads in under 15ms at the edge. Use it for feature flags, kill switches, A/B test assignments, and short allow-lists. Don't use it for user data; it's read-mostly and writes propagate slowly.

// middleware.ts
import { get } from "@vercel/edge-config";
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(req: NextRequest) {
  const inMaintenance = await get<boolean>("maintenance");
  if (inMaintenance && !req.nextUrl.pathname.startsWith("/maintenance")) {
    return NextResponse.redirect(new URL("/maintenance", req.url));
  }
  return NextResponse.next();
}

export const config = { matcher: "/((?!_next|api|maintenance).*)" };

A maintenance toggle that used to require a redeploy is now a single API call. The first time you ship a kill switch this way and use it during an incident, you'll wonder how you lived without it.

Step 7: Image optimization and remote domains

next/image with Vercel optimizes on demand, caches transformations, and serves AVIF / WebP based on the Accept header. The cost trap: every unique transformation counts, so a thumbnail and a hero variant of the same source are two transformations.

Whitelist remote sources in next.config.js:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      { protocol: "https", hostname: "cdn.example.com" },
      { protocol: "https", hostname: "*.supabase.co" },
    ],
    formats: ["image/avif", "image/webp"],
    minimumCacheTTL: 86400, // 1 day; default is 60 seconds
  },
  experimental: {
    ppr: "incremental",
  },
};

module.exports = nextConfig;

Bumping minimumCacheTTL from the 60-second default to a day cuts re-optimization cost by 90% on a content-heavy site. Most teams never touch this and overpay accordingly.

Step 8: Monitoring, Web Vitals, and Speed Insights

Install both tracking packages in the App Router root layout:

// app/layout.tsx
import { Analytics } from "@vercel/analytics/next";
import { SpeedInsights } from "@vercel/speed-insights/next";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  );
}

Speed Insights captures real-user LCP, INP, and CLS per route. Set a budget and alert on regression: LCP > 2.5s on any route shipped that week is a P1. The team that ignores this for six months ends up with a 4-second LCP and no idea where it came from. Pair this with OpenTelemetry for backend traces and you have a full picture from browser to database. If you also need to track render performance issues at the component level, optimizing React performance with the React Compiler closes the loop.

Step 9: Cron jobs without a cron server

Vercel Cron runs scheduled HTTP calls against your routes. Defined in vercel.json:

{
  "crons": [
    {
      "path": "/api/cron/daily-digest",
      "schedule": "0 13 * * *"
    },
    {
      "path": "/api/cron/cleanup",
      "schedule": "*/15 * * * *"
    }
  ]
}

The handler should verify the request came from Vercel:

// app/api/cron/daily-digest/route.ts
import { NextRequest } from "next/server";

export async function GET(req: NextRequest) {
  if (req.headers.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response("unauthorized", { status: 401 });
  }
  // ... do the work
  return Response.json({ ok: true });
}

Hobby plan caps at two crons; Pro at 40. If you need second-level precision or guaranteed-once delivery, this is where you outgrow Vercel Cron and adopt Inngest, Trigger.dev, or a Cloudflare Workers cron.

Step 10: Storage tradeoffs (KV, Postgres, Blob)

Vercel resells Upstash (KV / Redis), Neon (Postgres), and provides Blob storage. The integration is one click and the UX is excellent for prototypes. The price is the catch.

StorageVercel-branded priceDirect provider priceVerdict
KV / RedisUpstash + 30-50% marginUpstash direct, same SLAUse Upstash directly past prototype
PostgresNeon + Vercel marginNeon direct, identical PostgresUse Neon direct, or Supabase / Render
Blob$0.15/GB stored, $0.30/GB egressR2: $0.015/GB stored, $0 egressR2 wins for any media workload

For prototypes and quick MVPs, the integrated storage is worth the markup. For anything past 10k users, click out to the provider's direct console and migrate the connection string. You'll halve the bill with no architectural change.

The Vercel pricing trap

Vercel's pricing page shows $20/seat/month for Pro and looks reasonable. The bill at the end of the month rarely is. Four dimensions stack:

DimensionWhat you payWhen it bites
Bandwidth$0.15 / GB after 1 TB Pro allowanceHigh-traffic content sites, image-heavy apps
Function invocations$0.60 per million, plus $0.128 / CPU-hour, plus $0.0106 / GB-hourSSR-heavy dashboards, every request hits a function
Image transformationsPer unique transformation past free tierDynamic resizing on product images / avatars
Build minutes$0.126 / minute on Turbo (Feb 2026 default)Monorepos, frequent PRs, slow builds

A real example we audited: a Next.js dashboard with 25 PRs/month and ~50k MAU was billed $415 in build minutes alone before any traffic costs. The fix was a Turborepo cache hit rate of 70% and pruning preview deploys to relevant apps only. Bill dropped to $80.

There are no hard spend caps. A misconfigured cron, a viral tweet, or a runaway loop can bill four figures overnight, and Vercel will cheerfully process it. Set a billing alert on day one.

When to leave Vercel

Vercel is the right answer for most apps under $1M ARR. The math flips in three scenarios:

  • Bandwidth-heavy content sites. If you push more than 5 TB/month of static assets, Cloudflare Pages costs $0 in bandwidth versus Vercel's $600+. The DX is rougher; the savings are real.
  • Long-running or stateful workloads. Vercel Functions cap at 60 seconds (Hobby) or 300 seconds (Pro). PDF generation, video encoding, batch jobs, or WebSocket-heavy features belong on Render, Fly, or Railway. Run a separate worker, leave the Next.js front door on Vercel.
  • Cost predictability matters more than DX. A flat-rate $25/month Render web service or a $5/month Fly.io machine is boring and predictable. For an internal tool used by 30 people, the metered model is overkill.

You don't have to go all-or-nothing. The most common production pattern we see in 2026: Next.js front door on Vercel for the marketing site and dashboard, long-running jobs on Render or Fly, static media on Cloudflare R2, Postgres on Neon directly. Each piece on the platform that prices it best.

Common pitfalls

  • Forgetting to set runtime = 'edge' on routes that don't need Node, then paying Node pricing for trivial JSON responses.
  • Calling revalidatePath('/') from a webhook and crashing the cache for every visitor in the same second.
  • Running next dev against production env vars and writing to the production database by accident.
  • Letting preview deploys accumulate with migrations attached; old previews keep DB connections open.
  • Skipping output: 'standalone' in next.config.js if you ever plan to self-host alongside Vercel.

Where Cadence engineers fit in

If you're stuck mid-rollout (ISR not invalidating, Server Actions misbehaving in production, build minutes spiraling) the work is small but specialized. Cadence's senior tier ($1,500/week) routinely owns Next.js production hardening: deploy pipelines, monitoring, cache invalidation, and the cost-trim audit. Every engineer on the platform is AI-native, vetted on Cursor and Claude Code fluency before they unlock bookings, so they ship the first PR fast. Median time to first commit across our 12,800-engineer pool is 27 hours.

If you'd rather audit the stack first, our free ship-or-skip stack audit grades your Vercel + Next.js setup against this checklist and tells you which of the four cost dimensions you're already triggering. It takes 90 seconds.

Steps

  1. Push to Git and import at vercel.com/new. Pin Node version, set Framework Preset to Next.js, accept default build command.
  2. Add environment variables under Project Settings, scoped per environment. Pull locally with vercel env pull.
  3. Configure vercel.json with regions, buildCommand, and git.deploymentEnabled to control where and when functions run.
  4. Add custom domain, point DNS, wait for the Let's Encrypt cert. Set canonical redirect between apex and www.
  5. Enable ISR per route with export const revalidate = N, and wire on-demand revalidateTag from your CMS webhook.
  6. Add Edge Config for feature flags and kill switches. Wire a maintenance toggle in middleware.ts first.
  7. Configure next.config.js with images.remotePatterns and bump minimumCacheTTL to 86400.
  8. Install @vercel/analytics and @vercel/speed-insights in the root layout. Set Web Vitals budgets in CI.
  9. Add crons block to vercel.json and verify Bearer token in handlers.
  10. Set a billing alert under the team's billing settings on day one. Audit invocations, bandwidth, and build minutes monthly.

Auditing your Vercel + Next.js setup before scale matters more than picking a cheaper alternative. Cadence's ship-or-skip stack grader returns an honest report card in 90 seconds, and you can book a senior engineer for a focused 48-hour deploy hardening sprint at $1,500/week if the report flags real issues.

FAQ

How long does deploying a Next.js app to Vercel take the first time?

Three to five minutes if your repo is on GitHub already. The Vercel import flow auto-detects Next.js, runs next build, and gives you a URL. The 10-step hardening playbook above takes another two to four hours, mostly spent writing config and wiring monitoring.

Do I need a vercel.json file at all?

No. Vercel auto-detects every default. You add vercel.json when you want to pin regions, define cron jobs, set custom headers, override the build command, or enable git-deployment rules. For a typical Next.js app shipping today, you'll add it within the first week.

What's the cheapest way to handle ISR for a 100k-page content site?

Use tag-based revalidation, set minimumCacheTTL on images to 24 hours or more, and route static assets through Cloudflare R2 (zero egress) instead of Vercel Blob. ISR cache hits don't count as function invocations, so the bill stays under control as long as your hit rate is high. Watch for cache stampedes after a global revalidate.

When do Server Actions make sense versus a separate API route?

Server Actions shine for forms and mutations co-located with the page that triggers them. They reduce round-trip code and remove a layer of API plumbing. For shared logic across multiple frontends, mobile apps, or third-party consumers, a dedicated API route or REST endpoint still wins. We cover the tradeoffs in the Server Actions Next.js 15 guide and our REST API design playbook.

Can I run a Postgres migration in a Vercel deploy?

Yes, but be careful. Run migrations in a separate one-off step before the deploy promotes traffic, not as part of the build. The cleanest pattern: a predeploy GitHub Action that runs drizzle-kit migrate against the production database, then triggers the Vercel build via a deploy hook. Migrating inside the build couples schema changes to deploy success and creates ugly rollback scenarios.

How do I know it's time to leave Vercel?

When your monthly bill crosses $2,000, when a single function bills more than $200/month, or when you hit the 300-second function timeout. Below that threshold, the platform pays for itself in shipped features. Above it, do the math on Render, Cloudflare Pages, or Fly. Our Vercel vs Cloudflare Pages comparison and Vercel pricing review walk through the migration math.

All posts