開發了個Next專案,將部署過程記錄一下。另外由於專案准備了兩個伺服器分別作為開發自測的開發環境和交付給客戶的生產環境使用;因此也介紹一下NextJS專案中多環境的設定。
計劃是讓Nginx根據不同的路徑字首決定請求發給哪個後端;而路徑字首則是由Docker打包映象的時候傳遞引數給Next App作為環境變數。
next.config.js
我們需要Next專案編譯後的檔案,這需要我們把next.config.js
中加上output: "export"
設定,這樣我們在執行next build
命令後,Next會生成一個靜態資原始檔夾out
,如圖:
⚠️注意事項
在output: "export"
模式下無法使用rewrites
或者headers
等設定,官方檔案列出的完整不支援的功能如下:
如上文提到,Nginx需要根據不同的路徑字首來決定請求哪個後端,那麼就需要前端去判斷當前是什麼環境再設定當前的請求的路徑字首。
根據NODE_ENV判斷當前環境【已失敗】
計劃是當NODE_ENV
為production
的時候,請求字首為/prod
;當NODE_ENV
為development
的時候則為/dev。實施的時候卻發現next build
和next start
這兩條命令都會預設設定NODE_ENV
為production
。因此當我使用cross-env在執行命令時設定NODE_ENV
為development
就失敗了(如圖)。
這麼設定,執行npm run dev
後獲取到的process.env.NODE_ENV
還是production
。
後來看到有網友說可以通過webpack的DefinePlugin外掛來建立全域性變數,從而改變環境。參考連結:使用process.env.NODE_ENV的正確姿勢
自定義環境變數
發現NODE_ENV會被next build
和next 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
可以有兩個值:dev
和prod
。
使用—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,也成功請求到了開發環境的那臺後端伺服器。