Vercel is the fastest place to ship a Next.js app and, past a certain traffic level, one of the most expensive places to run it. This is the senior-engineer playbook for moving off Vercel onto AWS: the four realistic targets (Amplify Hosting, SST/OpenNext on Lambda + CloudFront, ECS/Fargate, or pure S3 + CloudFront for static), how every Vercel primitive maps — serverless and edge functions, ISR, image optimization, middleware, preview deploys — the real cost-at-scale math, the Next.js-on-AWS quirks that actually bite, and the cutover. A MAP-funded AWS partner can run the whole thing — often at little-to-no cost to you.
Nobody migrates off Vercel because the developer experience is bad — it's the best in the category. They migrate because that experience is priced as a multiplier on every unit of traffic, and once the app is successful, the multiplier becomes the line item finance asks about.
The dominant reason is cost at scale, and it's structural rather than a billing accident. Vercel resells AWS (and other) infrastructure with a convenience margin, and it meters the things a busy app does most. Bandwidth is billed well above raw CDN egress — past the plan allowance you pay per-GB at a rate that is a multiple of what CloudFront egress costs at the same volume. Serverless and edge function execution is billed per invocation plus duration, so a high-traffic SSR app generates millions of billable invocations a month. ISR (Incremental Static Regeneration) writes, edge-middleware invocations, and the Image Optimization API are each separately metered. None of these are unreasonable individually; together, at scale, they compound into a bill that grows faster than your traffic.
The second reason is control and lock-in. Vercel abstracts the CDN, the function runtime, the build pipeline, and the edge network into a single opinionated product. That is exactly what you want at v1 and exactly what chafes once you need a VPC-private connection to your database, a custom WAF rule set, a specific caching policy, multi-account isolation for compliance, or co-location with the rest of your AWS estate (queues, RDS, S3, Bedrock). On Vercel those needs route through Vercel's primitives and pricing; on AWS you own them directly.
The third reason is consolidation. Most teams that outgrow Vercel already run the rest of their backend on AWS — the database is on RDS or Aurora, the queue is SQS, object storage is S3, auth touches Cognito, and increasingly the AI features run on Bedrock. Keeping the Next.js front end on Vercel means a second vendor, a second bill, cross-cloud egress between your front end and your AWS data, and a second security/compliance surface. Pulling the front end onto AWS removes the egress, unifies the bill, and puts the whole app inside one account boundary.
The honest counterpoint: Vercel's DX, preview deploys, and zero-config Next.js support are genuinely excellent, and AWS asks you to own more — a build pipeline, a CDN distribution, function packaging, and the Next.js-on-Lambda adapter layer. That operational surface is precisely why most teams don't run this migration alone — and the CloudRoute angle is that a vetted AWS partner runs it for you, with AWS often funding the work.
A successful Next.js app serving real traffic commonly runs $350–$2,000+/month on Vercel Pro/Enterprise once bandwidth, function invocations, ISR, and image optimization stack up — and it scales roughly linearly with traffic. The same app on AWS — CloudFront + Lambda (via OpenNext) or Fargate + S3, with the image API on Lambda and a CloudFront cache policy — commonly lands at $80–$500/month at the same traffic, because you pay AWS list/committed price for egress and compute instead of a platform multiplier. That recovered margin is what pays for the migration (and with MAP, AWS pays for it instead).
The single most common mistake is assuming Vercel→AWS means one specific architecture. It doesn't. There are four sensible targets, and the right one depends on whether you're running a full Next.js app (SSR/ISR/middleware) or a static export, and how much operational control you want versus how close to the Vercel experience you want to stay.
Picking the wrong target is what turns a two-week migration into a two-month one. The CloudRoute routing logic for a Vercel inquiry is essentially this triage: is the app fully static or dynamic, does it lean on ISR / edge middleware / the image API, and does the team want a managed Vercel-like product or a self-owned serverless stack?
What it is: AWS's managed web-hosting product with first-class Next.js support — server-side rendering, ISR/SSG, API routes, image optimization, git-based deploys, and preview branches. It is the closest thing AWS offers to the Vercel experience: connect the repo, Amplify builds and deploys, and it provisions the CloudFront + Lambda + S3 behind the scenes for you.
Best for: teams that want to leave Vercel for the cost and consolidation but keep a managed, low-ops, git-push workflow — and don't need deep control over the underlying CloudFront/Lambda configuration.
The tradeoff: Amplify supports a defined set of Next.js versions and features; bleeding-edge App Router / streaming / advanced-middleware behavior can lag what Vercel ships day-one. Verify your exact Next.js version and feature set against Amplify's supported matrix in the Assess phase — this is the single most important compatibility check for this target.
What it is: OpenNext is an open-source adapter that compiles a Next.js build into the AWS primitives Vercel uses under the hood — the server runs on Lambda (or Lambda@Edge), static assets sit in S3, CloudFront is the CDN, ISR uses S3 + a revalidation queue (SQS) + DynamoDB for tag/revalidation tracking, and the Image Optimization API runs as its own Lambda. SST is the infrastructure framework that deploys this stack (and the surrounding resources) as code.
Best for: teams that want the full Vercel feature set — SSR, ISR/on-demand revalidation, edge middleware, image optimization — but self-owned in their own AWS account, with full control over caching, networking, and cost, and no platform margin.
The tradeoff: you (or the partner) own the OpenNext/SST configuration and its upgrade cadence as Next.js evolves. This is the most faithful Vercel replacement and the most common landing spot for "we love Next.js, we just can't afford Vercel at this traffic" — but it is real infrastructure-as-code, not a managed product.
What it is: run Next.js in output: 'standalone' (Node server) mode inside a container on ECS/Fargate behind an Application Load Balancer, with CloudFront in front for caching and static assets on S3. The app is one always-on Node server rather than per-request Lambdas.
Best for: teams that already run other services on ECS/Fargate (consolidation), want predictable always-on latency with no cold starts, need long-lived connections (WebSockets, SSE/streaming), or run a custom Node server alongside Next.js.
The tradeoff: you pay for the container 24/7 rather than per-request, so it is most cost-effective at steady, sustained traffic; ISR and on-demand revalidation work but you manage the shared cache (e.g. via a shared volume or a cache handler backed by S3/Redis) across tasks. It does not give you the per-request edge scaling Lambda does, which is fine for most apps.
What it is: if the app is fully static — next build && next export (output: 'export'), a marketing site, docs, or a SPA with a separate API — the entire thing is static files in an S3 bucket served by CloudFront, with ACM for TLS and Route 53 for DNS. No Lambda, no server.
Best for: static sites, documentation, landing pages, and SPAs where there is no server-side rendering. This is by far the cheapest target — pennies-to-single-dollars a month for most sites — and the lowest-ops.
The tradeoff: none if the app is genuinely static; but the moment you need SSR, ISR, API routes, or middleware, you're back to Target A or B. Many teams discover that what they think is "static" actually uses a couple of dynamic routes — flag that in Assess so you don't pick D and then have to re-platform.
The full row-by-row mapping — every Vercel feature, where it lands on AWS, and the effort — lives in the comparison table further down. Three principles make reading that table safe, because the Vercel→AWS mapping is less about servers and more about translating Vercel's edge-and-function model into CloudFront + Lambda primitives.
First, the static and CDN layer is the easy part. Vercel's global edge network is CloudFront; the static assets Vercel serves from its edge cache live in S3 fronted by CloudFront with an appropriate cache policy. Custom domains and automatic TLS become Route 53 + ACM (free certificates). For a static export, that is the entire migration — these are the "trivial/low" rows.
Second, the compute layer is the substantive part, and it maps cleanly once you choose a target. Vercel Serverless Functions and Next.js API routes / Route Handlers become Lambda functions (Amplify and OpenNext generate these for you; on Fargate they run inside the Node server). Vercel Edge Functions and Next.js Middleware become Lambda@Edge or CloudFront Functions — CloudFront Functions for lightweight header/redirect logic at the edge, Lambda@Edge for heavier middleware that needs a full runtime. This is where an adapter (OpenNext) or a managed product (Amplify) earns its place: it does this translation so you don't hand-wire every function.
Third, the stateful Next.js features are the ones that need deliberate design: ISR and on-demand revalidation need a cache store (OpenNext uses S3 + DynamoDB + an SQS revalidation queue; Amplify manages it; Fargate needs a shared cache handler) and the Image Optimization API needs its own Lambda (or a service like a CloudFront image handler). Environment variables and secrets move from Vercel's project settings into AWS Secrets Manager + SSM Parameter Store, injected at build and runtime. These are the "medium/high effort" rows — and exactly the work a migration partner does for you.
The substantive engineering in a Vercel→AWS move is reproducing Vercel's function-and-edge behavior on AWS primitives. It is well-trodden ground — OpenNext and Amplify exist precisely to do it — but it is where teams without serverless experience slow down, so here is exactly what each piece involves.
Serverless functions and API routes are the most direct mapping. A Vercel Serverless Function (or a Next.js API route / App Router Route Handler) becomes an AWS Lambda function fronted by CloudFront (and, in OpenNext, a single "server" Lambda handles SSR and route handlers). The runtime model is the same — request in, response out, scale to zero — so the main differences you tune are the memory/timeout settings, cold-start behavior (provisioned concurrency if a route is latency-critical), and packaging. On Amplify and OpenNext the function packaging is generated from your build; on Fargate the routes simply run inside the always-on Node server, sidestepping cold starts entirely.
Edge functions and middleware are the part that genuinely differs. Vercel runs middleware on its edge runtime at every matched request. On AWS you split this by weight: lightweight logic (auth-cookie checks, header rewrites, redirects, A/B bucketing) runs as a CloudFront Function — extremely fast, extremely cheap, runs at the edge, but a constrained JS runtime; heavier middleware that needs a full Node runtime (calling an API, complex rewrites) runs as Lambda@Edge. The nuance worth knowing up front: Lambda@Edge has its own limits (size, timeout, no VPC access at edge) and deploys globally, so it has a slightly longer deploy cycle. OpenNext maps Next.js Middleware onto this automatically; the design decision is just which logic belongs at the edge versus in the origin Lambda.
ISR and on-demand revalidation are the most Next.js-specific piece. On Vercel, ISR "just works." On AWS you need somewhere to store the regenerated pages and a mechanism to revalidate them. OpenNext implements this with S3 (the rendered page cache), DynamoDB (tag and revalidation tracking), and an SQS queue (to debounce and process background revalidation) — so revalidate intervals and revalidateTag()/revalidatePath() on-demand calls behave as they do on Vercel. Amplify manages this internally. On Fargate you configure a custom Next.js cache handler backed by a shared store (S3 or Redis/ElastiCache) so all tasks share one cache rather than each task regenerating independently. This is the row most likely to surprise a team doing it by hand — and the strongest argument for using OpenNext or Amplify rather than rolling it from scratch.
The Image Optimization API (next/image) becomes its own Lambda that resizes/optimizes on demand and caches the results in S3/CloudFront — OpenNext ships an image-optimization function; Amplify includes image handling; on Fargate it runs in-process or as a sidecar. It is metered per-image on Vercel and is frequently a meaningful chunk of the Vercel bill, so moving it to a cached Lambda is often where a surprising amount of the savings comes from.
The piece teams worry about losing most is Vercel's git workflow — the preview deployment per pull request and the per-environment env vars. Both reproduce on AWS; the fidelity depends on which target you chose.
On Amplify Hosting this is nearly a like-for-like swap: Amplify connects to your Git provider, builds on every push, and provisions preview deployments per pull request with their own URLs — the same mental model as Vercel preview deploys. Per-branch and per-environment environment variables are managed in the Amplify console, mapped to your main/staging/preview branches. For teams whose attachment to Vercel is really an attachment to the preview-deploy workflow, Amplify is the path of least disruption.
On the self-owned targets (OpenNext/SST or Fargate), the build pipeline becomes AWS CodePipeline + CodeBuild, or — very commonly — you keep GitHub Actions (or GitLab CI) and have it run the SST/OpenNext deploy or build-and-push the container to ECR and update the service. Preview environments are reproduced by deploying a per-pull-request stage (SST makes ephemeral per-stage deploys straightforward) or a per-branch CloudFront distribution; it is more configuration than Amplify's built-in previews, but it is fully under your control and there is no per-seat or per-preview platform cost.
Environment variables and secrets move out of Vercel's project settings and split by sensitivity, exactly as in any AWS migration: real secrets (API keys, database credentials, signing secrets) into AWS Secrets Manager, non-sensitive config (public URLs, feature flags, the deployment environment) into SSM Parameter Store, injected at build time and runtime. The one Next.js subtlety to get right: NEXT_PUBLIC_* variables are inlined at build time, so they must be present in the build environment (CodeBuild/Amplify/Actions), while server-only secrets are resolved at runtime from Secrets Manager — a partner scripts the export-and-sort so nothing is missed or accidentally baked into a public bundle.
A Vercel→AWS cutover is one of the gentler ones in the migration cluster, because a front end is largely stateless — the database usually doesn't move (it's often already on RDS/Aurora or a managed provider). The whole new stack can be built, deployed, and tested in parallel without touching production, and the switch is a DNS change.
Because the app tier is stateless, the cutover technique is straightforward: stand up the full AWS target (Amplify app, or the OpenNext/SST stack, or the Fargate service + CloudFront), point it at the same backend/database, and deploy the app there behind a temporary hostname. Run the full test suite and a manual smoke test against that hostname — every route, SSR pages, ISR revalidation, middleware/auth behavior, image optimization, API routes, and the preview workflow. Nothing in production is affected yet because public traffic still goes to Vercel.
When the AWS stack is validated, the cutover is a DNS switch: lower the TTL on your domain a day ahead, then repoint the apex/CNAME from Vercel to the CloudFront distribution (or the Amplify domain). Because both serve the same backend and the front end holds no durable state, there is no data to migrate at the flip — user-visible downtime is effectively the DNS propagation window, commonly 0–5 minutes, and you can run both in parallel during propagation so requests resolve to whichever endpoint they hit. If your database is also moving (e.g. you're leaving a Vercel-integrated Postgres for RDS), that database cutover follows the standard DMS/continuous-replication pattern covered in the Heroku and database-migration guides, and it — not the front end — sets the downtime window.
The rollback is equally clean and is the reason this cutover is low-risk: keep the Vercel project live until you're confident, and if anything is wrong, repoint DNS back to Vercel. With a 60-second TTL the rollback is as fast as the cutover. Most teams keep Vercel running for a few days to a couple of weeks as a warm fallback, then cancel the plan — and that cancellation is the moment the 50–80% savings become real.
A Next.js front end is stateless — the durable state lives in your database, which usually isn't moving. So the cutover is a DNS repoint between two stacks serving the same backend, not a data migration. That makes Vercel→AWS one of the safest moves in the cluster: near-zero downtime, instant DNS rollback, and no risk to your data. The work is in faithfully reproducing the Next.js behavior, not in the switch.
Here is the end-to-end sequence a partner runs, mapped to the AWS MAP phases (Assess → Mobilize → Migrate). For a typical Next.js app this is a 2–6 week project — most of it parallelizable, none of it touching production until the final DNS flip.
Most Vercel→AWS migrations that go sideways do so for a small, repeatable set of Next.js-specific reasons. None are dealbreakers; all are cheaper to know than to discover at cutover.
The definitive lookup: every Vercel primitive, how you reproduce it on AWS, which target handles it, and the engineering effort. "Trivial/Low" rows are config or generated for you; "Medium/High" rows are the real work — and the work a partner does for you. (Amplify and OpenNext generate most of the medium-effort rows automatically.)
| Vercel feature | AWS approach | Who generates it | Effort |
|---|---|---|---|
| Global edge CDN | Amazon CloudFront | Amplify / OpenNext / manual | Trivial |
| Static assets | Amazon S3 (behind CloudFront) | Amplify / OpenNext | Trivial |
| Static export (output: export) | S3 + CloudFront only — no server | Direct deploy | Low |
| SSR / Server Components | Lambda (OpenNext/Amplify) or Node on Fargate | OpenNext / Amplify | Medium |
| API routes / Route Handlers | AWS Lambda (or in the Fargate Node server) | OpenNext / Amplify | Medium |
| Serverless Functions | AWS Lambda + CloudFront | OpenNext / Amplify | Medium |
| Edge Functions / Middleware | CloudFront Functions (light) / Lambda@Edge (heavy) | OpenNext | Medium–High |
| ISR / on-demand revalidation | S3 + DynamoDB + SQS (OpenNext); shared cache on Fargate | OpenNext / Amplify | High |
| Image Optimization (next/image) | Dedicated image Lambda, cached in S3/CloudFront | OpenNext / Amplify | Medium |
| Preview deployments (per PR) | Amplify previews, or per-PR SST stage / per-branch CF | Amplify (built-in) / SST | Low–Medium |
| Environment variables / secrets | Secrets Manager + SSM Parameter Store | Partner scripts the migration | Low |
| Build pipeline | CodePipeline + CodeBuild, or keep GitHub Actions | Partner / your CI | Medium |
| Custom domains + auto TLS | Route 53 + ACM (free certificates) | Manual (one-time) | Low |
| Analytics / Web Vitals | CloudWatch RUM + CloudFront logs (or keep a SaaS) | Partner / your choice | Low |
Situation: The Vercel bill had become the second-largest infra line item, driven by bandwidth and function invocations on the SSR/ISR pages plus image optimization, and it scaled linearly with a growing user base ahead of a raise. The four-engineer team wanted to consolidate the front end into their existing AWS account (kill the cross-cloud egress, unify the bill and the SOC 2 surface) but had no serverless/CloudFront experience and couldn't lose Vercel's preview-deploy workflow or risk breaking ISR.
What CloudRoute did: Routed within 24 hours to an AWS Advanced-tier partner with a documented Next.js / OpenNext track record, who ran the MAP Assess phase (free) and filed the work as a MAP engagement. Target: OpenNext on Lambda + CloudFront + S3, with ISR backed by S3 + DynamoDB + an SQS revalidation queue, edge middleware split between CloudFront Functions (auth-cookie + geo) and Lambda@Edge, the image API on a dedicated cached Lambda, env vars moved into Secrets Manager + Parameter Store, and per-PR preview stages via SST with GitHub Actions driving deploys. Provisioned concurrency was set on the two latency-critical routes.
Outcome: Cutover was a Tuesday-morning DNS flip with no measurable downtime — the OpenNext stack was validated against a temp hostname and both endpoints ran in parallel through propagation. Steady-state AWS cost (CloudFront + Lambda + S3 + DynamoDB + SQS, including the image API) landed at ~$430/month versus ~$2,400 on Vercel — an ~82% cut — plus the cross-cloud egress to the backend disappeared. Project ran ~4 weeks; preview deploys preserved via SST stages. Because the workload qualified for MAP, AWS funded the assessment and credited the migration cost — out-of-pocket migration cost was effectively $0, and CloudRoute's commission was paid by the partner from MAP funding.
project length: ~4 weeks · cutover downtime: ~0 min (DNS) · monthly spend: $2,400 → $430 (−82%) · migration cost to customer: ~$0 (MAP-funded)
CloudRoute routes you to a vetted AWS partner who plans and runs the whole Vercel→AWS migration — the right target (Amplify, OpenNext/SST, Fargate, or static), the Next.js wiring (SSR, ISR, edge middleware, image optimization), preview deploys, and the near-zero-downtime DNS cutover. Qualifying migrations are MAP-funded, so you capture the 50–80% ongoing savings without paying the usual migration bill.