NextJS项目的部署以及多环境的实现

发布时间 2023-07-01 22:28:30作者: 绛绛

背景

开发了个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,也成功请求到了开发环境的那台后端服务器。