Skip to content
ExponaBLOG
REACT & NEXT.JS · DAY 080 / 100

Deploying Next.js: a React & Next.js guide for developers

Vercel, Docker or a VM — the trade-offs.

Expona Admin7 min read

After 80 days of building React and Next.js applications, the final piece of the puzzle is knowing how to get your work in front of users. Deploying Next.js isn't a one-size-fits-all decision: the right choice depends on your team's capacity, budget, and tolerance for infrastructure complexity. In this post, I'll walk through the main deployment options, their real trade-offs, and how to pick the one that won't cost you weeks of DevOps work or your sanity at 2 AM.

Options: Platform vs. Trade-off

I've deployed Next.js applications across all three major categories, and each has dominated different projects in my career. Let me break down the honest comparison.

Vercel: Easiest, best features, cost at scale

Vercel is the managed platform built by the creators of Next.js, and it handles most of the complexity for you. Deploying is literally connecting your Git repository and pushing code. Your site is live in seconds.

What you get: automatic edge caching at 200+ global data centers, built-in image optimization, automatic ISR (Incremental Static Regeneration), preview deployments for every branch, instant rollbacks, serverless functions, and middleware execution. The developer experience is exceptional—you ship code, not infrastructure.

The trade-off: Vercel costs money as you scale. A side project runs free. A production app with 100k requests daily costs you $20–50/month. With 1M+ requests or heavy computation, you're looking at $200–500+. For bootstrapped startups or agencies, this adds up. You also have less visibility into what's happening under the hood, and you're locked into Vercel's runtime and constraints.

I use Vercel for client projects where time-to-market matters more than infrastructure ownership. The ROI on not hiring a DevOps engineer typically pays for itself in weeks.

Docker + VM: Full control, you run it

Self-hosting via Docker on a rented VM (DigitalOcean, Hetzner, AWS EC2) gives you complete control. You own the entire stack. This is the path I take when a client needs to reduce ongoing costs or comply with data residency requirements.

What you build: a Docker image containing your built Next.js application, typically using next build and next start, or the standalone output format. You run it behind Nginx as a reverse proxy. You manage SSL certificates, monitoring, log aggregation, and backups yourself.

The trade-off: flexibility comes with responsibility. A single VM costs $5–20/month, but now you're responsible for security patches, scaling, downtime, and debugging production issues. Most teams underestimate this. You also don't get free global CDN or edge caching—users in Singapore fetch from your single server in Frankfurt.

Self-hosting makes sense when you have someone on the team who enjoys ops work, or when your traffic is predictable and low enough that a single server handles it comfortably.

AWS / Amplify / SST: Cloud-native, more setup

These services sit between Vercel's simplicity and Docker's control. AWS Amplify can deploy Next.js with automatic scaling and integrations to RDS, S3, and Lambda. SST (Serverless Stack Toolkit) gives you Infrastructure as Code for serverless Next.js deployments.

What you gain: AWS's ecosystem (database, auth, storage, CDN) all under one roof. You get production-grade scaling without managing VMs. Your app automatically scales from 0 to 1000s of concurrent users.

The trade-off: the AWS learning curve is real. Amplify abstracts away some choices (which can be good), but when something breaks, you need to understand CloudFormation, IAM roles, and Lambda cold starts. Pricing is also non-obvious—a small app might cost $30/month, but a misconfigured Lambda that runs constantly could cost $300. I've seen this happen.

AWS shines for teams with DevOps bandwidth or enterprises that already live in AWS. For a 5-person startup, it's often overkill.

Platform Setup time Monthly cost (1M req/mo) Scaling Best for
Vercel 5 mins $200–400 Automatic, infinite Agencies, fast shipping
Docker + VM 2–3 hours $10–30 Manual, vertical only Cost-sensitive, DevOps-capable
AWS / Amplify 4–8 hours $100–300 Automatic, scales infinitely Enterprise, complex infrastructure

Self-hosting works: next start or standalone output

If you choose Docker + VM, here's how you actually run a Next.js app on a server you control.

The cleanest approach is Next.js's standalone output. When you run next build, you get a .next/standalone folder that contains everything needed to run the app without the entire node_modules. This drastically reduces your Docker image size.

tsx
// next.config.js
module.exports = {
  output: 'standalone',
};

Then your Dockerfile looks like this:

tsx
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]

Push this to any container registry, and run it on your VM:

tsx
docker pull myapp:latest
docker run -d -p 3000:3000 myapp:latest

Now you have a Next.js process running on your server. Put Nginx in front of it as a reverse proxy, handle SSL termination, and you're done. This setup has run production apps I've built for e-commerce platforms with 50k+ concurrent users. It's reliable and boring, which is exactly what you want in production.

Cache and images: Self-hosted ISR needs a shared cache

Here's where self-hosting gets tricky: if you run multiple instances of your Next.js app behind a load balancer (which you should for high availability), ISR breaks.

ISR works by caching generated pages to disk (.next/cache). If you have three Docker containers running, each has its own isolated .next/cache folder. When a user requests a revalidated page, one instance regenerates it and caches locally. Another instance, serving the next request, has no idea that page was regenerated and serves a stale version.

The solution: externalize your cache. Use Redis or a shared file system (NFS, or cloud storage like S3).

For Redis, you'd configure Next.js to use a custom cache handler:

tsx
// pages/api/revalidate.ts
import redis from 'redis';

const client = redis.createClient({
  url: process.env.REDIS_URL,
});

export const handler: NextApiHandler = async (req, res) => {
  if (req.query.secret !== process.env.REVALIDATE_SECRET) {
    return res.status(401).json({ message: 'Invalid token' });
  }

  try {
    await redis.del(`cache:${req.query.path}`);
    res.revalidate(req.query.path as string);
    return res.json({ revalidated: true });
  } catch (err) {
    return res.status(500).send('Error revalidating');
  }
};

Or, for a simpler approach, use S3 as your cache backend. AWS provides an example cache handler for this. It's heavier than Redis but works reliably for distributed setups.

Vercel handles this automatically. Docker + VM requires you to think about it. This is a hidden complexity that catches teams off-guard.

Choose by team: Without DevOps, managed hosting saves weeks

This is the honest conversation I have with every client. Ask yourself: Does your team want to operate infrastructure, or ship features?

A 3-person startup with no dedicated DevOps engineer using Vercel ships their first product in a day. The same team self-hosting spends a week on Docker, Nginx, SSL certificates, monitoring, and backup strategies. That's weeks of engineering time that could've been spent fixing bugs or talking to users.

For a bootstrapped SaaS, that opportunity cost is enormous. You're paying Vercel $50/month to save $1000s in engineering time and reduce your bus factor (the risk that one person leaving breaks production).

Conversely, a profitable company processing millions of requests annually might self-host because $500k/year in Vercel bills can fund an entire infrastructure team.

My advice: start with Vercel. As you grow and traffic becomes predictable, revisit the decision. Moving from Vercel to self-hosted Docker is straightforward (your app doesn't know or care). Moving the other direction is harder because you've built custom infrastructure.

Common mistakes

  • Forgetting ISR cache on multi-instance setups: Deploying to Kubernetes with three replicas, then wondering why ISR doesn't work. You need Redis or S3 before you scale horizontally.
  • Underestimating hidden DevOps time: "We'll self-host to save money." Then your certificate expires at 3 AM on a Sunday and you're scrambling to fix it. Budget for operational overhead.
  • Not using standalone output: Running next start instead of next build + standalone means a 500MB Docker image instead of 100MB. It doesn't sound like much, but deployment times balloon.
  • Leaving your application server directly on the internet: Always put Nginx or a reverse proxy in front of your Next.js process. It handles SSL termination, compression, and shields your app from malformed requests.
  • Ignoring monitoring on self-hosted: Vercel's built-in observability is part of its value. Self-hosting requires you to add your own logging (Datadog, New Relic, ELK) or you're blind when things break.

Key takeaways

  • Vercel is the fastest path to production and worth the cost for most teams. You trade infrastructure ownership for time and reliability.
  • Docker + VM is viable when you have ops bandwidth or strict cost requirements. Use standalone output and always put Nginx in front.
  • AWS and similar cloud platforms offer middle ground but add complexity. Only choose this if you already live in that ecosystem or need advanced scaling and integration.
  • If self-hosting, solve ISR caching with Redis or S3 before you scale to multiple instances. Your future self will thank you.
  • The right choice depends on your team's size, DevOps comfort, and budget. Revisit this decision every 6–12 months as your product grows.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles