# Deploy Next.js lên VPS, không cần Vercel

> Cách deploy Next.js lên VPS: output standalone, systemd, nginx cache /_next/static, SSL miễn phí, cập nhật không gián đoạn và những gì bạn mất so với Vercel.

Source: https://zopanel.net/vi/blog/deploy-nextjs-to-vps  
Updated: 2026-10-21

## Ý chính

- Bật output standalone, chép public và .next/static vào .next/standalone, rồi chạy node server.js với PORT và HOSTNAME.
- Trên một server, tối ưu ảnh, ISR và Proxy (trước gọi là middleware) chạy được ngay; chạy nhiều instance thì cần cache handler dùng chung và khóa Server Functions cố định.
- Để nginx tự phục vụ /_next/static với cache immutable một năm, và giữ file tĩnh cũ sau mỗi lần deploy để tránh version skew.
- Vercel Pro có giá 20 USD mỗi developer seat mỗi tháng tại thời điểm viết; VPS rẻ hơn nhưng bạn phải tự lo CDN, preview và vận hành.

Để deploy Next.js lên VPS mà không cần Vercel, bạn đặt `output: 'standalone'` trong `next.config`, chạy `next build`, chép hai thư mục `public` và `.next/static` vào `.next/standalone`, rồi chạy `node server.js` bằng systemd với biến `PORT` và `HOSTNAME`. Đặt nginx phía trước để lo tên miền, SSL và cache dài hạn cho `/_next/static`. Trên một server duy nhất, tối ưu ảnh, ISR và Proxy (trước đây là middleware) đều chạy ngay; thứ bạn mất là CDN toàn cầu, preview deployment của Vercel và việc có người khác vận hành hạ tầng cho bạn.

Bài này chỉ nói những gì riêng của Next.js. Phần cài đặt chung (Node LTS, user riêng, tường lửa, systemd hay PM2) có trong bài [deploy Node.js lên VPS](/vi/blog/deploy-nodejs-vps).

## Rời Vercel thì bạn mất những gì?

Ít hơn nhiều người nghĩ, miễn là bạn chạy một server. [Hướng dẫn self-host của Next.js](https://nextjs.org/docs/app/guides/self-hosting) nêu rõ tính năng nào chạy được trên một server Node.js thông thường:

| Tính năng | Trên một VPS (`next start` hoặc standalone) | Khác biệt |
| --- | --- | --- |
| Tối ưu ảnh (`next/image`) | Chạy được, không cần cấu hình | Từ [Next.js 15](https://nextjs.org/blog/next-15), `sharp` được dùng tự động với `next start` và output standalone. Việc resize ảnh tốn CPU và RAM của VPS. |
| ISR và data cache | Chạy được; cache lưu trên ổ đĩa local | Nhiều instance thì mỗi cái giữ cache riêng, trừ khi bạn thêm [cache handler](https://nextjs.org/docs/app/guides/self-hosting#configuring-caching) dùng chung, ví dụ Redis |
| Proxy (trước là middleware) | Chạy được | [Next.js 16 đổi tên middleware thành Proxy](https://nextjs.org/docs/app/api-reference/file-conventions/proxy), mặc định chạy trên runtime Node.js. Nó chạy trên server của bạn, không phải ở edge. |
| Streaming và Suspense | Chạy được | nginx không được buffer response (xem bên dưới) |
| Preview deployment | Không có | Vercel tạo preview cho [mỗi lần push lên nhánh không phải production và mỗi pull request](https://vercel.com/docs/deployments/environments). Trên VPS bạn phải tự dựng hoặc dùng một site staging. |
| CDN toàn cầu | Không có | File tĩnh được phục vụ từ một nơi, trừ khi bạn thêm CDN như Cloudflare |
| Vận hành | Bạn tự lo | Cập nhật hệ điều hành, backup, giám sát và tài nguyên |

Cái bẫy thật sự là chạy nhiều hơn một instance. Tài liệu Next.js liệt kê những thứ khi đó bắt buộc phải có: cache handler dùng chung, cùng một `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` trên mọi instance (nếu không sẽ gặp lỗi "Failed to find Server Action"), và `deploymentId` để xử lý version skew. Nếu một VPS là đủ, bạn bỏ qua được hết.

## Vercel hay VPS: chi phí bao nhiêu?

Tại thời điểm viết (tháng 10/2026), [trang giá của Vercel](https://vercel.com/pricing) ghi:

| | Vercel Hobby | Vercel Pro | VPS Hetzner CX23 |
| --- | --- | --- | --- |
| Giá | 0 USD | 20 USD mỗi developer seat mỗi tháng | 5,99 EUR/tháng chưa VAT |
| Mục đích sử dụng | Cá nhân, phi thương mại | Dự án thương mại | Tùy ý |
| Bao gồm | Giới hạn cố định, không mua thêm được | 20 USD credit, 1 TB Fast Data Transfer, 10 triệu edge request mỗi tháng | 2 vCPU, 4 GB RAM, 40 GB NVMe, 20 TB băng thông |
| Vượt mức | Không được | Băng thông từ 0,15 USD/GB | Băng thông thêm tính theo TB |

Số liệu Hetzner lấy từ [trang gói Cost-Optimized](https://www.hetzner.com/cloud/cost-optimized/) (khu vực EU). Hai bên không hoàn toàn tương đương: giá Vercel đã gồm CDN, preview, hạ tầng build và không có server nào phải bảo trì, còn VPS cho bạn hóa đơn cố định, nhiều băng thông hơn và chỗ cho cả database lẫn các website khác. Nếu team coi trọng preview và không muốn vận hành gì, Vercel vẫn đáng tiền.

## Cách deploy Next.js lên VPS thủ công

Các bước dưới đây giả định đã có Node.js 22 hoặc 24, nginx và user `nextapp`.

### Bước 1: Bật output standalone

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

Khi `next build`, Next.js dò xem mỗi route cần file nào và ghi ra một thư mục tự chạy được ở `.next/standalone`, gồm phần `node_modules` cần thiết và một file `server.js` tối giản. Bạn deploy thư mục đó mà không cần `npm install` trên server. [Tài liệu `output`](https://nextjs.org/docs/app/api-reference/config/next-config-js/output) lưu ý `server.js` **không** tự chép `public` và `.next/static`, vì hai thư mục này "nên được CDN phục vụ". Bạn tự chép:

```bash
npm ci
npm run build
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/
```

### Bước 2: Để ý RAM khi build

`next build` thường tốn RAM hơn app lúc chạy, và một bản build bị OOM killer dừng chỉ in ra `Killed`. [Hướng dẫn tối ưu bộ nhớ](https://nextjs.org/docs/app/guides/memory-usage) của Next.js đưa ra các cách: `experimental.webpackMemoryOptimizations`, tắt source map (`productionBrowserSourceMaps: false`), và kiểm tra kiểu trong CI thay vì lúc build. Trên VPS nhỏ, bạn có thể:

- Thêm swap, để lúc cao điểm build chậm lại thay vì bị dừng.
- Build trong CI (GitHub Actions) rồi chỉ gửi `.next/standalone` lên server. Standalone giúp việc này dễ vì thư mục không cần cài `node_modules`.

### Bước 3: Chạy server.js bằng systemd

Tạo `/etc/systemd/system/nextapp.service`:

```ini
[Unit]
Description=Next.js app
After=network.target

[Service]
User=nextapp
Group=nextapp
WorkingDirectory=/srv/nextapp/current
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=HOSTNAME=127.0.0.1
EnvironmentFile=-/etc/nextapp.env
ExecStart=/usr/local/bin/node server.js
Restart=on-failure
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/srv/nextapp

[Install]
WantedBy=multi-user.target
```

`/srv/nextapp/current` là symlink trỏ tới thư mục release chứa `server.js` (nội dung của `.next/standalone`). `HOSTNAME=127.0.0.1` giữ server không lộ ra interface công khai; [tài liệu output](https://nextjs.org/docs/app/api-reference/config/next-config-js/output) xác nhận `server.js` đọc `PORT` và `HOSTNAME`. `ReadWritePaths` quan trọng vì ISR ghi cache xuống đĩa bên trong `.next`. Thời gian dừng 30 giây theo đúng hướng dẫn self-host: gửi `SIGTERM` và chờ 10 đến 30 giây để các request đang chạy và callback `after()` hoàn tất.

Secret chỉ dùng lúc chạy để trong `/etc/nextapp.env`. Nhớ rằng biến `NEXT_PUBLIC_*` được nhúng lúc **build**, nên đổi trên server sẽ không có tác dụng cho tới khi bạn build lại.

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now nextapp
curl -I http://127.0.0.1:3000
```

### Bước 4: nginx với cache cho /_next/static

File trong `/_next/static` có mã hash trong tên, và Next.js đã gửi sẵn `Cache-Control: public, max-age=31536000, immutable` cho chúng. Hãy để nginx phục vụ thẳng từ đĩa để Node không phải xử lý các request này. Biến `$connection_upgrade` cho WebSocket là map trong [bài Node.js](/vi/blog/deploy-nodejs-vps):

```nginx
upstream nextapp {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    server_name example.com www.example.com;

    location /_next/static/ {
        alias /srv/nextapp/static/;
        add_header Cache-Control "public, max-age=31536000, immutable";
        access_log off;
    }

    location / {
        proxy_pass http://nextapp;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}
```

`/srv/nextapp/static/` là thư mục dùng chung, mỗi lần deploy chép thêm file `.next/static` mới vào (`cp -rn`) và giữ file cũ một thời gian. Cách này tránh một lỗi kinh điển khi self-host: người dùng mở trang trước lúc deploy, sau đó tải một chunk cũ không còn tồn tại. Tài liệu Next.js gọi đây là [version skew](https://nextjs.org/docs/app/guides/self-hosting#version-skew) và ghi rằng giữ lại các asset immutable cũ sẽ tránh được nó.

Để streaming và Suspense hoạt động, nginx không được buffer. Bạn thêm `proxy_buffering off;` vào khối `location /`, hoặc gửi header `X-Accel-Buffering: no` từ `headers()` trong `next.config.js` như hướng dẫn self-host.

### Bước 5: SSL bằng Certbot

```bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
```

Certbot lấy chứng chỉ [Let's Encrypt](https://letsencrypt.org/getting-started/) miễn phí, thêm khối server HTTPS và cài timer tự gia hạn. Nếu muốn có CDN cho người dùng ở xa server, xem bài [dùng Cloudflare với VPS](/vi/blog/cloudflare-with-vps); header immutable ở trên giúp Cloudflare cache `/_next/static` an toàn.

### Bước 6: Cập nhật không gián đoạn

`systemctl restart` làm rớt request vài giây trong lúc server mới khởi động. Chuyển kiểu blue/green tránh được điều đó:

1. Build vào thư mục mới, ví dụ `/srv/nextapp/releases/20261021-1200`, rồi chép file tĩnh vào thư mục `static` dùng chung.
2. Khởi động bản mới ở cổng đang rảnh bằng systemd template unit (`nextapp@3001.service`, dùng `Environment=PORT=%i`).
3. Chờ tới khi `curl -fsS http://127.0.0.1:3001/` trả lời.
4. Đổi dòng `server` trong khối `upstream` sang cổng 3001, rồi `nginx -t && systemctl reload nginx`.
5. Dừng instance cũ khi các request đang chạy đã xong (`systemctl stop nextapp@3000`).

Lưu ý các trang ISR sẽ tạo lại từ cache rỗng trong thư mục release mới, và hãy giữ release trước đó để rollback chỉ là lặp lại bước 2 đến 5 với nó. Bài [tự động deploy từ GitHub](/vi/blog/git-auto-deploy) hướng dẫn kích hoạt việc này mỗi lần push.

## Deploy Next.js bằng ZoPanel

ZoPanel chạy app như một service sau nginx, có SSL và chỉ chuyển release sau khi qua health check, bên trong một hosting account có giới hạn tài nguyên riêng:

1. Tạo website loại **Git deploy** với repository và nhánh; giữ **Free SSL (Let's Encrypt)** bật. ZoPanel nhận diện Next.js từ `package.json` và đề xuất script `start` của bạn, hoặc `npx next start`. Tối ưu ảnh chạy được vì `next start` tự dùng `sharp`.
2. ZoPanel đặt sẵn `PORT` và `HOST=127.0.0.1`, và `next start` tự đọc `PORT`. Đừng dùng `output: 'standalone'` chung với `next start`. Nếu muốn dùng standalone, tắt **Auto-detect** trong **Build & run**, nối hai lệnh `cp` vào build command bằng `&&`, và đặt start command là `node .next/standalone/server.js`. `server.js` của standalone đọc `HOSTNAME` chứ không phải `HOST`, nên thêm `HOSTNAME=127.0.0.1` trong **Environment variables**.
3. Mỗi lần deploy build vào một thư mục release mới, trong giới hạn CPU và RAM của gói. Bản mới chạy ở slot đang rảnh và phải trả lời **Health check path** với mã dưới 500 trong 90 giây thì nginx mới chuyển sang; nếu không, bản đang chạy vẫn giữ nguyên. **Rollback** giữ 5 bản build gần nhất.
4. Bật **Deploy when main is pushed** và thêm webhook vào GitHub, GitLab hoặc Gitea.

Cần biết: `NODE_ENV=production` chỉ được đặt lúc chạy nên `devDependencies` vẫn được cài để build; biến `NEXT_PUBLIC_*` thêm trong **Environment variables** có sẵn lúc build; và file app ghi bên trong thư mục release (kể cả cache ISR) sẽ bắt đầu lại từ đầu ở mỗi release, nên file upload phải để trong **Persistent paths**. Nếu build bị dừng, hãy tăng RAM của gói. **Use every CPU core** chạy một tiến trình cho mỗi CPU, tức là app trở thành nhiều instance: chỉ bật khi bạn đã xử lý cache dùng chung và state trong bộ nhớ như ở trên. ZoPanel không có preview deployment hay CDN toàn cầu. Chi tiết trong [Node.js apps](/vi/docs/nodejs) và [Git deploy](/vi/docs/git-deploy).

## Kết luận

Self-host Next.js trên một VPS là bài toán đã có lời giải: output standalone (hoặc `next start`), systemd, nginx phục vụ `/_next/static` với cache immutable, Certbot và chuyển release kiểu blue/green. Khi cần nhiều instance hoặc preview, đó là lúc Vercel xứng với giá tiền. Nếu muốn build, health check, rollback và SSL được làm sẵn, hãy thử [gói Free của ZoPanel](/vi/pricing) (3 hosting account, 10 website, 10 database, không giới hạn thời gian) trên một VPS thử nghiệm và deploy repository Next.js của bạn.

## Câu hỏi thường gặp

### Có dùng được gói Hobby miễn phí của Vercel cho website thương mại không?

Không. Trang giá của Vercel ghi gói Hobby dành "cho mục đích cá nhân, phi thương mại" với giới hạn sử dụng cố định. Dự án thương mại cần gói Pro, tại thời điểm viết là 20 USD mỗi developer seat mỗi tháng, hoặc tự host.

### next/image có chạy trên VPS không?

Có. Từ Next.js 15, `sharp` được dùng tự động với `next start` và output standalone, nên ảnh được resize ngay trên server của bạn. Trên Linux dùng glibc, tài liệu Next.js khuyên xem phần cấu hình memory allocator của sharp nếu tối ưu ảnh tốn quá nhiều RAM. Bạn cũng có thể đặt `unoptimized` và tự tối ưu ảnh.

### Build Next.js trên VPS cần bao nhiêu RAM?

Tùy kích thước dự án, nên hãy đo: chạy `next build --experimental-debug-memory-usage` hoặc theo dõi RAM trong lúc build. Trên VPS nhỏ, thêm swap hoặc build trong CI rồi deploy thư mục standalone. Server lúc chạy thường cần ít RAM hơn nhiều so với lúc build.

### Self-host Next.js có cần Docker không?

Không. `node server.js` chạy bằng systemd sau nginx đơn giản hơn trên một VPS. Docker hữu ích khi bạn muốn cùng một image trên nhiều server hoặc trong orchestrator, và [tài liệu deploy của Next.js](https://nextjs.org/docs/app/getting-started/deploying#docker) có hướng dẫn cho trường hợp đó.

### ISR có chạy khi self-host không?

Có. Với một instance, ISR và `revalidatePath`/`revalidateTag` dùng cache trên filesystem local mà không cần cấu hình. Với nhiều instance, bạn cần cache handler dùng chung, ví dụ Redis, để revalidate ở một instance có tác dụng trên các instance còn lại.
