BuildWithMatija
  1. Home
  2. Blog
  3. Next.js
  4. Next.js Scaling in Production: Proven VPS Strategies

Next.js Scaling in Production: Proven VPS Strategies

Real-world guide to horizontal scaling, caching, Docker Compose, Traefik, and Postgres for self-hosted Next.js on VPS.

8th June 2026·Updated on:12th June 2026··
Next.js
Next.js Scaling in Production: Proven VPS Strategies

⚡ Next.js Implementation Guides

In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.

No spam. Unsubscribe anytime.

📄View markdown version
0

Frequently Asked Questions

About the author

Matija Žiberna

Matija Žiberna

Full-stack developer, co-founder

AboutResume

Self-taught full-stack developer sharing lessons from building software and startups.

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

Contents

  • What Actually Runs When You Deploy Next.js
  • How Much Load Can a Single Server Handle?
  • How Horizontal Scaling Works for Next.js
  • Shared Infrastructure Across Instances
  • What the Load Balancer Actually Does
  • Where Scaling Actually Gets Difficult
  • Infrastructure Tiers for Self-Hosted Next.js
  • Docker Compose — Most Client Projects Live Here
  • Kubernetes — When You Need Orchestration
  • Managed Cloud — Delegated Complexity
  • What About Docker Swarm?
  • FAQ
  • Conclusion
On this page:
  • What Actually Runs When You Deploy Next.js
  • How Much Load Can a Single Server Handle?
  • How Horizontal Scaling Works for Next.js
  • Shared Infrastructure Across Instances
  • What the Load Balancer Actually Does
Build with Matija logo

Build with Matija

Modern websites, content systems, and AI workflows built for long-term growth.

Services

  • Headless CMS Websites
  • Next.js & Headless CMS Advisory
  • AI Systems & Automation
  • Website & Content Audit

Resources

  • Case Studies
  • How I Work
  • Blog
  • CMS Hub
  • E-commerce Hub
  • Dashboard

Headless CMS

  • Payload CMS Developer
  • CMS Migration
  • Multi-Tenant CMS
  • Payload vs Sanity
  • Payload vs WordPress
  • Payload vs Contentful

Get in Touch

Ready to modernize your stack? Let's talk about what you're building.

Book a discovery callContact me →
© 2026Build with Matija•All rights reserved•Privacy Policy•Terms of Service
BuildWithMatija
Get In Touch

By Matija Žiberna — Full-stack developer. I build with Next.js, Payload CMS, and TypeScript, mostly deployed on VPSes for clients ranging from small Slovenian businesses to larger international companies. Last updated: June 2026.


For years I deployed Next.js the same way: a VPS, Docker, Postgres, a reverse proxy, and the app itself. Everything worked. I didn't think much about what was actually happening under the hood, and I didn't need to.

Then I started getting questions from clients — and asking them myself. What happens when traffic grows? How does Next.js actually scale? Is it production-ready? Can a self-hosted server genuinely serve a real business, or does it start cracking once load increases?

This article is what I found after digging into those questions through real deployments. The short answer: Next.js is more capable than most developers give it credit for, and the scaling bottlenecks almost never live where you'd expect.


What Actually Runs When You Deploy Next.js

The first misconception I had was that Next.js sat on top of Express, borrowing its HTTP server and just adding a rendering layer. That's not how it works.

In production, Next.js runs as a standalone Node.js server. When you build with output: 'standalone' and deploy the result, you're running:

bash
node server.js

That process handles Server Components, SSR, Route Handlers, API endpoints, image optimization, caching, and Incremental Static Regeneration. It's a real backend process. Framing it as "just a frontend framework" doesn't fit what it actually does.

Understanding that changes how you think about scaling. You're not trying to scale a thin rendering layer sitting in front of something else. You're scaling a Node.js server that has real responsibilities.


How Much Load Can a Single Server Handle?

The better question is: how much dynamic work actually reaches the Node process?

A surprisingly large share of traffic can bypass it entirely when the setup is right:

  • Static pages served from a CDN
  • Cached responses returned before hitting the Node process
  • ISR pages regenerated in the background and served statically
  • Image responses cached after the first optimization pass
  • Assets delivered directly from the edge

When those levers are used correctly, the Node server handles a fraction of total requests. For most applications at most traffic levels, the Node process isn't where pressure accumulates.

The actual bottlenecks tend to be database performance, slow queries, missing indexes, expensive external API calls, and poor caching strategy. A modern VPS handles significantly more traffic than developers usually assume. The question worth asking isn't "Is Next.js fast enough?" — it's "How much unnecessary work am I routing through it?"


How Horizontal Scaling Works for Next.js

When you do need to scale beyond a single server, the answer is horizontal scaling: multiple identical instances behind a load balancer, rather than a larger single machine.

code
Cloudflare
    ↓
Load Balancer
    ↓
Next.js Instance A
Next.js Instance B
Next.js Instance C
    ↓
Postgres

Each instance runs the same code with the same environment variables. The load balancer distributes incoming requests across them. The key constraint this creates is statelessness — no request-specific state can live on the application server itself. Sessions, cache, file uploads — all of that must live outside the process, in Redis, a shared database, or object storage.

This is also what makes horizontal scaling practical. Application servers become disposable. If one dies, the others continue serving traffic. Deployments become a matter of replacing instances one at a time with no downtime.


Shared Infrastructure Across Instances

Multiple Next.js servers connecting to the same database and the same Redis instance is normal and expected. The application servers are ephemeral; the data layer is the source of truth.

A typical environment configuration shared across all instances:

env
# File: .env
DATABASE_URL=postgres://...
REDIS_URL=redis://...
S3_ENDPOINT=https://...
PAYLOAD_SECRET=...
NEXT_PUBLIC_SITE_URL=https://...

Every instance reads the same config. Any instance can handle any request. That uniformity is what makes the load balancer's job straightforward.


What the Load Balancer Actually Does

The load balancer is the entry point for all traffic. Its responsibilities go beyond just routing requests:

  • Distributing load across healthy instances
  • Running health checks and removing unhealthy servers automatically
  • Handling SSL termination so the app servers work over plain HTTP internally
  • Providing a single external endpoint regardless of how many instances are behind it

Common options for self-hosted setups:

ToolGood for
NginxSimple, battle-tested, wide support
TraefikDocker-native, automatic config via labels
HAProxyHigh-performance, fine-grained control
Cloudflare Load BalancerManaged, works well with Cloudflare CDN

For VPS-based deployments I usually reach for Traefik. It integrates cleanly with Docker Compose, picks up new containers automatically via labels, and handles Let's Encrypt certificates without manual configuration.


Where Scaling Actually Gets Difficult

The web servers are the easy part. Once you have multiple stateless Next.js instances behind a load balancer, that layer stays simple. The complexity accumulates elsewhere:

Database scaling is where most teams eventually hit a ceiling. A single Postgres primary handles a lot, but read-heavy workloads benefit from read replicas. The standard pattern:

code
Postgres Primary
├─ Read Replica 1
├─ Read Replica 2
└─ Read Replica 3

Application logic routes writes to the primary and reads to replicas. This distributes query load and gives the primary room to breathe.

Cache invalidation becomes harder across multiple instances. Anything cached in memory on instance A isn't visible to instance B. Shared Redis solves this, but it introduces a new dependency to manage.

File storage should move to object storage (S3 or compatible) from the beginning. Local disk storage doesn't work across multiple servers.

Background jobs need a shared queue so they don't run in duplicate across all instances.

Deployments require a strategy — rolling updates, zero-downtime deploys, and health check endpoints become important once you're serving real traffic.


Infrastructure Tiers for Self-Hosted Next.js

There are a few natural levels depending on where the project sits.

Docker Compose — Most Client Projects Live Here

code
Docker Compose
├─ Next.js
├─ Payload
├─ Postgres
├─ Redis
├─ Workers
└─ Traefik

Simple, reliable, and easy to reason about. For the majority of client work I run at buildwithmatija.com and through WHCP, this is the right level. A well-tuned Docker Compose setup running 2–3 Next.js replicas with Redis, Postgres, and Cloudflare in front of it can support a serious business without Kubernetes complexity.

Kubernetes — When You Need Orchestration

Kubernetes manages replicas, rolling deployments, autoscaling, secrets, storage, and health checks. If a node dies, it reschedules the affected containers. If traffic spikes, it can spin up additional replicas automatically. The tradeoff is real operational complexity. Kubernetes is the right answer at scale, but it's expensive to manage without dedicated infrastructure resources.

Managed Cloud — Delegated Complexity

AWS, GCP, and similar providers offer managed versions of each infrastructure component: RDS instead of self-managed Postgres, ALB instead of self-managed Nginx, EKS instead of self-managed Kubernetes. The operational complexity doesn't disappear — it shifts from your team to the provider.


What About Docker Swarm?

Docker Swarm still works. It's genuinely simpler than Kubernetes and can orchestrate multi-server deployments with less overhead. For teams that need something between Docker Compose and Kubernetes, it's a reasonable option technically.

In practice, most tooling, tutorials, and hosting solutions have consolidated around Kubernetes. For any project that will eventually need orchestration, going directly from Docker Compose to Kubernetes skips a migration step. Swarm is a viable detour, but most teams bypass it.


FAQ

Is Next.js production-ready for self-hosted deployments? Yes. A standalone Next.js build runs as a proper Node.js server and handles SSR, API routes, image optimization, and caching. It doesn't require Vercel or any managed platform to work reliably.

Do I need Kubernetes to scale Next.js? For most projects, no. A Docker Compose setup with 2–3 replicas behind Traefik and a properly configured Postgres instance handles significant traffic. Kubernetes becomes relevant when you need automated scaling, multi-node orchestration, or large team deployments.

Can multiple Next.js instances share one database? Yes, and this is the standard pattern. Each instance connects to the same Postgres primary (and optionally read replicas). The instances themselves stay stateless; the database holds all persistent state.

What's the most common scaling mistake? Treating the Node process as the bottleneck before confirming it actually is. Most Next.js servers under pressure are struggling with slow database queries or missing caches, not with Node.js throughput. Fixing queries and adding caching layers typically resolves the issue before any horizontal scaling is needed.

Where should session and cache data live in a multi-instance setup? In Redis, shared across all instances. In-memory caches tied to a single process break as soon as you have more than one server. Redis gives you a shared, fast store that every instance reads and writes consistently.


Conclusion

After deploying Next.js across a range of client projects — from single-VPS setups to multi-instance setups with load balancers and read replicas — the mental model that helped most was this: the Node server works best when it does less.

A well-configured self-hosted setup with aggressive caching, a CDN in front, static generation where possible, and queries that don't block unnecessarily will outperform an over-engineered multi-server setup with none of those things in place. Horizontal scaling is a real option when you need it, and the stateless architecture of Next.js makes it straightforward to implement. The infrastructure patterns covered here — Docker Compose with replicas, Traefik, shared Postgres, Redis — aren't theoretical. They're what runs actual client projects today.

If any of this raised questions about your own setup, drop them in the comments. And subscribe if you want more practical guides on deploying Next.js and Payload in production.

Thanks, Matija