Next.js 项目部署

    要部署一个 Next.js 项目,你可以选择多种托管平台。

    这里列几种方案:

    1. 部署到 Linux 物理机
    2. 部署到 docker
    3. 部署到 Kubernetes
    4. 部署到 Vercel

    1. 部署到 Linux 物理机

    这种部署方式非常传统. 在单体项目中比较常见, 简单高效. 有一些几个步骤

    1. 安装 Node.js

      首先,确保你的服务器已安装 Node.js。Next.js 需要 Node.js 运行环境。你可以在官方网站上找到适合你操作系统的安装说明:https://nodejs.org/en/download/

    2. 上传项目

      使用 SCP、SFTP 或者 Git 将 Next.js 项目上传到服务器的一个目录中。确保包含了所有的源代码和依赖文件(package.jsonyarn.lockpackage-lock.json)。

    3. 安装依赖

      yarn
      
    4. 构建项目

      yarn build
      
    5. 启动项目

      使用以下命令启动项目:

      yarn start
      

      默认情况下,Next.js 会在端口 3000 上运行。如果需要修改端口,请在启动命令后面添加 --port 参数,例如:

      yarn start --port 8080
      
    6. 设置反向代理(可选)

      如果你已经在服务器上运行了一个 Web 服务器(如 Nginx 或 Apache),你可能需要设置一个反向代理,将用户的请求从 Web 服务器代理到 Next.js 应用上。以下是一个 Nginx 示例配置:

      server {
          listen 80;
          server_name yourdomain.com;
      
          location / {
              proxy_pass http://localhost:3000;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection 'upgrade';
              proxy_set_header Host $host;
              proxy_cache_bypass $http_upgrade;
          }
      }
      

    yourdomain.com 替换为你的实际域名,并根据需要修改其他配置。

    现在,你的 Next.js 项目应该已经在你的服务器上运行了。

    你也可以通过 systemd 将 nextjs 程序管理起来. 方便系统重启时自动启动, 或者进程异常终止时自动重启

    /etc/systemd/system 目录下新建 nextjs.service 文件. 内容模板如下, 根据情况进行调整

    [Unit]
    Description=nextjs
    Documentation=nextjs
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    WorkingDirectory=/workspace/  # 工作目录
    Environment="PORT=8080"
    EnvironmentFile=-/etc/default/%p
    ExecStart=yarn start --port 8080
    ExecStop=/bin/kill -HUP $MAINPID
    Restart=on-failure
    StandardOutput=append:/var/log/app/nextjs.log
    StandardError=append:/var/log/app/nextjs-error.log
    
    KillSignal=SIGINT
    
    [Install]
    WantedBy=multi-user.target
    

    执行下属命令

    # 使 systemd 配置生效
    systemctl daemon-reload
    
    # 启动应用
    systemctl start nextjs.service
    
    # 开机启动
    systemctl enable nextjs.service
    

    2. 部署到 docker

    请确保你已经安装 docker. 如果没有, 请先配置 docker.

    官方 dockerfile 如下:

    一般来说无需调整即可正常使用

    FROM node:18-alpine AS base
    
    # Install dependencies only when needed
    FROM base AS deps
    # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
    RUN apk add --no-cache libc6-compat
    WORKDIR /app
    
    # Install dependencies based on the preferred package manager
    COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
    RUN \
      if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
      elif [ -f package-lock.json ]; then npm ci; \
      elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
      else echo "Lockfile not found." && exit 1; \
      fi
    
    
    # Rebuild the source code only when needed
    FROM base AS builder
    WORKDIR /app
    COPY --from=deps /app/node_modules ./node_modules
    COPY . .
    
    # Next.js collects completely anonymous telemetry data about general usage.
    # Learn more here: https://nextjs.org/telemetry
    # Uncomment the following line in case you want to disable telemetry during the build.
    # ENV NEXT_TELEMETRY_DISABLED 1
    
    RUN yarn build
    
    # If using npm comment out above and use below instead
    # RUN npm run build
    
    # Production image, copy all the files and run next
    FROM base AS runner
    WORKDIR /app
    
    ENV NODE_ENV production
    # Uncomment the following line in case you want to disable telemetry during runtime.
    # ENV NEXT_TELEMETRY_DISABLED 1
    
    RUN addgroup --system --gid 1001 nodejs
    RUN adduser --system --uid 1001 nextjs
    
    COPY --from=builder /app/public ./public
    
    # Set the correct permission for prerender cache
    RUN mkdir .next
    RUN chown nextjs:nodejs .next
    
    # Automatically leverage output traces to reduce image size
    # https://nextjs.org/docs/advanced-features/output-file-tracing
    COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
    COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
    
    USER nextjs
    
    EXPOSE 3000
    
    ENV PORT 3000
    # set hostname to localhost
    ENV HOSTNAME "0.0.0.0"
    
    # server.js is created by next build from the standalone output
    # https://nextjs.org/docs/pages/api-reference/next-config-js/output
    CMD ["node", "server.js"]
    

    打包. 并推送到远程仓库

    # 在代码工作目录(与 package.json 同级)下执行
    
    # 打包
    docker build -t jansora/nextjs:v1
    
    # 推送到 docker hub (需要登录)
    docker push jansora/nextjs:v1 
    
    # 推送到 aliyun hongkong 仓库 (需要登录)
    # 先打标签
    docker tag jansora/nextjs:v1  registry.cn-hongkong.aliyuncs.com/jansora/nextjs:v1
    # 在推送
    docker push registry.cn-hongkong.aliyuncs.com/jansora/nextjs:v1
    

    部署

    docker run -d  -p 3000:3000 registry.cn-hongkong.aliyuncs.com/jansora/nextjs:v1
    

    如果需要配置反向代理, 与部署到linux比较类似, 不再赘述.

    3. 部署到 Kubernetes

    打包镜像步骤与部署到 docker 比较类似, 不再赘述

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nextjs
    
    spec:
      selector:
        matchLabels:
          app: nextjs
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 20
          maxUnavailable: 20
      replicas: 1 # pod 副本个数
      template:
        metadata:
          labels:
            app: nextjs
        spec:
          containers:
            - name: nextjs
              image: registry.cn-hongkong.aliyuncs.com/jansora/nextjs:v1
              imagePullPolicy: Always
              ports:
                - containerPort: 3000
              env:
                - name: xxx
                  value: 'xxx'
    
    
    # service 负载均衡
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: nextjs-service
    spec:
      type: LoadBalancer # 如果有负载均衡就选负载均衡, 如果没有, 就选 ClusterIP
      externalIPs:
        - 192.168.36.100
      selector:
        app: nextjs
      ports:
        - protocol: TCP
          port: 3000
          targetPort: 3000
          name: http
    
    
    # 部署 ingress 
    ---
    
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: nextjs-service-ingress
    
    spec:
      defaultBackend:
        service:
          name: nextjs-service
          port:
            number: 3000
      ingressClassName: nginx
      rules:
        - host: nextjs.kubernetes.jansora.com
          http:
            paths:
              - backend:
                  service:
                    name: nextjs-service
                    port:
                      number: 3000
                pathType: Prefix
                path: /
    
    
    

    4. 部署到 Vercel

    https://vercel.com/ 使用 Github 登录

    新建项目, 绑定 repository 和 branch

    框架选择 next 即可

    image.png

    部署完成后, 默认会给个 domain, 也可以选择自定义域名

    image.png

    附录

    通过 github action 来管理全流程

    在项目根目录下新建 action 文件 .github/workflows/deploy.yaml

    name: Build and Publish Docker Image
    
    on:
      push:
        branches: # 分支匹配时执行 action
          - '2.0' 
          - master
    
    jobs:
      build-and-publish:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v2
            with:
              fetch-depth: 2  # fetch head and merge-base
    
    
          - name: Analysis And Extract Commit Message   # 在 commit 中提取 ci 和版本信息,  commit message 中包含 ci 时才执行剩余 ci流程
            run: |
              COMMIT_MESSAGE=$(git log -1 --pretty=%B) # 获取最后一次提交的 commit message
              VERSION=$(echo "${COMMIT_MESSAGE}" | grep -o -E "version@[0-9]+\.[0-9]+\.[0-9]+" | sed 's/version@//')
              if [[ $(git log -1 --pretty=%B) == *"ci"* ]]; then
                DO_CI=true
              else
                DO_CI=false
              fi
              echo "BRANCH_NAME=$(basename ${GITHUB_REF})" >> $GITHUB_ENV  
              echo "VERSION=${VERSION}" >> $GITHUB_ENV
              echo "DO_CI=${DO_CI}" >> $GITHUB_ENV
    
    
          - name: Build Docker image
            run: |
              if [ "${DO_CI}" = "true" ]; then
                docker build -t jansora/nextjs:${VERSION} .
              else
                echo "skipped."
              fi
    
          - name: Push to Aliyun HK Docker Registry # 推送镜像
            run: |
              if [ "${DO_CI}" = "true" ]; then
                echo "${{ secrets.DEPLOYMENT_HOST_PASSWORD }}" | docker login --username=xxx registry.cn-hongkong.aliyuncs.com --password-stdin
                docker tag jansora/nextjs:${VERSION} registry.cn-hongkong.aliyuncs.com/jansora/nextjs:${VERSION}
                docker push registry.cn-hongkong.aliyuncs.com/jansora/nextjs:${VERSION}
              else
               echo "skipped."
              fi
    
    
          - name: Deploy to Kubernetes by SSH   #   通过代理服务器登录 k8s 集群 controller-pane 所在节点, 自动更新镜像 kubectl set image deployment/nextjs nextjs=registry.cn-hongkong.aliyuncs.com/jansora/nextjs:${VERSION}
         
            run: |
              if [ "${DO_CI}" = "true" ]; then
                sudo apt install proxytunnel sshpass -y
                sshpass -p '${{ secrets.DEPLOYMENT_HOST_PASSWORD }}' ssh -o 'ProxyCommand=proxytunnel -p proxy.jansora.com:3128 -P name:password -d %h:%p' \
                root@master.kubernetes.jansora.com -o StrictHostKeyChecking=no \
                "export KUBECONFIG=/etc/kubernetes/admin.conf && /usr/bin/kubectl set image deployment/nextjs nextjs=registry.cn-hongkong.aliyuncs.com/jansora/nextjs:${VERSION} "
              else
                echo "skipped."
              fi
    
    

    其他

    推荐 aliyun 香港镜像地址, github 和 内地访问速度都比较友好

    评论栏