nextjs项目部署流程

开发环境和生产环境nextjs的产物区别

Next.js 中"静态资源"的几种类型:

类型 放置位置 访问路径 示例
公开资源(public) /public /xxx /public/logo.png → /logo.png
构建产物(_next/static) 自动生成,放在内存中 /_next/static/... 构建时生成的 JS、CSS、图片等
静态导出的页面 SSG 页面输出为 HTML 文件 自定义路径或嵌入构建目录 /about → about.html(仅限 export 模式)

开发环境

  1. 内部启动一个 Node.js + Webpack(或 Turbopack) 的服务
  2. 所有静态资源都是内存中实时生成,不是写在磁盘上的
  3. 公开资源 public/ 文件照常访问,无需打包
  4. 所有 JS/CSS/图片资源由 /_next/static/ 路径动态提供

特点:

特性 是否持久存储 说明
public/ 静态资源 ✅ 有磁盘文件 本地文件系统直接读取
/_next/static/ 资源 ❌ 无磁盘文件 临时缓存于内存中,实时构建

生产环境

路径 说明
.next/static/chunks/ 各页面按需加载的 JS chunk
.next/static/media/ 使用 import 的图片、字体等静态文件
public/ 所有手动放的公开资源,不会变,直接拷贝

构建完成后所有这些资源可以通过 /_next/static/xxx 路径访问,Next.js 会自己提供路由处理。

部署后的静态资源加载流程:

  1. 用户访问页面(如 /about
  2. 服务端返回 SSR 或 SSG 渲染页面
  3. 页面中会加载 /_next/static/... 的 JS 和 CSS
  4. 浏览器请求这些资源,Next.js 服务会直接返回 .next/static 下的文件内容
  5. 所有 /public 下资源,继续通过 /logo.png/favicon.ico 等路径访问

项目 Logo、图标等公开资源可以放在public下,这样就能直接通过/logo.png/favicon.ico这种方式访问

为什么有的时候,执行nextjs build后,再run dev会报错,删除.next文件就正常了?

开发环境下,虽然大部分资源是内存中动态生成,但 路由信息,构建缓存,以及某些临时文件等部分缓存和中间产物依然会写入 .next 文件夹,如果 .next 目录被生产环境的构建产物污染,开发服务器可能会读取到不兼容的内容,导致报错。

部署方案一:前后端分离

使用nodejs部署

本地打包方案

在本地执行npm run build后,nextjs会生成一个.next文件夹,存放打包好的文件。nextjs包含了所有预编译的页面、服务端渲染模块等。但运行生产服务(npm startnext start)时,它仍然需要以下东西:

lua 复制代码
.next/                   构建产物
public/                  静态资源
package.json             启动和依赖声明
package-lock.json        锁定依赖
scripts/deploy.sh        脚本文件(可选但推荐)
.env.production          环境变量
next.config.js           配置文件(如你自定义了)

我们可以执行以下命令将所有需要的东西打成一个.tar.gz的压缩包

lua 复制代码
tar czf next-app.tar.gz \
  .next \
  public \
  package.json \
  package-lock.json \
  next.config.js \
  .env.production \
  scripts

注意:如果你不上传 node_modules/,服务器上要执行一次 npm install!!!!!

接下来我们就可以写一个部署脚本了。

在项目中添加scripts文件夹,结构如下:

lua 复制代码
your-nextjs-project/
├── scripts/               ← 存放所有脚本的目录(推荐)
│   └── deploy.sh          ← 这是部署脚本
├── package.json
├── .next/
├── app/ 或 pages/
└── ...

脚本内容:

bash 复制代码
#!/bin/bash
​
# 变量
APP_NAME="next-app"
# /var/www/ 是系统目录,普通用户没有写权限,需要确保用户有权限,否则可以改为用户拥有权限的位置(如 /home/ubuntu/next-app)
DEPLOY_DIR="/var/www/$APP_NAME"
#非必须,可以通过DEPLOY_USER获取当前执行人,但一般都使用root用户
DEPLOY_USER=$(whoami) 
​
echo "🚀 正在部署 Next.js 应用到 $DEPLOY_DIR"
echo "👤 当前用户: $DEPLOY_USER"
​
# 创建部署目录,如果有该目录也不会被覆盖
mkdir -p "$DEPLOY_DIR"
​
#不推荐 rm -rf "$DEPLOY_DIR" 整体删除,目录权限可能丢失
echo "🧹 清理旧构建文件..."
rm -rf "$DEPLOY_DIR/.next"
rm -rf "$DEPLOY_DIR/public"
rm -f "$DEPLOY_DIR/package.json"
rm -f "$DEPLOY_DIR/package-lock.json"
rm -f "$DEPLOY_DIR/next.config.js"
rm -f "$DEPLOY_DIR/.env.production"
​
# 解压上传的包
echo "📦 解压上传文件..."
# 会覆盖同名文件
tar -xzf next-app.tar.gz -C "$DEPLOY_DIR" --strip-components=1
​
cd "$DEPLOY_DIR"
​
# 安装依赖
echo "📦 安装依赖..."
npm install --omit=dev
​
# 启动应用(推荐使用 pm2)
if command -v pm2 &> /dev/null; then
  if pm2 list | grep -q "$APP_NAME"; then
    echo "🔁 正在重启 $APP_NAME..."
    pm2 restart "$APP_NAME"
  else
    echo "🚀 正在启动 $APP_NAME..."
    pm2 start npm --name "$APP_NAME" -- start
  fi
  pm2 save
else
  echo "⚠️ 未安装 pm2,将直接使用 npm 启动(需保持终端打开)"
  npm start
fi
​
echo "✅ 部署完成!你现在可以访问你的服务了。"
​

服务器部署流程

具体操作步骤见我的服务器上传登录流程

ruby 复制代码
# 上传压缩包
scp next-app.tar.gz youruser@yourserver:/var/www/
​
# 登录服务器
ssh youruser@yourserver
​
# 解压和执行部署脚本
cd /var/www/
tar xzf next-app.tar.gz
​
# 有脚本的情况, 执行脚本
cd next-app-dist/scripts/
# 首次给执行权限(只需一次)
chmod +x deploy.sh
./deploy.sh
​
# 没有脚本的情况,手动执行,和有脚本的情况保留一个既可
# 安装依赖(如果没有上传 node_modules)
npm install
# 启动服务(推荐使用 pm2)
npm start         # 或 pm2 start npm --name next-app -- start
​

git拉取代码部署(后续再完善)

更改脚本:

bash 复制代码
#!/bin/bash
​
# 项目配置
REPO_URL="git@github.com:user/next-app.git"
BRANCH="main"
APP_NAME="next-app"
DEPLOY_DIR="/var/www/$APP_NAME"
​
echo "🚀 开始部署 $APP_NAME"
​
# 如果目录不存在,克隆;否则拉取最新
if [ ! -d "$DEPLOY_DIR" ]; then
  echo "📥 克隆仓库到 $DEPLOY_DIR"
  git clone -b $BRANCH $REPO_URL "$DEPLOY_DIR"
else
  echo "🔄 拉取最新代码"
  cd "$DEPLOY_DIR"
  git fetch origin $BRANCH
  git reset --hard origin/$BRANCH
fi
​
cd "$DEPLOY_DIR"
​
# 安装依赖(只安装生产依赖)
echo "📦 安装依赖..."
npm install --omit=dev
​
# 构建项目
echo "🏗️ 构建项目..."
npm run build
​
# 启动或重启服务
if command -v pm2 &> /dev/null; then
  if pm2 list | grep -q "$APP_NAME"; then
    echo "🔁 重启服务..."
    pm2 restart "$APP_NAME"
  else
    echo "🚀 启动服务..."
    pm2 start npm --name "$APP_NAME" -- start
  fi
  pm2 save
else
  echo "⚠️ 未安装 pm2,将直接启动(非守护)"
  npm start
fi
​
echo "✅ 部署完成!"
​

部署方案二 前端资源嵌入到后端的 Java 应用中

很多 Java 后端项目(如使用 Spring Boot)中常见的一种部署方式。它的核心思想是:将前端静态资源(如 Next.js 构建后的 HTML、JS、CSS 等)作为资源嵌入到后端的 Java 应用中 ,然后通过后端来统一对外提供服务,打成一个可执行的 .jar 包,直接运行即可部署。

这个过程可以分为三步:

  1. 前端构建(Next.js)

    你首先运行:

    arduino 复制代码
    npm run build

    然后获取构建后的静态资源,通常位于:

    ruby 复制代码
    .next/
    public/   ← 静态资源目录

    不过 Next.js 是服务端渲染框架,不像 Vue/React 的纯前端 SPA 构建后只有静态 HTML/JS,它需要 Node 服务来运行。

    ⚠️ 所以通常用于打进 .jar 的前端项目是纯静态页面(比如 Vue CLI、Vite 构建的项目),而不是 SSR 的 Next.js 项目,除非你把 Next.js export 成纯静态 HTML(用 next export

  2. 将前端静态资源复制到 Java 项目的 resources/static

    在 Spring Boot 项目中,有一个目录:

    css 复制代码
    src/main/resources/static/

    你可以把构建好的前端资源(如 out/dist/)拷贝进去:

    bash 复制代码
    # 举例:将前端构建结果复制到后端静态目录
    cp -r your-frontend-project/out/* your-springboot-project/src/main/resources/static/

    Spring Boot 会自动将 static/ 下的资源映射为 Web 资源,用户访问 / 会加载你的前端首页。

得到一个完整的 .jar 文件,只需使用 Java 运行 jar:java -jar target/app-1.0.0.jar, 这个 .jar 是一个完整的 HTTP 服务(内嵌了 Tomcat),会监听端口,比如 http://localhost:8080

注意:仅限使用 next export 将项目转成纯静态资源时:npx next export,会生成一个 out/ 文件夹,你可以把里面的文件复制进 Spring Boot 的 static/

后端代理前端开发环境

有的时候会遇到后端想要代理前端的开发起的服务,然后会出现访问静态资源403,这是因为:

Next.js 的 dev server 默认是按根路径 / 提供静态资源的。

一旦你通过代理让访问路径变成了 /frontend/,而又没配置对应的 basePath,前端就找不到资源了,或者服务端返回 403(禁止访问)。

解决方案:使用 basePath + 代理转发配置匹配

设置 next.config.js

java 复制代码
/** @type {import('next').NextConfig} */
const nextConfig = {
  basePath: '/frontend', // 👈 设置访问前缀
}

module.exports = nextConfig

这样所有页面、静态资源、JS 都会自动以 /frontend 为前缀。

此时如果需要访问/pubilc等公共资源,也需要加上/frontend

例如:

  • 页面变成 /frontend
  • 静态资源变成 /frontend/_next/static/xxx.js

网关/反向代理配置转发 /frontendlocalhost:3000

bash 复制代码
location /frontend/ {
  proxy_pass http://localhost:3000/frontend/; # 👈 保留 /frontend
  proxy_http_version 1.1;
  proxy_set_header Host $host;
  proxy_set_header Connection '';
  proxy_cache_bypass $http_upgrade;
}

注意:一定 不要 去掉 /frontend/ 路径,否则前端的 basePath 会找不到

bash 复制代码
proxy_pass` 后面也要带上 `/frontend/

访问地址

你现在应该访问:http://后端网关IP/frontend,就可以访问到 Next.js 页面、JS、图片等静态资源,不再 403 / 404

为什么不通过网关或nginx为 _next 静态资源添加代理?

  1. 资源路径割裂: 页面路径是 /frontend,资源路径是 /_next,路径逻辑不一致
  2. SSR 返回的 HTML 不一致: 服务端返回的 HTML 中资源引用路径是 /_next/...,但页面在 /frontend
  3. 缺少统一前缀控制: 页面和资源分别配置,不如使用 basePath 一致性好
  4. 法代理静态文件如 /favicon.ico/robots.txt 等: 因为这些资源也要单独加代理才生效

因此,推荐使用basePath,自动解决路径和资源映射问题

在本地联调接口,配置代理

假设我们需要在本地开发时,调试接口,并且后端会代理我们的服务,最后还需要部署到线上,我们可以如下处理:

一、使用环境变量管理接口地址

bash 复制代码
.env.development     # 开发环境
.env.production      # 生产环境

.env.development 和 .env.production 文件名可以随便改吗?

不可以!Next.js(以及大多数 Node.js 项目)会自动识别以下几种环境变量文件名:

  • .env(通用环境变量)
  • .env.local(本地覆盖,git 忽略)
  • .env.development(开发环境专用)
  • .env.development.local(开发环境本地覆盖)
  • .env.production(生产环境专用)
  • .env.production.local(生产环境本地覆盖)

项目如何区分生产环境和开发环境?

Next.js 通过环境变量 NODE_ENV 来区分:

  • NODE_ENV=development:开发环境(next dev 时自动设置)
  • NODE_ENV=production:生产环境(next build 和 next start 时自动设置)

加载顺序:

运行环境 加载顺序(后者覆盖前者)
开发环境 .env → .env.local → .env.development → .env.development.local
生产环境 .env → .env.local → .env.production → .env.production.local

二、配置接口环境变量

.env.development

ini 复制代码
NEXT_PUBLIC_API_BASE=/api

.env.production

ini 复制代码
# 有nginx代理
NEXT_PUBLIC_API_BASE=/web
# 没有nginx或者网关代理就写真实地址
NEXT_PUBLIC_API_BASE=https://api.yourdomain.com

变量名必须以 NEXT_PUBLIC_ 开头,才能在客户端代码中访问 EXT_PUBLIC_ 前缀是 Next.js 的一个安全机制:

  • 只有以 NEXT_PUBLIC_ 开头的环境变量才会被注入到前端代码
  • 其他环境变量只能在服务端使用,不会暴露到前端

让开发者必须显式声明哪些环境变量是公开的,避免意外泄露敏感信息。

三、封装 Axios 实例

php 复制代码
import axios from 'axios'

const service = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_BASE, // 👈 环境自动切换
  timeout: 10000,
  withCredentials: true,
})

四、开发环境:配合 rewrites 实现接口代理

javascript 复制代码
const nextConfig = {
  async rewrites() {
		// 生产环境下也会走rewrites需要做判断
    if (process.env.NODE_ENV === 'development') {
      return [
        {
          source: '/api/:path*',
          destination: 'http://10.1.1.1:4000/:path*', // 后端真实路径(无 /api)
        },
      ]
    }
		
    return []
  },
}

module.exports = nextConfig

开发时前端请求 /api/user/info,Next.js 会自动转发到后端,无跨域。

五、生产环境配合nginx

bash 复制代码
# 假设页面路径是 /web(Next.js 的 basePath 可选设置)
location /web/ {
  proxy_pass http://localhost:3000/web/;
  proxy_set_header Host $host;
}

# 如果没配置basePath
 location / {
   proxy_pass http://localhost:3000;  # 代理到 Next.js 服务
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
  }
}


# 接口路径也是 /web/xxx,但实际转发为 /xxx
location ~ ^/web/(.*)$ {
  rewrite ^/web/(.*)$ /$1 break;
  proxy_pass http://10.1.1.1:4000;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
}

选配:如果你前端页面也部署在 /web 路径下):

javascript 复制代码
// next.config.js
module.exports = {
  basePath: '/web',
  async rewrites() {
    const isDev = process.env.NODE_ENV === 'development'
    return isDev
      ? [
          {
            source: '/api/:path*',
            destination: 'http://10.1.1.1:4000/:path*',
          },
        ]
      : []
  }
}
相关推荐
Hockor1 小时前
用 Kimi K2 写前端是一种什么体验?还支持 Claude Code 接入?
前端
杨进军1 小时前
React 实现 useMemo
前端·react.js·前端框架
海底火旺1 小时前
浏览器渲染全过程解析
前端·javascript·浏览器
你听得到111 小时前
揭秘Flutter图片编辑器核心技术:从状态驱动架构到高保真图像处理
android·前端·flutter
驴肉板烧凤梨牛肉堡1 小时前
浏览器是否支持webp图像的判断
前端
Xi-Xu1 小时前
隆重介绍 Xget for Chrome:您的终极下载加速器
前端·网络·chrome·经验分享·github
摆烂为不摆烂1 小时前
😁深入JS(九): 简单了解Fetch使用
前端
杨进军1 小时前
React 实现多个节点 diff
前端·react.js·前端框架
用户40812812003811 小时前
拓展运算符和剩余参数
前端
再见了那维莱特1 小时前
6.29 drilling notes
前端