---
title: "從 GitHub 到上線：部署 Next.js、PostgreSQL 與自訂網域"
description: "在 Zenavo 上把 Next.js 應用程式與 PostgreSQL 部署到自己的伺服器，設定內網連線、建置期與執行期環境變數、Prisma 資料庫遷移、HTTPS 網域與自動部署。"
date: 2026-11-10
language: zh-TW
url: https://zenavo.app/zh-TW/blog/deploy-nextjs-postgresql
---

# 從 GitHub 到上線：部署 Next.js、PostgreSQL 與自訂網域

把本機寫好的 Next.js 應用程式部署上線，通常要依序搞定五件事：啟動指令碼、資料庫連線、環境變數（分清建置期與執行期）、資料表遷移，以及綁定 HTTPS 網域。

本文以一個既有的 Next.js 儲存庫為例，把它和 PostgreSQL 一起跑在你自己的伺服器上。開始前，先[連接一台 Linux 伺服器](https://docs.zenavo.app/zh-TW/servers/add-a-server)到 Zenavo。

## 1. 檢查儲存庫的 `package.json`

如果儲存庫沒有自訂 `Dockerfile`，Zenavo 會用 zbpack 自動識別 Next.js 並建置。確認儲存庫根目錄有鎖定檔（`package-lock.json`、`pnpm-lock.yaml`、`yarn.lock` 或 `bun.lockb`），且 `package.json` 包含標準的正式環境指令碼：

```json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "engines": {
    "node": ">=20"
  }
}
```

上線前先在本機跑一次 `npm run build`，把 TypeScript 型別錯誤或建置期錯誤提前修掉。

## 2. 部署 PostgreSQL

在 Zenavo 建立專案，選擇伺服器，然後點擊 **新增服務 → 資料庫 → PostgreSQL**，選擇版本並部署。

資料庫跑在專案私有網路裡，預設不向公網暴露連接埠。服務就緒後，它會向同專案的其他服務共享連線變數；服務名稱叫 `postgresql` 時，完整連線字串變數名為 `POSTGRESQL_CONNECTION_STRING`。

## 3. 從 GitHub 新增 Next.js 應用程式

在同一個專案裡點擊 **新增服務 → GitHub**，授權 Zenavo GitHub App 讀取你的儲存庫，然後選擇儲存庫、分支與根目錄（如果是 monorepo，例如放在 `apps/web`，就把根目錄填成 `apps/web`）。

在環境變數裡引用剛才的資料庫：

```dotenv
DATABASE_URL=${POSTGRESQL_CONNECTION_STRING}
```

`${POSTGRESQL_CONNECTION_STRING}` 是 Zenavo 的跨服務變數引用，執行時會自動展開成真實的內網連線字串（如果你改了資料庫服務名稱，請以介面上實際顯示的變數名稱為準）。

預設的 `next start` 會自動讀取平台傳入的 `PORT` 環境變數。如果你自己寫了 `Dockerfile` 或自訂 `server.js`，請確保監聽 `0.0.0.0` 且連接埠與 Zenavo 服務裡設定的 HTTP 連接埠一致。更多設定見 [Next.js 部署文件](https://docs.zenavo.app/zh-TW/frameworks/nextjs)。

## 4. 分清 `NEXT_PUBLIC_*` 與伺服器端變數

Next.js 有兩類環境變數，行為差別很大：

- **伺服器端變數**（如 `DATABASE_URL`、金流密鑰、AI API Key）：只在伺服器端程式碼中讀取，絕不要加上 `NEXT_PUBLIC_` 前綴。在 Zenavo 的 **變數** 分頁修改後，重新啟動服務就會生效。
- **`NEXT_PUBLIC_*` 變數**：在執行 `next build` 時會被直接內嵌進前端 JS 套件裡。**修改 `NEXT_PUBLIC_*` 後必須重新建置部署**，只重新啟動容器不會改變瀏覽器拿到的值。

另外，**Zenavo 的建置機在建置階段連不上你伺服器內網裡的資料庫**。因此：

- 不要把 `next build` 寫成需要先連正式資料庫才能通過的指令碼；
- 需要讀資料庫的頁面應使用動態轉譯（不在建置期預先轉譯靜態頁面），或在資料庫無法連線時跳過靜態預取。

## 5. 自動執行資料庫遷移（以 Prisma 為例）

因為建置期連不上內網資料庫，資料表遷移應該放在**容器啟動前**，而不是 `build` 指令碼裡。

使用 zbpack 建置時，可以在 `package.json` 裡加一個 `predeploy` 指令碼，它會在每次容器啟動、`start` 執行之前執行：

```json
{
  "scripts": {
    "build": "prisma generate && next build",
    "predeploy": "prisma migrate deploy",
    "start": "next start"
  }
}
```

注意兩點：

- 正式環境請用 `prisma migrate deploy`（只套用已提交到儲存庫的遷移檔），不要用開發環境的 `prisma migrate dev` 或 `db push --force-reset`。
- 如果你使用自訂 `Dockerfile`，zbpack 的 `predeploy` 掛鉤不會自動介入，把 `npx prisma migrate deploy && npm start` 寫進你的容器啟動指令（`CMD` 或 `ENTRYPOINT`）即可。

## 6. 綁定網域與自動部署

應用程式啟動後，在服務的 **網路** 頁綁定你的網域（如 `app.example.com`），把 DNS 的 A 記錄指向伺服器 IP，等待 HTTPS 憑證自動簽發（詳見[網域文件](https://docs.zenavo.app/zh-TW/networking/domains)）。如果你的應用程式用了 NextAuth / Auth.js、Clerk 或 OAuth 登入，別忘了同步更新 `NEXTAUTH_URL` 等站點網址變數以及第三方後台的回呼網址。

開啟頁面，走一遍登入與一筆資料的新增、讀取、修改、刪除，確認讀寫都進了新的 PostgreSQL。

在服務 **設定** 中開啟 **推送到這個分支時自動部署**，之後每次 `git push` 都會觸發建置並滾動更新；如果是 monorepo，可以設定監聽路徑，只在該目錄有改動時才觸發建置（詳見 [GitHub 部署文件](https://docs.zenavo.app/zh-TW/deploy/github)）。

## 常見問題

**建置日誌報錯 `Can't reach database server at postgresql.zenavo.internal:5432`？**
你的 `build` 指令碼或某個頁面在靜態產生（SSG）階段嘗試連接內網資料庫。把資料庫遷移移到 `predeploy` 或啟動指令裡，並給需要查資料庫的頁面加上 `export const dynamic = 'force-dynamic'`。

**修改了 `NEXT_PUBLIC_API_URL` 並重新啟動，網頁裡還是舊網址？**
`NEXT_PUBLIC_*` 是在建置時打包進前端程式碼的。點擊 **重新部署（Redeploy）** 重新跑一次建置即可。

**部署新版本後發現有問題，怎麼回退？**
在部署歷史裡可以把應用程式一鍵回滾到上一個映像檔版本。注意：**服務回滾只還原應用程式映像檔與設定，不會回滾資料庫**。如果新版本包含刪欄位等破壞性資料庫遷移，執行前請先在 PostgreSQL 服務的 **備份與還原** 裡建立一個資料庫備份。

準備好儲存庫後，在 [Zenavo 控制台](https://zenavo.app)新增專案：先起 PostgreSQL，再連 GitHub 儲存庫、設好 `DATABASE_URL` 與網域，幾分鐘就能跑通從 `git push` 到上線的完整流程。
