
最近在开发一个前后端分离项目时用 Vite 作为前端构建工具、Maven 作为后端工程管理工具结果在环境兼容、跨域代理、构建产物提交这几个环节反复踩坑。网上的资料大多只讲了其中某一段很少有人把「Vite Maven」这对组合从零到一完整串起来。本文整理了一份闭环实操方案包含前后端工程结构划分、代理配置、打包联动、本地联调以及常见报错排查适合正在做前后端分离项目、准备把构建流程整合起来的新手也适合需要快速搭一套可维护工程骨架的后端开发。「VxM」在这篇文章里特指 Vite Maven 的技术组合“cp 向自行避雷”的意思是如果你准备用这对组合管理前后端项目哪些地方容易出问题、哪些配置别乱改我会把容易踩的坑提前标注出来方便你自行避让。1. Vite 与 Maven 分别解决什么问题1.1 Maven 在项目里的定位Maven 是 Java 生态里非常经典的构建工具核心能力是依赖管理和生命周期管理。你在pom.xml里声明需要哪些第三方库Maven 会从中央仓库把对应版本的 jar 包下载到本地仓库并在打包时将这些依赖按作用域compile、provided、runtime、test处理进最终产物。此外Maven 把项目构建过程拆成了标准生命周期比如validate - compile - test - package - verify - install - deploy这意味着你不需要手动去下载 jar、不需要自己敲 javac 编译命令只需要执行mvn clean package就可以完成从清理到打包的全部步骤。对于多人协作的 Java 项目来说统一构建命令、统一依赖版本、统一打包产物结构价值非常大。1.2 Vite 在项目里的定位Vite 是前端构建工具定位是开发体验好、构建速度快。它底层利用浏览器原生 ES Module 的能力在开发环境不需要像 Webpack 那样先把所有模块打包完再启动服务而是按需编译所以冷启动非常快。生产构建时则使用 Rollup 来打包最终输出静态资源。Vite 的核心配置文件是vite.config.js可以配置开发服务器端口、代理、别名、构建输出目录、环境变量等。配合 React 或 Vue 使用非常方便。1.3 为什么要把 Vite 和 Maven 放在一起大部分后端项目并不只是提供接口还需要把前端构建出来的静态资源嵌入到 Spring Boot 的 jar 包中实现一个“单一可部署产物”。如果前端构建和后端打包完全分离会出现几个问题前端改完代码后需要手动执行构建命令再把dist目录拷贝到后端resources/static下过程繁琐且容易漏。团队协作时前端产物可能没有及时提交后端人员打包后运行的是旧的静态页面。本地联调时如果前端开发服务器和后端接口服务端口不一致跨域问题会反复出现。通过 Maven 插件把 Vite 构建过程集成进来可以实现执行mvn clean package时自动先构建前端资源再将构建产物复制到后端静态资源目录最后统一打包成可运行的 jar。2. 环境准备与项目结构说明2.1 基础环境本文示例环境如下工具版本建议JDK1.8 及以上Maven3.6 及以上Node.js16 及以上npm 或 pnpmnpm 8 或 pnpm 7Spring Boot2.7.x按实际项目调整Vite4.x按实际项目调整如果本机还没装 Node.js 和 Maven可以先执行下面两条命令确认环境mvn -v node -v如果命令提示找不到说明环境变量没有配置好需要先安装对应的运行环境。这里的版本并不是硬性要求你完全可以根据公司已有技术栈调整本文重点演示配置方式和组合思路。2.2 推荐的项目目录结构为了让前后端代码在同一个仓库里管理又避免互相干扰推荐采用下面的结构vxm-demo/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/ │ ├── resources/ │ └── vue-app/ # 前端项目 │ ├── package.json │ ├── vite.config.js │ ├── index.html │ └── src/ │ ├── main.js │ ├── App.vue │ └── components/ └── docs/把前端项目放在src/main/vue-app下而不是单独放在仓库根目录的平行目录是为了让 Maven 在构建时可以直接定位到前端源码目录减少路径穿越的配置成本。当然如果你更倾向于把frontend目录放在根目录也是可以的只要 Maven 插件里的workingDirectory配置对应调整。3. 核心配置拆解Maven 集成 Vite 构建3.1 使用 frontend-maven-plugin在 Maven 里集成前端构建最常用的插件是frontend-maven-plugin它可以自动下载 Node.js 和 npm然后执行npm install与npm run build。下面是一个基础的配置示例放在pom.xml的buildplugins中plugin groupIdcom.github.eirslett/groupId artifactIdfrontend-maven-plugin/artifactId version1.12.1/version configuration workingDirectorysrc/main/vue-app/workingDirectory /configuration executions execution idinstall node and npm/id goals goalinstall-node-and-npm/goal /goals phasegenerate-resources/phase configuration nodeVersionv16.20.2/nodeVersion npmVersion8.19.4/npmVersion /configuration /execution execution idnpm install/id goals goalnpm/goal /goals phasegenerate-resources/phase configuration argumentsinstall/arguments /configuration /execution execution idnpm build/id goals goalnpm/goal /goals phasegenerate-resources/phase configuration argumentsrun build/arguments /configuration /execution /executions /plugin几个关键点需要说明workingDirectory指定了前端项目的根目录也就是package.json所在的位置。install-node-and-npm会自动下载对应版本的 Node.js 和 npm。如果本地已经安装了 Node.js可以去掉这一步但考虑到团队新成员环境可能不统一保留会更省心。三个 execution 都绑定在generate-resources阶段这个阶段在compile之前可以保证前端构建产物先就绪后续复制资源时不会找不到目录。另一种做法是使用exec-maven-plugin直接调用本机的npm命令。这种方式不会自动安装 Node.js但可以利用本机已有的 npm 镜像配置速度可能更快。示例plugin groupIdorg.codehaus.mojo/groupId artifactIdexec-maven-plugin/artifactId version3.1.0/version executions execution idexec-npm-install/id phasegenerate-resources/phase configuration workingDirectorysrc/main/vue-app/workingDirectory executablenpm/executable arguments argumentinstall/argument /arguments /configuration goals goalexec/goal /goals /execution execution idexec-npm-build/id phasegenerate-resources/phase configuration workingDirectorysrc/main/vue-app/workingDirectory executablenpm/executable arguments argumentrun/argument argumentbuild/argument /arguments /configuration goals goalexec/goal /goals /execution /executions /plugin3.2 将 dist 目录复制到 Spring Boot 静态资源目录前端npm run build之后产物默认在src/main/vue-app/dist目录。为了让 Spring Boot 能直接托管这些静态资源需要将dist里的内容复制到src/main/resources/static。Spring Boot 默认会从以下位置加载静态资源classpath:/static/ classpath:/public/ classpath:/resources/ classpath:/META-INF/resources/因此把前端构建产物放到classpath:/static/下是最直接的方式。复制操作可以使用maven-resources-plugin完成plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-resources-plugin/artifactId executions execution idcopy-vue-dist/id phaseprepare-package/phase goals goalcopy-resources/goal /goals configuration outputDirectory${project.build.outputDirectory}/static/outputDirectory resources resource directorysrc/main/vue-app/dist/directory /resource /resources /configuration /execution /executions /pluginprepare-package阶段位于compile之后、package之前此时编译产物已经生成复制进target/classes/static后最终会被放进 jar 包。这里有一个容易踩的坑如果输出目录写成了src/main/resources/static可能会污染本地的源码目录尤其在使用 git 时会产生大量不需要提交的变动。推荐直接输出到target/classes/static保证源码目录干净。3.3 配置 Vite 的 base 路径前端构建时生成的资源路径默认是/如果你的后端接口整体带有上下文路径比如http://localhost:8080/project-name/就需要在vite.config.js中配置base。打开前端目录下的vite.config.jsimport { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ base: ./, plugins: [vue()], server: { port: 3000, host: 0.0.0.0, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }, build: { outDir: dist, assetsDir: assets } })base: /是默认值适用于部署在域名根路径的场景。如果改成base: ./构建出来的 HTML 资源路径会变成相对路径方便直接部署在任意子目录或者通过 jar 包访问时减少路径问题。proxy配置是本地联调的关键。开发时 Vite 服务器默认跑在3000端口后端接口在8080端口如果不配置代理浏览器直接请求http://localhost:8080/api会跨域。配置代理后前端代码里请求/api时Vite 会在开发服务器层面把请求转发到后端浏览器的同源策略限制就不存在了。4. 完整实战搭建一个 Vite Spring Boot Maven 项目4.1 创建后端工程骨架先用 Spring Initializr 或者手工创建 Maven 工程核心是pom.xml包含 Spring Boot 相关依赖。假设项目名称为vxm-demopom.xml关键内容如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.17/version relativePath/ /parent properties java.version1.8/java.version node.versionv16.20.2/node.version npm.version8.19.4/npm.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies创建主启动类// 文件路径src/main/java/com/example/vxmdemo/VxmDemoApplication.java package com.example.vxmdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class VxmDemoApplication { public static void main(String[] args) { SpringApplication.run(VxmDemoApplication.class, args); } }创建一个简单的接口用于联调// 文件路径src/main/java/com/example/vxmdemo/controller/HelloController.java package com.example.vxmdemo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; RestController RequestMapping(/api) public class HelloController { GetMapping(/hello) public MapString, String hello() { MapString, String result new HashMap(); result.put(message, Hello from Spring Boot); return result; } }4.2 创建前端工程在前端目录src/main/vue-app下初始化项目。如果目录不存在先创建mkdir -p src/main/vue-app然后进入目录初始化package.jsoncd src/main/vue-app npm init -y安装 Vue 和 Vitenpm install vue3 npm install vite4 vitejs/plugin-vue --save-dev为了方便npm run build调用构建命令在package.json中配置 scripts{ name: vue-app, version: 1.0.0, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { vue: ^3.3.4 }, devDependencies: { vitejs/plugin-vue: ^4.2.3, vite: ^4.4.9 } }创建index.html!-- 文件路径src/main/vue-app/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleVite Maven Demo/title /head body div idapp/div script typemodule src/src/main.js/script /body /html创建前端入口文件// 文件路径src/main/vue-app/src/main.js import { createApp } from vue import App from ./App.vue createApp(App).mount(#app)创建根组件!-- 文件路径src/main/vue-app/src/App.vue -- template div stylefont-family: Arial, sans-serif; padding: 20px; h1Vite Maven 工程演示/h1 p{{ message }}/p button clickfetchMessage请求后端接口/button /div /template script setup import { ref } from vue const message ref(点击按钮请求 Spring Boot 接口) async function fetchMessage() { const response await fetch(/api/hello) const data await response.json() message.value data.message } /script这里的fetch(/api/hello)在开发环境会通过 Vite 代理转发到后端在生产环境则会直接访问同源地址下的/api/hello所以不需要区分环境地址。4.3 配置前端代理在src/main/vue-app目录下创建vite.config.js// 文件路径src/main/vue-app/vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ base: ./, plugins: [vue()], server: { port: 3000, host: 0.0.0.0, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }, build: { outDir: dist, assetsDir: assets } })这里需要特别解释changeOrigin的作用。当 Vite 代理转发请求时请求头里的Host默认是localhost:3000如果后端服务对Host有校验可能会拒绝请求或者会因为虚拟主机配置而路由错误。设置changeOrigin: true后代理会重写请求的Host为target的主机名避免这类问题。4.4 完整 pom.xml 配置把前面的内容整合到项目的pom.xmlproject modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.17/version relativePath/ /parent groupIdcom.example/groupId artifactIdvxm-demo/artifactId version1.0.0/version packagingjar/packaging properties java.version1.8/java.version node.versionv16.20.2/node.version npm.version8.19.4/npm.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin plugin groupIdcom.github.eirslett/groupId artifactIdfrontend-maven-plugin/artifactId version1.12.1/version configuration workingDirectorysrc/main/vue-app/workingDirectory /configuration executions execution idinstall node and npm/id goals goalinstall-node-and-npm/goal /goals phasegenerate-resources/phase configuration nodeVersion${node.version}/nodeVersion npmVersion${npm.version}/npmVersion /configuration /execution execution idnpm install/id goals goalnpm/goal /goals phasegenerate-resources/phase configuration argumentsinstall/arguments /configuration /execution execution idnpm build/id goals goalnpm/goal /goals phasegenerate-resources/phase configuration argumentsrun build/arguments /configuration /execution /executions /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-resources-plugin/artifactId executions execution idcopy-vue-dist/id phaseprepare-package/phase goals goalcopy-resources/goal /goals configuration outputDirectory${project.build.outputDirectory}/static/outputDirectory resources resource directorysrc/main/vue-app/dist/directory /resource /resources /configuration /execution /executions /plugin /plugins /build /project4.5 运行与验证先启动后端服务mvn spring-boot:run后端默认端口是 8080通过http://localhost:8080/api/hello可以验证接口是否正常。然后启动前端开发服务器cd src/main/vue-app npm run dev浏览器访问http://localhost:3000点击页面按钮如果能看到后端返回的消息说明代理生效前后端联调正常。接下来测试整体打包mvn clean package打包完成后查看target目录ls -lh target/*.jar运行 jarjava -jar target/vxm-demo-1.0.0.jar访问http://localhost:8080/可以看到前端页面点击按钮也能正常请求后端接口。这说明前端资源已经被正确打进 jar 包。4.6 注意事项首次构建时间较长使用frontend-maven-plugin时第一次执行构建会自动下载 Node.js 和 npm耗时取决于网络状况。如果公司网络访问 Node 官方源较慢可以通过设置nodeDownloadRoot和npmDownloadRoot参数指定镜像地址。也可以跳过自动下载直接使用本地 Node.js用exec-maven-plugin方案代替。5. 常见问题与排查思路5.1 前端构建产物没有复制到 static 目录问题现象执行mvn clean package后target/classes/static目录不存在或者 jar 包中没有前端页面。常见原因maven-resources-plugin的复制阶段配置错误。前端构建过程没有被触发。src/main/vue-app/dist目录不存在说明npm run build失败。解决思路在本地手动执行前端构建确认能否生成dist目录cd src/main/vue-app npm install npm run build如果构建成功再检查pom.xml中插件的执行阶段和输出目录是否正确。5.2 前端页面能打开但接口请求 404问题现象访问http://localhost:8080/能看到页面但点击按钮后接口返回 404。常见原因前端构建时请求的接口路径是/api/hello后端接口地址不匹配。Spring Boot 的 context-path 配置有值比如server.servlet.context-path/demo导致实际接口地址变成/demo/api/hello。接口所在的 Controller 类没有被扫描到。解决思路先用 curl 直接测试后端接口curl http://localhost:8080/api/hello如果接口返回数据说明后端正常问题出在前端路径。如果返回 404检查RestController的注解、SpringBootApplication所在的包路径以及application.properties中的context-path配置。5.3 开发环境跨域请求失败问题现象前端页面跑在 3000 端口直接请求 8080 端口的接口浏览器控制台报 CORS 错误。常见原因Vite 代理未生效。proxy配置路径写错。前端请求地址写了全路径比如http://localhost:8080/api/hello没有走/api开头的相对路径。解决思路检查浏览器请求的 URL如果是http://localhost:3000/api/hello说明代理生效。如果请求直接指向了http://localhost:8080则需要修改前端请求代码使用/api/hello。另外确认 Vite 配置文件的修改已经生效。Vite 开发服务器并不一定会自动重启修改vite.config.js后建议重启npm run dev。5.4 Maven 构建时报 npm 命令找不到问题现象Failed to execute goal com.github.eirslett:frontend-maven-plugin:1.12.1:npm (npm install) on project vxm-demo: The npm command could not be found.常见原因install-node-and-npm执行失败Node.js 没有下载成功。workingDirectory配置的路径没有找到package.json。系统代理或镜像源问题导致下载超时。解决思路先检查target目录下是否有 Maven 自动下载的 Node.js 文件比如node目录。如果为空说明下载步骤失败了可以尝试设置镜像源configuration nodeDownloadRoothttps://npmmirror.com/mirrors/node//nodeDownloadRoot npmDownloadRoothttps://npmmirror.com/mirrors/npm//npmDownloadRoot /configuration也可以换成exec-maven-plugin方案直接使用本机已安装的 Node.js 和 npm。5.5 打包后静态资源加载 404问题现象jar 包运行后访问根路径出现 404 或者页面空白。常见原因静态资源没有打包进classpath:/static/。Vite 构建的index.html里的资源路径不对。前端路由使用的是 history 模式刷新后交给后端处理但后端没有对应的路由转发配置。解决思路先确认 jar 包中是否包含静态资源jar tf target/vxm-demo-1.0.0.jar | grep static如果没有输出说明复制阶段出问题。如果有输出再看index.html的引用路径。如果前端使用了vue-router的history模式还需要在后端处理前端路由的 fallback但这超出了本文范围建议先使用hash模式避免该问题。排查清单如下问题现象常见原因解决思路接口 404请求路径和后端RequestMapping不匹配用 curl 测试接口比对路径接口 404context-path导致地址前缀变化检查application.properties或yml中的server.servlet.context-path跨域报错前端请求走了全路径改成/api/xx相对路径交给代理转发跨域报错Vite 代理配置未重启修改vite.config.js后重启开发服务器打包后页面空白dist里资源路径是绝对路径/assets/xx.js修改base: ./后重新构建打包后页面空白前端路由 history 模式刷新 404改用 hash 模式或配置后端 fallback首次构建很慢Maven 下载 Node.js/npm 超时配置镜像源替换下载地址6. 最佳实践与工程建议6.1 明确前后端边界Vite Maven 的组合容易给人一种“前后端代码放一起”的错觉但在实际开发中建议严格区分模块边界后端接口不要依赖前端工程里的任何代码。前端代码不要直接引用后端 Java 类。前后端通过 HTTP 接口通信接口的入参、出参、错误码要提前定义好。在项目结构上前端代码只是后端构建时的一个构建产物来源而不是后端项目的一部分。6.2 使用环境变量区分部署场景Vite 支持通过环境变量区分开发、测试、生产环境。你可以在前端目录下创建.env.development和.env.production文件分别写入不同的配置项。例如# .env.development VITE_API_BASE/api# .env.production VITE_API_BASE/api然后在代码中使用import.meta.env.VITE_API_BASE拼接接口路径。这样可以避免在代码中写死环境地址。不过要注意Vite 环境变量默认只在构建时生效运行时修改不会生效。6.3 Maven 构建与前端构建解耦如果你的团队中有人不需要改前端代码每次执行mvn clean package都要等前端构建完成体验不够好。可以通过 Maven Profile 来区分是否构建前端。例如profiles profile idbuild-frontend/id activation activeByDefaulttrue/activeByDefault /activation build plugins !-- 前端构建和复制插件放在这里 -- /plugins /build /profile /profiles当需要跳过前端构建时执行mvn clean package -P !build-frontend或者直接设置 Maven 属性跳过mvn clean package -DskipFrontendtrue在插件配置里通过属性判断是否需要执行这样更灵活。6.4 统一 npm 镜像源为了避免团队中不同成员的 npm 下载速度差异可以在前端目录下创建.npmrc文件registryhttps://registry.npmmirror.com这会强制 npm 使用镜像源减少 install 时间。当然如果你的网络环境可以直接访问官方源也可以不配置。6.5 安全边界与部署建议前端资源打包进 jar 包适合中小型应用、内部系统、单体服务。如果你的应用访问量很大或需要独立扩容前端静态资源更推荐将前端资源部署到 Nginx、OSS、CDN而不是打进 Java 进程里。在生产环境部署时需要注意后端接口不要暴露到公网除非有明确需求和权限控制。配置 HTTPS 后注意前端资源中是否含有明文 http 请求。如果前端资源中有敏感信息比如密钥、API Token不要写在前端代码中。使用 Spring Security 时静态资源是否需要认证要根据业务决定。如果是控制台类系统通常需要登录后访问此时需要放行登录页相关资源但保护接口。6.6 资源目录不要提交到 Gitnode_modules、dist、target这些目录都不应该提交到版本控制中。建议在.gitignore中配置target/ node_modules/ src/main/vue-app/dist/ *.log .DS_Store这样团队成员克隆代码后执行构建会自动生成这些目录不会因为本地产物不同而产生冲突。7. 总结与下一步学习建议通过这篇文章你应该掌握了 Vite 与 Maven 组合使用的基本思路前端代码由 Maven 插件统一构建构建产物自动复制到 Spring Boot 静态资源目录最终打包成一个可运行的 jar 包。同时也了解了本地开发时如何通过 Vite 代理解决跨域问题、生产环境如何检查静态资源是否打包成功以及常见的几个报错点如何排查。如果你准备在团队中落地这种模式建议先从一个小项目开始尝试跑通后再推广到正式项目。重点观察首次构建时间、团队成员本机环境差异、前端依赖升级对构建的影响这三个方面。稳定的构建流程需要逐步调优不要期望一次配置就能覆盖所有场景。更进一步的优化方向包括前端使用pnpm替代 npm提升依赖安装速度。将前端构建步骤用 Docker Multi-stage 独立出来减少构建机器依赖。使用spring-boot-maven-plugin的layers功能优化 jar 包分层。前端增加 ESLint、单元测试、代码规范检查与 Maven 生命周期结合让坏代码在package之前就暴露出来。引入接口文档工具保证前后端并行开发时接口定义清晰。如果你对这套配置有什么坑要补充欢迎在评论区留言。后面我也会继续分享 Spring Boot 静态资源处理、前端构建性能优化、Maven Profile 多环境部署等相关内容。