从 SSR 踩坑到 CSR 封神:Nuxt4 全流程终极实战

Vue3 + Axios + 多环境部署 + 国际化 全套落地方案

一、前言

全程使用 Nuxt4 开发。从最开始疯狂踩 SSR、接口 502、打包报错、部署失败,到最后彻底跑通全流程,我把所有真实踩坑 + 解决方案全部整理在这里。


二、我的项目背景

  • 项目类型:智能仓储 / 工厂 3D 可视化后台系统

  • 技术栈:Vue3 + Nuxt4 + Three.js + Axios + i18n 国际化

  • 核心需求:

    • 3D 场景渲染
    • 接口请求统一封装
    • 中英文切换
    • 开发 / 生产环境自由切换
    • 打包后不改代码、不重新打包就能换接口
    • 稳定部署,不 502、不报错

三、我踩过的所有 Nuxt4 大坑(全部真实)

1. 打包后访问报错:nuxt instance unavailable

原因 在 axios 工具文件最外层直接写:

js

arduino 复制代码
const config = useRuntimeConfig()

SSR 服务端执行时,还没有实例,直接崩溃。

最终正确写法 把 useRuntimeConfig() 放到 请求拦截器里:

js

arduino 复制代码
service.interceptors.request.use((config) => {
  const runtimeConfig = useRuntimeConfig()
  config.baseURL = runtimeConfig.public.apiBase
})

2. 接口代理配置错误,一直 502

错误写法

ts

vbnet 复制代码
routeRules: {
  '/api/**': {
    proxy: '{{runtimeConfig.public.apiBase}}/api/**'
  }
}

原因 :routeRules 不支持运行时变量,只会当成字符串,所以代理地址无效。

正确方案

  • 开发环境:用 vite 代理
  • 生产环境:用运行时环境变量

3. 服务端渲染(SSR)不适合我的项目

我总结了一个超级实用的判断标准:

表格

项目类型 是否适合 SSR
官网、电商、需要 SEO ✅ 适合
后台系统、3D 可视化、内部系统 ❌ 不适合

我的项目属于后台系统 + 3D 渲染,直接关闭 SSR:

ts

arduino 复制代码
export default defineNuxtConfig({
  ssr: false
})

关闭后:

  • localStorage 正常用
  • 不再报实例错误
  • 部署超级简单
  • 接口不再 502

四、开发 / 生产环境一套代码搞定

1. 开发环境(vite 代理)

ts

css 复制代码
vite: {
  server: {
    proxy: {
      '/api': {
        target: 'http://10.102.129.12:18088',
        changeOrigin: true
      }
    }
  }
}

2. 生产环境(运行时配置)

ts

css 复制代码
runtimeConfig: {
  public: {
    apiBase: '' // 留空,环境变量覆盖
  }
},
nitro: {
  host: '0.0.0.0',
  port: 3000
}

五、Axios 封装最终版(可直接复制)

ts

javascript 复制代码
import axios from 'axios'

const service = axios.create({
  baseURL: '/api',
  timeout: 10000
})

service.interceptors.request.use((config) => {
  if (process.env.NODE_ENV === 'production') {
    const runtimeConfig = useRuntimeConfig()
    config.baseURL = runtimeConfig.public.apiBase
  }

  const token = process.client ? localStorage.getItem('factory_token') : null
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

service.interceptors.response.use((response) => {
  const res = response.data
  if (res.code !== 'SUCCESS') return Promise.reject(res)
  return res.records
})

export const request = {
  get: (url, params) => service.get(url, { params }),
  post: (url, data) => service.post(url, data),
  put: (url, data) => service.put(url, data),
  delete: (url, params) => service.delete(url, { params })
}

export default service

六、国际化 i18n 配置(URL 不带前缀)

ts

css 复制代码
i18n: {
  locales: ['zh', 'en'],
  defaultLocale: 'zh',
  strategy: 'no_prefix',
  detectBrowserLanguage: false
}

七、生产环境启动命令

PowerShell

powershell

bash 复制代码
$env:NUXT_PUBLIC_API_BASE="http://10.102.129.12:18088"
node .output/server/index.mjs

Windows 一键启动脚本 start.bat

bat

bash 复制代码
@echo off
set NUXT_PUBLIC_API_BASE=http://10.102.129.12:18088
node .output/server/index.mjs
pause

Docker 部署(目录挂载,不用重新打包)

yaml

yaml 复制代码
version: '3.8'
services:
  nuxt-app:
    image: node:20-alpine
    volumes:
      - ./.output:/app
    ports:
      - "3000:3000"
    environment:
      - NUXT_PUBLIC_API_BASE=http://10.102.129.12:18088
    command: ["node", "server/index.mjs"]

八、nuxt 配置文件

php 复制代码
export default defineNuxtConfig({
  ssr: true,
  
  // 只保留最干净的配置
  modules: [
    '@pinia/nuxt',
    '@element-plus/nuxt',
    '@nuxtjs/i18n',
    '@pinia-plugin-persistedstate/nuxt'
  ],
  css: ['~/assets/css/main.css'],
  elementPlus: {
    // 配置图标组件前缀,设置为 'ElIcon' 即可启用自动导入
    icon: "ElIcon",
    // 如果你需要自定义主题,可以设置为 'scss'
    // importStyle: 'scss',
  },
  i18n: {
    // 指定翻译文件存放的目录
    langDir: '',

    // 配置支持的语言列表
    locales: [
      { 
        code: 'zh', 
        language: 'zh', 
        file: 'zh.json', 
        name: '简体中文' 
      },
      { 
        code: 'en', 
        language: 'en', 
        file: 'en.json', 
        name: 'English' 
      },
    ],

    // 默认语言
    defaultLocale: 'en',

    
    strategy: 'no_prefix',
    

    // 是否检测浏览器语言并进行重定向
    detectBrowserLanguage: {
      useCookie: true, // 使用cookie保存用户语言选择
      cookieKey: 'i18n_redirected', // cookie的key
      redirectOn: 'root', // 仅在访问根路径时检测
    },
  },
  runtimeConfig: {
    public: {
      apiBase: "", // 运行时覆盖
    },
  },
  nitro: {
    compressPublicAssets: true,
    minify: true,
    devProxy: {
      '/web': {
        target: 'http://10.102.129.12:18088/web',
        changeOrigin: true,
      }
    },
    // routeRules: {
    //   "/web/**": {
    //     proxy: 'http://10.102.129.12:18088/web/**'
    //   }
    // }
  },
  vite: {
    ssr: {
      noExternal: ['vue']
    }
  }
})

九、结束语

这篇文章完全来自 我真实的 Nuxt4 实战提问与踩坑历史,从零基础到全流程打通,希望能帮助到正在做 Nuxt4 后台、可视化、3D 项目的同学。

如果你也在踩坑,欢迎交流~

相关推荐
miss4 分钟前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan42 分钟前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真1 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment1 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan1 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么1 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
上海懒图科技1 小时前
工厂厂区电子地图定位导航:厂区人车精准定位,安全合规双保障
前端·arcgis·vue
西安小哥2 小时前
端侧跑大模型,真正的门槛不在参数量:一次 596 秒到 10 秒的压缩路径复盘
前端·人工智能
陈随易2 小时前
Cloudflare 出钱,Vite 团队挑人,44 个维护者分 30 万美元
前端·后端·程序员
恋猫de小郭3 小时前
Flutter THLC 的 WebView 滚动终于从 70 FPS 提升到 120 FPS
android·前端·flutter