开发环境和生产环境nextjs的产物区别
Next.js 中"静态资源"的几种类型:
类型 | 放置位置 | 访问路径 | 示例 |
---|---|---|---|
公开资源(public) | /public |
/xxx |
/public/logo.png → /logo.png |
构建产物(_next/static) | 自动生成,放在内存中 | /_next/static/... |
构建时生成的 JS、CSS、图片等 |
静态导出的页面 | SSG 页面输出为 HTML 文件 | 自定义路径或嵌入构建目录 | /about → about.html (仅限 export 模式) |
开发环境
- 内部启动一个 Node.js + Webpack(或 Turbopack) 的服务
- 所有静态资源都是内存中实时生成,不是写在磁盘上的
- 公开资源
public/
文件照常访问,无需打包 - 所有 JS/CSS/图片资源由
/_next/static/
路径动态提供
特点:
特性 | 是否持久存储 | 说明 |
---|---|---|
public/ 静态资源 |
✅ 有磁盘文件 | 本地文件系统直接读取 |
/_next/static/ 资源 |
❌ 无磁盘文件 | 临时缓存于内存中,实时构建 |
生产环境
路径 | 说明 |
---|---|
.next/static/chunks/ |
各页面按需加载的 JS chunk |
.next/static/media/ |
使用 import 的图片、字体等静态文件 |
public/ |
所有手动放的公开资源,不会变,直接拷贝 |
构建完成后所有这些资源可以通过 /_next/static/xxx
路径访问,Next.js 会自己提供路由处理。
部署后的静态资源加载流程:
- 用户访问页面(如
/about
) - 服务端返回 SSR 或 SSG 渲染页面
- 页面中会加载
/_next/static/...
的 JS 和 CSS - 浏览器请求这些资源,Next.js 服务会直接返回
.next/static
下的文件内容 - 所有
/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 start
或 next 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
包,直接运行即可部署。
这个过程可以分为三步:
-
前端构建(Next.js)
你首先运行:
arduinonpm 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
) 。 -
将前端静态资源复制到 Java 项目的
resources/static
中在 Spring Boot 项目中,有一个目录:
csssrc/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
网关/反向代理配置转发 /frontend
到 localhost: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 会找不到
bashproxy_pass` 后面也要带上 `/frontend/
访问地址
你现在应该访问:http://后端网关IP/frontend,就可以访问到 Next.js 页面、JS、图片等静态资源,不再 403 / 404。
为什么不通过网关或nginx为 _next
静态资源添加代理?
- 资源路径割裂: 页面路径是
/frontend
,资源路径是/_next
,路径逻辑不一致 - SSR 返回的 HTML 不一致: 服务端返回的 HTML 中资源引用路径是
/_next/...
,但页面在/frontend
下 - 缺少统一前缀控制: 页面和资源分别配置,不如使用
basePath
一致性好 - 法代理静态文件如
/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*',
},
]
: []
}
}