从 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 项目的同学。

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

相关推荐
想努力找到前端实习的呆呆鸟1 小时前
网易云桌面端--精选歌单布局思路记录
前端·javascript·vue.js
Flywith241 小时前
【每日一技】Raycast 实现 scrcpy 的快捷显示隐藏
android·前端
薛端阳2 小时前
OpenClaw的架构优化思路杂想
前端
hi大雄2 小时前
我的 2025 — 名为《开始的勇气》🌱
前端·年终总结
OpenTiny社区2 小时前
TinyRobot:基于 OpenTiny Design 的企业级 AI 交互组件框架
前端·vue.js·ai编程
用户3153247795452 小时前
Tailwind CSS 学习手册
前端·css
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(三):核心概念之引擎架构与生命周期
前端·vue.js·ai编程
发际线向北2 小时前
0x00 Android 渲染机制解析
前端
_Eleven2 小时前
Tiptap 完全使用指南
前端·vue.js·github