Chuyển app Next.js có Postgres khỏi Vercel: kiểm gì trước ngày đổi DNS?
Hoàng Xuân · Embra Engineering · Xuất bản 21/10/2026 · Đối chiếu tài liệu Next.js, Vercel và PostgreSQL cùng ngày.
Phần khó của việc rời Vercel thường không phải build app ở nơi khác. Phần khó là những việc Vercel đang làm thay bạn mà không ai ghi lại, và database phải chuyển sang mà không mất đơn hàng được ghi trong lúc chuyển. Bài này đi qua bốn bước: kiểm kê, build chạy ngoài Vercel, chuyển database có diễn tập, đổi DNS có đường quay lại.
Hướng dẫn dùng được khi bạn chuyển sang VPS, một PaaS khác hay tự host. Không cần tài khoản Embra.
Trong hướng dẫn này
1. Kiểm kê những gì Vercel đang làm thay bạn
Mở dashboard của project và ghi lại từng mục dưới đây. Mỗi dòng “có” là một việc nơi mới phải làm được, hoặc bạn phải tự làm.
| Ở Vercel | Cần kiểm | Nơi mới phải có |
|---|---|---|
| Environment Variables | Biến nào cho Production, Preview, Development; biến nào có tiền tố NEXT_PUBLIC_ | Cách lưu secret không lộ trong log; build lại khi đổi biến public |
| Integrations / Marketplace | Database, Redis, blob storage được cài qua Marketplace và biến môi trường chúng tự thêm | Từng dịch vụ tương ứng, hoặc giữ nguyên dịch vụ đó và chỉ chuyển app |
| Cron Jobs | Route nào được gọi theo lịch, bao lâu một lần | Bộ lập lịch gọi đúng route, có xác thực |
| Domains | Domain chính, domain phụ, redirect www, chứng chỉ | HTTPS và redirect tương đương |
| Image Optimization, cache, ISR | Trang nào dùng revalidate, ảnh nào qua next/image | Xem bước 2 |
| Preview Deployments | Team có dùng link preview cho mỗi pull request không | Môi trường preview, nếu vẫn cần |
Nếu database của bạn từng là Vercel Postgres, nó đã được chuyển sang Neon từ 12/2024. Chuỗi kết nối hiện trỏ tới Neon; bạn có thể giữ database ở đó và chỉ chuyển app, hoặc chuyển cả hai.
2. Build Next.js chạy được ngoài Vercel
Next.js chạy bằng next start trên một server Node.js có Image Optimization, Proxy và ISR hoạt động không cần cấu hình thêm. Để đóng gói gọn vào container, bật output: 'standalone':
// next.config.js
module.exports = {
output: 'standalone',
}
Build sẽ tạo .next/standalone kèm server.js. Thư mục này không tự chép public và .next/static; thiếu bước này, trang chạy nhưng CSS, JS và ảnh trả 404:
cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/
PORT=3000 HOSTNAME=0.0.0.0 node .next/standalone/server.js
Bốn điểm hay lỗi khi rời Vercel:
- Biến
NEXT_PUBLIC_được nhúng vào bundle lúc build. Đổi giá trị ở runtime không có tác dụng; phải build lại cho từng môi trường. Biến không có tiền tố chỉ đọc ở server; giá trị lúc chạy chỉ có tác dụng khi trang render động, còn trang prerender dùng giá trị lúc build. - Đặt reverse proxy phía trước. Tài liệu Next.js khuyến nghị dùng reverse proxy như nginx thay vì mở thẳng server ra internet. Nếu dùng nginx và có streaming, tắt buffering (header
X-Accel-Buffering: no), nếu không trang sẽ chờ render xong mới trả. - Image Optimization trên Linux glibc. Image Optimization có thể dùng nhiều bộ nhớ và cần cấu hình thêm cho sharp. Theo dõi RAM của container trong vài ngày đầu.
- Tắt server cho đúng. Gửi
SIGTERMvà chờ 10–30 giây để request đang chạy và callbackafter()hoàn tất.
Khi chạy nhiều instance
Cache và ISR mặc định nằm trên đĩa của từng instance. Một instance với đĩa bền vững thì chạy đúng ngay. Từ hai instance trở lên, hoặc khi đĩa không bền, cần thêm:
- Một
cacheHandlerdùng chung (ví dụ Redis), nếu không mỗi instance giữ bản cache riêng vàrevalidateTagchỉ xoá cache trên một instance. - Cùng một
NEXT_SERVER_ACTIONS_ENCRYPTION_KEYcho mọi instance, nếu không sẽ gặp lỗi “Failed to find Server Action”. - Một
deploymentIdđể khi deploy cuốn chiếu, trình duyệt đang mở bản cũ được tải lại sang bản mới thay vì gọi asset hoặc Server Action không còn tồn tại.
Nếu app nhỏ, một instance kèm restart tự động thường đơn giản hơn nhiều so với việc dựng đủ các phần trên.
3. Chuyển database có diễn tập
Với database cỡ vài GB, cách dễ kiểm nhất là pg_dump rồi pg_restore. pg_dump tạo bản xuất nhất quán và không chặn người đọc hay ghi, nhưng bản xuất chỉ chứa dữ liệu tới thời điểm bắt đầu dump. Đơn ghi sau đó không có trong bản này.
Vì vậy hãy làm hai lần: một lần diễn tập, một lần thật.
- Kiểm phiên bản. Server đích nên cùng hoặc mới hơn major version của nguồn. Dùng
pg_dumpcùng major với server đích; nó từ chối dump từ server mới hơn chính nó, và bản dump không chắc nạp được vào server cũ hơn. Khác major thì thêm--quote-all-identifiers. Ghi lại các extension đang dùng (\dx) và kiểm nơi mới có đủ. - Diễn tập. Dump database đang chạy, restore vào database trống ở nơi mới, trỏ một bản app thử vào đó. Đọc lại vài bản ghi cụ thể bạn biết trước (mã đơn, tổng tiền), không chỉ đếm số dòng. Ghi lại thời gian dump và restore.
- Chọn cửa sổ dừng ghi. Thời gian diễn tập cho biết app cần dừng ghi bao lâu. Báo trước cho người dùng; bật chế độ chỉ đọc hoặc trang bảo trì cho các route ghi.
- Chuyển thật. Dừng ghi, dump lần cuối, restore, chạy lại bước đối chiếu, rồi mới trỏ app mới vào database mới.
# Định dạng custom để pg_restore chọn lọc được
pg_dump --format=custom --dbname="$SOURCE_URL" --file=app.dump
# Bỏ owner và GRANT của role ở nơi cũ; dừng ngay khi có lỗi, áp trong một transaction
pg_restore --no-owner --no-privileges --exit-on-error --single-transaction \
--dbname="$TARGET_URL" app.dump
Mặc định pg_restore chạy tiếp khi gặp lỗi, nên một restore “xong” vẫn có thể thiếu bảng; --exit-on-error làm lỗi hiện ra ngay. Nếu nguồn là Neon, dùng chuỗi kết nối direct, không qua pooler. Đọc chuỗi kết nối từ biến môi trường, không dán mật khẩu vào lệnh để tránh lưu vào lịch sử shell. Nếu không chấp nhận được thời gian dừng ghi, bạn cần replication (ví dụ logical replication) thay vì dump; việc đó phức tạp hơn và nên diễn tập riêng.
Sau khi chuyển, giữ database cũ ở chế độ chỉ đọc vài ngày. Đó là đường quay lại nếu phát hiện thiếu dữ liệu, với điều kiện bạn chấp nhận bỏ các lần ghi đã xảy ra ở nơi mới.
4. Đổi DNS có đường quay lại
- Hạ TTL trước 24–48 giờ, ví dụ xuống 300 giây, để khi đổi bản ghi, phần lớn người dùng nhận địa chỉ mới nhanh.
- Chuẩn bị HTTPS ở nơi mới trước khi đổi. Nếu chứng chỉ chỉ cấp được sau khi DNS trỏ về, sẽ có vài phút người dùng gặp lỗi chứng chỉ. Có thể cấp trước bằng DNS-01 challenge; nếu domain đang dùng nameserver của Vercel, chuyển bản ghi hoặc nameserver và kiểm bản ghi CAA trước.
- Giữ deployment cũ trên Vercel cho tới khi nơi mới chạy ổn định vài ngày. Quay lại DNS chỉ an toàn khi database vẫn tương thích với code cũ.
- Theo dõi sau khi đổi: tỉ lệ lỗi, thời gian phản hồi của route có truy vấn, cron có chạy đúng giờ, ảnh có tải được.
Ghi kết quả trước ngày chuyển
Bảng dưới là mẫu để tự điền, không phải kết quả của Embra. “Chưa chạy” khác với “đạt”.
| Phép kiểm | Bằng chứng cần lưu | Kết quả của bạn |
|---|---|---|
| Kiểm kê | Danh sách biến môi trường, integration, cron, domain đã đối chiếu với nơi mới | Chưa chạy |
| Build ngoài Vercel | Trang tải đủ CSS, JS, ảnh; biến public đúng môi trường | Chưa chạy |
| Diễn tập database | Bản ghi mẫu khớp từng trường; thời gian dump + restore | Chưa chạy |
| Cửa sổ dừng ghi | Thời lượng dự kiến, đã báo người dùng | Chưa chạy |
| HTTPS nơi mới | Chứng chỉ hợp lệ cho mọi domain trước khi đổi DNS | Chưa chạy |
| Đường quay lại | Deployment cũ còn chạy; database cũ chỉ đọc; người quyết định quay lại | Chưa chạy |
Thời gian restore của database thử nhỏ không dự báo đúng cho database lớn hơn; đo lại với dữ liệu cỡ thật nếu được.
Đang cân nhắc nơi chạy mới cho app có người dùng tại Việt Nam? Bảng so sánh Vercel, Railway, Mắt Bão Vibe Host và VPS đặt các lựa chọn cạnh nhau theo vị trí server, cách trả tiền và trách nhiệm database. Embra đang xây hosting app và PostgreSQL tập trung vào migration và restore, hiện ở closed alpha; bài này là hướng dẫn chung, không phải danh sách tính năng đã phát hành.