EMBRA

Engineering / Hướng dẫn thực hành

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.

Ở VercelCần kiểmNơi mới phải có
Environment VariablesBiế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 / MarketplaceDatabase, Redis, blob storage được cài qua Marketplace và biến môi trường chúng tự thêmTừng dịch vụ tương ứng, hoặc giữ nguyên dịch vụ đó và chỉ chuyển app
Cron JobsRoute nào được gọi theo lịch, bao lâu một lầnBộ lập lịch gọi đúng route, có xác thực
DomainsDomain chính, domain phụ, redirect www, chứng chỉHTTPS và redirect tương đương
Image Optimization, cache, ISRTrang nào dùng revalidate, ảnh nào qua next/imageXem bước 2
Preview DeploymentsTeam có dùng link preview cho mỗi pull request khôngMô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:

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:

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.

  1. 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_dump cù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ó đủ.
  2. 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.
  3. 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.
  4. 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

  1. 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.
  2. 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.
  3. 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ũ.
  4. 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ểmBằng chứng cần lưuKế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ớiChưa chạy
Build ngoài VercelTrang tải đủ CSS, JS, ảnh; biến public đúng môi trườngChưa chạy
Diễn tập databaseBản ghi mẫu khớp từng trường; thời gian dump + restoreChưa chạy
Cửa sổ dừng ghiThời lượng dự kiến, đã báo người dùngChưa chạy
HTTPS nơi mớiChứng chỉ hợp lệ cho mọi domain trước khi đổi DNSChưa chạy
Đường quay lạiDeployment cũ còn chạy; database cũ chỉ đọc; người quyết định quay lạiChư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.