Next.js 静态资源优化:开发、测试和生产环境分离部署实践

问题背景

在开发 Next.js 项目时,我们经常遇到以下问题:

  1. 静态资源(图片、视频等)体积较大,每次部署都需要上传这些文件
  2. 开发环境和生产环境的静态资源路径不同
  3. 需要将静态资源部署到专门的静态资源服务器(如 Nginx)
  4. 需要保持其他静态资源(如 CSS、JS)的原始路径

本文将介绍如何解决这些问题,实现静态资源的优化部署。

解决方案

1. 环境配置

首先,我们需要为不同环境配置不同的静态资源路径:

.env.development文件内容:

env 复制代码
# 开发环境配置
# 当运行 npm run dev 时自动加载此文件

# 环境标识
NODE_ENV=development

# 静态资源基础URL(开发环境本地地址)
NEXT_PUBLIC_STATIC_BASE_URL=

.env.test文件内容:

env 复制代码
# 测试环境配置
# 当运行 npm run build:test 时自动加载此文件

# 环境标识
NODE_ENV=test

# 静态资源基础URL(测试环境地址)
NEXT_PUBLIC_STATIC_BASE_URL=/nginx-assets/website

# 基础路径
NEXT_PUBLIC_BASE_PATH=/official-website 

.env.production文件内容:

env 复制代码
# 生产环境配置
# 当运行 npm run build 时自动加载此文件

# 环境标识
NODE_ENV=production

# 静态资源基础URL(CDN地址)
NEXT_PUBLIC_STATIC_BASE_URL=/nginx-assets/website 

2. Next.js 配置

next.config.ts 中配置静态资源路径:

typescript 复制代码
import type { NextConfig } from "next";

const isDev = process.env.NODE_ENV === 'development';
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
const assetsPath = process.env.NEXT_PUBLIC_ASSETS_PATH || '/nginx-assets/website';

const nextConfig: NextConfig = {
  /* config options here */
  output: 'export',  // 添加这行
  basePath,  // 添加基础路径配置
  images: {
    unoptimized: true,  // 如果使用了 next/image,需要添加这行
    domains: ['your-domain.com'], // 添加您的域名
    path: isDev ? '' : assetsPath, // 使用环境变量中的路径
  },
  // 添加配置以排除特定目录
  webpack: (config) => {
    if (!isDev) {
      // 在生产环境下,从 public 目录中排除 svg、images 和 videos 目录
      config.module.rules.push({
        test: /\.(png|jpg|jpeg|gif|svg|webp|avif|mp4|webm)$/,
        exclude: [
          /public\/svg/,
          /public\/videos/,
          /public\/images/
        ],
        use: {
          loader: 'file-loader',
          options: {
            publicPath: basePath || assetsPath,
            outputPath: 'static/media',
            name: '[name].[hash].[ext]',
          },
        },
      });
    }
    return config;
  },
};

export default nextConfig;

3. 静态资源路径处理工具

创建工具函数统一处理静态资源路径:

typescript 复制代码
// src/utils/static.ts
export const getStaticUrl = (path: string): string => {
  const baseUrl = process.env.NEXT_PUBLIC_STATIC_BASE_URL;
  const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
  
  // 开发环境下直接返回原始路径
  if (process.env.NODE_ENV === 'development') {
    return path;
  }

  // 检查是否是特殊资源路径(svg/、images/、videos/)
  const isSpecialPath = /^(\/?(svg|images|videos)\/)/i.test(path);
  
  if (isSpecialPath) {
    // 特殊资源使用完整的静态资源URL
    if (!baseUrl) {
      return path;
    }
    
    // 移除开头的斜杠
    let cleanPath = path.startsWith('/') ? path.slice(1) : path;
    
    // 处理视频路径
    if (cleanPath.startsWith('videos/')) {
      // 已经是正确的格式,不需要修改
    } else if (cleanPath.includes('/videos/')) {
      cleanPath = 'videos/' + cleanPath.split('/videos/')[1];
    }
    
    // 处理图片路径
    if (cleanPath.startsWith('svg/')) {
      // 已经是正确的格式,不需要修改
    } else if (cleanPath.includes('/svg/')) {
      cleanPath = 'svg/' + cleanPath.split('/svg/')[1];
    }
    
    // 处理图片路径
    if (cleanPath.startsWith('images/')) {
      // 已经是正确的格式,不需要修改
    } else if (cleanPath.includes('/images/')) {
      cleanPath = 'images/' + cleanPath.split('/images/')[1];
    }
    
    return `${baseUrl}/${cleanPath}`;
  } else {
    // 非特殊资源使用basePath
    return `${basePath}${path}`;
  }
};

4. 构建后清理脚本

创建构建后清理脚本,移除不需要的静态资源目录:

javascript 复制代码
// scripts/post-build.js
import fs from 'fs';
import path from 'path';

// 要删除的目录
const directoriesToRemove = [
  path.join(process.cwd(), 'out', 'images'),
  path.join(process.cwd(), 'out', 'videos'),
  path.join(process.cwd(), 'out', 'svg')
];

// 删除目录
directoriesToRemove.forEach(dir => {
  if (fs.existsSync(dir)) {
    console.log(`Removing directory: ${dir}`);
    fs.rmSync(dir, { recursive: true, force: true });
  }
});

5. 更新 package.json

package.json 中添加相关配置:

注意:cross-env 这部分是window上需要的配置,根据电脑系统不同需要有所调整。

json 复制代码
{
  "type": "module",
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build && node scripts/post-build.js",
    "build:test": "cross-env NODE_ENV=test next build && node scripts/post-build.js",
    "start": "next start",
    "lint": "next lint"
  }
}

实现效果

  1. 开发环境

    • 直接访问 public 目录下的静态资源
    • 开发体验不受影响
    • 所有路径保持原样
  2. 生产环境

    • 图片和视频资源从 Nginx 配置的目录加载
    • 其他静态资源(CSS、JS等)保持原始路径
    • 构建时不包含图片和视频目录
  3. 部署优化

    • 构建包体积减小
    • 部署时间缩短
    • 图片和视频由 Nginx 直接提供,性能更好
    • 其他静态资源保持原有部署方式

注意事项

  1. 确保 Nginx 配置正确:

    nginx 复制代码
    location /nginx-assets/ {
        alias /usr/local/nginx/nginx-assets/;
        autoindex on;
        autoindex_exact_size on;
        autoindex_localtime on;
    }
  2. 静态资源目录结构:

    • public/imagespublic/videospublic/svg 目录复制到服务器的 /usr/local/nginx/nginx-assets/website/ 目录
    • 保持原有的目录结构
  3. 版本控制:

    • .gitignore 中添加:

      arduino 复制代码
      public/images/
      public/videos/
      public/svg/

总结

通过以上配置,我们实现了:

  • 开发环境和生产环境的无缝切换
  • 图片和视频资源的优化部署
  • 其他静态资源保持原有路径
  • 构建包的体积优化
  • 部署效率的提升

这种方案特别适合:

  • 静态资源较多的项目
  • 需要频繁部署的项目
  • 对部署速度有要求的项目
  • 使用 Nginx 作为静态资源服务器的项目
相关推荐
小满zs1 小时前
React-router v7 第五章(路由懒加载)
前端·react.js
Aotman_2 小时前
Vue el-from的el-form-item v-for循环表单如何校验rules(二)
前端·javascript·vue.js
在无清风3 小时前
Java实现Redis
前端·windows·bootstrap
_一条咸鱼_5 小时前
Vue 配置模块深度剖析(十一)
前端·javascript·面试
yechaoa6 小时前
Widget开发实践指南
android·前端
前端切图仔0016 小时前
WebSocket 技术详解
前端·网络·websocket·网络协议
JarvanMo7 小时前
关于Flutter架构的小小探讨
前端·flutter
前端开发张小七7 小时前
每日一练:4.有效的括号
前端·python
顾林海7 小时前
Flutter 图标和按钮组件
android·开发语言·前端·flutter·面试
雯0609~8 小时前
js:循环查询数组对象中的某一项的值是否为空
开发语言·前端·javascript