Before you start: you probably should not do this
Vercel is the best place in the world to run a Next.js application. That is not a polite opening, it is the reason this guide has a long list of things you lose. If any of the following is true, close the tab and stay where you are:
- You are on the Hobby plan and it is a personal project. Vercel's own FAQ describes Hobby as "for personal, non-commercial use", and it costs nothing. There is no saving to chase.
- Your team lives on preview deployments. One preview URL per pull request, with comments attached to the rendered page, is a workflow, not a feature. We do not have it.
- Your traffic is globally distributed and latency-sensitive. Vercel serves from a global edge network. A container on one server in one region does not.
- You are heavily invested in Edge Middleware, ISR at scale, or their image optimisation pipeline. All three are the parts that translate worst.
The case for moving is narrow and it is about usage-based billing on a workload that is predictable. Vercel Pro is $20 per user per month, with $20 of included usage credit, and then meters a long list of dimensions on top. If your application is a steady-state app with a database behind it, servicing a known amount of traffic, you are paying a metered price for a workload that never varies. That is the case where a fixed monthly container is cheaper and the trade-offs are survivable.
Pricing verified 13 August 2026 from vercel.com/pricing. Every figure below is quoted from that page.
What Vercel actually charges
| Dimension | Hobby (free) | Pro ($20/user/month) |
|---|---|---|
| Fast Data Transfer | 100 GB / month included | 1 TB / month included, then from $0.15 per GB |
| Edge Requests | 1M / month included | 10M / month included, then from $2 per 1M |
| Function Invocations | 1M / month included | from $0.60 per 1M |
| Active CPU | 4 hours / month included | from $0.128 per hour |
| Provisioned Memory | 360 GB-hrs / month included | from $0.0106 per GB-hr |
| Image Transformations | 5K / month included | from $0.05 per 1K |
| ISR Reads | 1M / month included | from $0.40 per 1M |
| ISR Writes | 200,000 / month included | from $4 per 1M |
| Build machines | not shown | Standard machines at $0.014 per minute |
| Runtime log retention | 1 hour | 1 day, 30 days with Observability Plus |
Two caveats about that table. The Pro column shows explicit included allowances only for Fast Data Transfer and Edge Requests; for the other lines the page publishes a unit rate plus the $20 usage credit, not a separate free tier, so what you pay depends on your mix. And "starting at" is Vercel's own wording, because rates vary by region. Do not read these as ceilings. Put your own numbers in the Vercel cost calculator before spending a morning on any of this.
Step 1 — Export your environment variables
The Vercel CLI will pull them for you, per environment. Do it for production explicitly, because the default is your development environment and the values differ:
npm i -g vercel
vercel login
vercel link
vercel env ls
vercel env pull --environment=production .env.production
That writes a KEY="value" file. Three things to fix before you import it anywhere:
- Remove the
VERCEL_*variables.VERCEL_URL,VERCEL_ENV,VERCEL_GIT_COMMIT_SHAand friends are injected by the platform and mean nothing off it. If your code readsVERCEL_URLto build absolute URLs, that is a code change, not a config change. Replace it with your ownAPP_URL. - Replace connection strings that came from a Marketplace integration. Anything Vercel provisioned for you injects its variables into the project. Those need to point somewhere you control before the old project is deleted.
- Check for values that only exist as secrets.
vercel env pullgives you decrypted values for environments you have access to. Anything you cannot pull, you will have to regenerate at the vendor.
Step 2 — Sort out the database, because Vercel does not include one
This is the part people forget. Vercel has no first-party managed Postgres. The old Vercel Postgres product was moved to Neon, and Postgres now arrives through the Marketplace, billed as a separate product. Practically, that means your database was never really part of your Vercel deployment and you have two choices:
- Leave it where it is. If you run Neon, Supabase or PlanetScale, they are independent services. Point the new deployment's
DATABASE_URLat the same database and you have no data migration at all. This is the lowest-risk path and it is usually the right one for the first cutover. - Move it to a managed database here, from $3/mo for PostgreSQL, MySQL, MariaDB, Redis or MongoDB with automated backups. Do this as a second, separate change once the application is already running.
If you do move the data, dump and restore with the ownership flags:
pg_dump -Fc --no-owner --no-acl -v -d "$OLD_DATABASE_URL" -f app.dump
pg_restore --verbose --clean --if-exists --no-owner --no-acl \
-d "$NEW_DATABASE_URL" app.dump
Make a second change at the same time. Serverless functions need pooled connections because every concurrent invocation is a new client, which is why so many Vercel projects carry ?pgbouncer=true, connection_limit=1, or an HTTP-based serverless driver. A long-running container holds one pool for the life of the process, so you can drop the serverless driver, use a normal pg or Prisma connection, and set a sensible pool size. Connection counts usually fall.
Step 3 — Turn functions into a running process
On Vercel, each route is a function that is invoked, runs, and dies. Here, your application is one process that starts and stays up. For a Next.js app that difference is mostly invisible, because next build then next start produces exactly the server Vercel was shimming for you. Set:
Build command: npm run build
Start command: npm run start # next start -p $PORT
Port: 3000
Bind to 0.0.0.0 and read the port from the environment rather than hardcoding it. If you want a smaller image, add output: "standalone" to next.config.js and start from node .next/standalone/server.js.
Several things get better as a side effect, and they let you delete code. There are no cold starts. In-process caching works, so an LRU map in module scope now survives between requests, which it never reliably did across function instances. Background work after the response no longer needs waitUntil. There is no function duration ceiling, so long-running requests and slow report generation stop being a platform problem.
One thing gets worse: a single process means a single blast radius. A memory leak that function recycling used to paper over will now grow until the container restarts. Size the pack with headroom and watch memory for the first week.
Step 4 — What actually breaks
Read this section before you start, not after.
- Edge Middleware.
middleware.tsstill runs, but in the Node runtime rather than at an edge POP, so it executes at your origin and adds latency to every request instead of removing it. More importantly, the helpers from@vercel/functionssuch asgeolocation()andipAddress()return nothing off-platform. If you do geo-routing or country blocking in middleware, rewrite it againstx-forwarded-for, or against Cloudflare'scf-ipcountryheader if you proxy through Cloudflare. - ISR. Incremental Static Regeneration works self-hosted, but the cache is written to
.next/cacheon local disk. That is fine on a single container and wrong the moment you run more than one, because each replica revalidates independently and serves different content. If you scale out, configure a sharedcacheHandlerinnext.config.jsbacked by Redis. On-demand revalidation viarevalidatePathandrevalidateTagkeeps working. - Image optimisation.
next/imageoff Vercel optimises in-process usingsharp. It works, it costs you CPU and RAM on your own container, and it is not their pipeline. Make suresharpis installed in the production image. If the CPU cost bothers you, either setunoptimized: trueand serve pre-sized assets, or pointloaderat an image CDN. @vercel/*packages. Every one of these is a platform binding.@vercel/analyticsand@vercel/speed-insightsstop reporting, so self-host Plausible or Umami.@vercel/blobneeds an S3-compatible bucket.@vercel/kvbecomes a managed Redis.@vercel/postgresbecomespgor Prisma.waitUntilfrom@vercel/functionsbecomes an ordinaryawait. For OG images, Next shipsnext/og, which is the same renderer without the platform dependency.vercel.jsonis ignored. Rewrites, redirects and headers move intonext.config.js. Thecronsblock has no equivalent in the framework at all, so those become scheduled tasks that call the same endpoints over HTTPS. Add a shared secret header to them, because the routes are now publicly reachable and Vercel is no longer the only caller.maxDurationandruntime: "edge"exports are inert. Harmless, but delete them so nobody is misled later.
If your app is a Next.js application with a database, server components, API routes and no edge middleware, almost none of this applies and the move takes an hour.
Step 5 — Deploy and test on the temporary domain
Connect the same GitHub repository. Framework detection fills in the build and start commands, so for a standard Next.js repo there is nothing to configure. Import the cleaned .env.production, pick a pack, and deploy. Every deployment gets a working subdomain with SSL, so you can exercise the real thing while Vercel still serves live traffic. A Standard pack at $7/mo (1 vCPU, 2 GB, 25 GB) is the right default for a Next.js app with a database; Performance at $18/mo (2 vCPU, 4 GB) is for apps doing image work or heavy SSR.
Test the things that fail quietly:
- Any route that used to read
VERCEL_URLto construct absolute links, including OAuth callbacks and webhook URLs - Image-heavy pages, watching container memory while you load them
- ISR pages, checking they revalidate rather than serving a build-time snapshot forever
- Anything with an IP allowlist at a third party. New host, new outbound IP.
- Cron endpoints, now that they are called by a scheduler and not by Vercel
The AI DevOps agent reads build and runtime logs directly, so a failed build here is usually one question rather than an afternoon.
Step 6 — Cut over DNS
The day before: drop the TTL on the records currently pointing at Vercel to 300 seconds. If they sit at 3600 or 86400, this single step is the difference between a five-minute cutover and a day of split traffic, and it only works if you do it in advance.
At cutover:
- Add your custom domain to the new deployment and let the certificate issue. Confirm it serves over HTTPS on the real hostname before you touch DNS.
- Replace the A or CNAME record that points at Vercel with the record for the new deployment.
- Watch both sets of logs. Traffic drains from Vercel over roughly one TTL.
- Leave the Vercel project in place for a week. It is your only rollback, and reverting is one DNS change.
If your database stayed where it was, this cutover is genuinely zero-downtime, because both deployments talk to the same database and it does not matter which one a given visitor reaches. That is the main argument for not moving the database in the same change.
What you give up, stated plainly
You lose preview deployments, and there is no substitute for a per-pull-request URL with the reviewer UI on top. You lose the global edge network, so a visitor in Sydney now travels to wherever your container lives. You lose the image optimisation service, instant rollback to a previous immutable deployment, and edge-level DDoS absorption. Vercel's Next.js integration is built by the people who build Next.js, and framework features land there first. None of that is marketing on their part, and no fixed-price container replaces it.
What you get is a bill that does not move when you are linked on Hacker News, a warm process, no duration limit, and control of the runtime.
What this actually costs
The comparison that is fair is a small team on Pro. Three developer seats is 3 × $20 = $60/mo before a single request is served, since Pro is priced per user per month. The equivalent here is a Standard pack at $7/mo plus a managed PostgreSQL at $3/mo, which is $10/mo, and seats are not a billing dimension.
The comparison that is not fair is a solo developer on Hobby. That is free, and free is cheaper than $10. Stay on Hobby until the non-commercial restriction or an allowance forces your hand.
Run the real numbers, from Vercel's published rates rather than a guess, in the Vercel cost calculator.