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 和 内地访问速度都比较友好

评论栏