NextJS專案的部署以及多環境的實現

2023-07-02 06:00:59

背景

開發了個Next專案,將部署過程記錄一下。另外由於專案准備了兩個伺服器分別作為開發自測的開發環境和交付給客戶的生產環境使用;因此也介紹一下NextJS專案中多環境的設定。

專案結構

計劃是讓Nginx根據不同的路徑字首決定請求發給哪個後端;而路徑字首則是由Docker打包映象的時候傳遞引數給Next App作為環境變數。

部署過程

設定next.config.js

匯出靜態檔案

我們需要Next專案編譯後的檔案,這需要我們把next.config.js中加上output: "export"設定,這樣我們在執行next build命令後,Next會生成一個靜態資原始檔夾out ,如圖:

⚠️注意事項

output: "export" 模式下無法使用rewrites 或者headers 等設定,官方檔案列出的完整不支援的功能如下:

設定環境變數

如上文提到,Nginx需要根據不同的路徑字首來決定請求哪個後端,那麼就需要前端去判斷當前是什麼環境再設定當前的請求的路徑字首。

  1. 根據NODE_ENV判斷當前環境【已失敗】

    計劃是當NODE_ENVproduction的時候,請求字首為/prod;當NODE_ENVdevelopment的時候則為/dev。實施的時候卻發現next buildnext start這兩條命令都會預設設定NODE_ENVproduction 。因此當我使用cross-env在執行命令時設定NODE_ENVdevelopment就失敗了(如圖)。

    這麼設定,執行npm run dev後獲取到的process.env.NODE_ENV還是production

    後來看到有網友說可以通過webpack的DefinePlugin外掛來建立全域性變數,從而改變環境。參考連結:使用process.env.NODE_ENV的正確姿勢

  2. 自定義環境變數

    發現NODE_ENV會被next buildnext start這兩條命令修改的時候,我就決定使用別的環境變數來區別本專案的開發環境與生產環境。

    注意,在這裡設定的環境變數API是無法在業務程式碼中直接存取的。如果此時在業務程式碼中使用process.env.API會得到undefined的值。
    因此我在next.config.js處新增了環境變數的設定,將scripts這裡設定的API變數傳遞給Next專案裡,如圖:

    使用方式如下:

這樣就成功設定好了環境變數,讓專案根據不同環境,請求帶上不同的字首。

next.config.js檔案設定範例:

const nextConfig = {
  output: "export", // 打包模式
  reactStrictMode: true,
	images: {
     unoptimized: true,
  },
	env: {
    API_PREFIX: process.env.API,
  },
  // async rewrites() {
  //   return [
  //     {
  //       source: "/api/:path*",
  //       destination: "http://domain:8000/:path*",
  //     },
  //   ];
  // }, // 本地偵錯時使用
};

module.exports = nextConfig;

設定nginx.conf

Nginx的設定沒啥特別的,就是根據不同字首把請求轉發到不同伺服器上,下面是我用的設定:

server {
    listen       80;
    server_name  localhost;
    gzip         on;

    access_log  /var/log/nginx/host.access.log  main;
    error_log  /var/log/nginx/error.log  error;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri.html /$uri /index.html;
    }

    location /dev/ {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $http_host;
        proxy_pass http://domain1:8000/;  # 開發環境
    }

    location /prod/ {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $http_host;
        proxy_pass http://domain2:8000/;   # 生產環境
    }

    # error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

其中要特別注意try_files $uri $uri.html /$uri /index.html; 這條設定。其他前端專案大部分設定的都是try_files $uri $uri/ /index.html; ,但是Next專案比較特殊,觀察它打包後的檔案可以發現規律,這裡不贅述。

設定Dockerfile

這裡用的 Dockerfile也沒啥特別的,就是把Next專案編譯好的靜態檔案複製到/usr/share/nginx/html,讓Nginx進行靜態代理,最後啟動Nginx。其中我用了ENV引數來區分前端的兩個環境,ENV可以有兩個值:devprod

使用—build-arg就能傳遞引數,範例:

docker build --build-arg ENV=dev -t domain/frontend:test-v0.1 .

完整的Dockerfile設定如下:

FROM node:16-alpine as build
ARG ENV
RUN npm config set registry https://registry.npm.taobao.org \
    && npm i npm -g

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run ${ENV}

FROM nginx:alpine
COPY --from=build /app/out /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
ENTRYPOINT nginx -g "daemon off;"

部署成功

所有部署檔案都已經完成了,得到docker映象後,不管是部署到K8s的叢集上面還是自己啟動一個docker容器都很簡單,在這裡就忽略了。最後的部署效果完美地實現了我的目的:開發環境的請求字首都有/dev,也成功請求到了開發環境的那臺後端伺服器。