前后端分离这个词前几年大家还在争论到底是不是过度设计现在基本已经是前端从业者的默认开发方式了。环境搭建这件事听起来简单可真到动手的时候很多同学会遇到一个尴尬的情况照着文档一步步装完了 Node、装完了数据库、启动了后端服务、启动了前端工程结果页面一打开接口全报跨域或者请求 404再或者代理配了但根本没生效于是一上午就耗在排查配置上了。我这些年帮团队搭过不少次前后端分离的开发环境也踩过不少次“看起来没问题但就是跑不通”的坑。这篇博文不打算讲高深理论而是把自己平时快速拉起一套前后端分离开发环境的完整流程、关键配置和排错思路整理出来。无论你是刚接触前后端分离的初学者还是要给新项目搭建基础架构的开发者这套流程都能帮你少走弯路半小时左右进入正经开发状态而不是卡在环境配置里。1. 前后端分离的开发场景与整体方案设计1.1 前后端分离究竟解决什么问题前后端分离的核心是把页面展示和数据处理这两件事拆开让前端专注于交互和渲染后端专注于接口和业务逻辑。早期很多项目是服务端渲染模式页面模板、跳转逻辑、数据绑定全混在后端代码里前端改了样式要重启服务后端改了数据格式前端页面也跟着遭殃。这种耦合越到项目后期越痛苦尤其是前端交互越来越复杂之后一套后端代码同时维护页面逻辑和接口逻辑工作量直接翻倍。引入前后端分离之后开发流程变成了前端写页面、调接口、处理状态后端只提供 API两边通过约定好的 JSON 数据结构通信。这样做的好处很直接团队分工清晰了后端接口可以复用多个客户端Web、小程序、App前端也可以独立迭代和部署。但需要提前想清楚的是前后端分离不是把代码拆开就完事了本地开发时前端跑在 5173 或 8080 这种端口后端跑在 8080 或 9090 这种端口天然存在两个问题需要处理一个是跨域另一个是接口地址的切换。理解了这两个核心痛点环境搭建其实就是在围绕它们做配置。跨域解决思路一般有两种后端开启 CORS跨域资源共享或者前端通过开发代理转发请求。我自己的实践经验是本地开发阶段优先用开发代理因为代理对代码几乎没有侵入只需要在前端构建工具的配置里加几行转发规则即可CORS 可以配合使用但一般放在生产环境由网关统一处理。这个选择后面会细讲。1.2 快速搭建环境需要准备的核心清单在动手之前先把你要准备的东西列清楚。一套基础的前后端分离开发环境一共四层东西缺一不可前端工程负责页面和交互。目前主流选型是 Vue 3 或 React配合 Vite 或 Webpack 构建。如果是新项目直接用 Vite 初始化最快。后端工程负责接口和业务。主流选型是 Spring Boot、Node.jsExpress/NestJS、PythonFastAPI/Django等。如果团队以 Java 为主Spring Boot 是默认选项。数据库与中间件根据业务需求选择常见的是 MySQL、PostgreSQL、Redis。如果只是本地联调可以先不接数据库用内存数据代替避免启动成本过高。开发代理与调试工具也就是前端工程里配置的代理规则、浏览器的开发者工具、接口调试工具。别小看这一层很多环境问题都出在代理配置不正确。另外还要说明一点快速搭建不等于不规划目录结构。我见过不少项目前后端代码放在同一个仓库里后端一个文件夹、前端一个文件夹这没问题只要目录边界清楚就行。但如果不做任何规划接口路径乱写、环境变量散落在各个配置文件里后面维护起来确实会崩溃。所以我会在讲解过程中把目录规划和命名规范也带出来这部分经验对新手特别有用。1.3 工具链选型为什么默认推荐 Vite Spring Boot环境搭建之所以要聊工具选型是因为工具决定了你后面要做多少配置工作。现在前端初始化新项目我基本只会推荐 Vite。它的启动速度比 Webpack 快太多本地开发时几乎是秒开HMR热更新的体验也好得多。Vite 内部默认集成了开发服务器自带一个 proxy 配置项可以直接把/api开头的请求转发到后端地址这个能力正是前后端分离联调需要的。后端这边如果是 Java 体系Spring Boot 依然是目前中小企业项目里最常见的方案。它内置了 Tomcat起步依赖很完善只需要加一个 Web 依赖就能跑起接口服务。虽然很多开发者觉得 Spring Boot 项目初始化有点“重”但 Spring Initializr 可以帮你快速生成骨架选择依赖也很方便。如果你不想用 Java那么 Node.js 的 Express 是轻量方案里最合适的它和前端技术栈统一上手成本低做本地 Mock 服务特别方便。我要强调的是选型没有绝对的对错关键是这套组合能让你在十分钟内把两个服务跑起来。Vite 解决了前端启动慢的痛点Spring Boot 解决了后端配置繁琐的痛点两者配合再加上一个清晰的代理配置基本就是现在最稳妥的“快速搭建”组合了。2. 前端工程搭建从初始化到可联调2.1 初始化 Vue 3 Vite 项目的具体操作前端工程初始化我习惯用包管理器直接执行创建命令。以 Vue 3 Vite 为例在终端里运行# 使用 npm 创建项目 npm create vitelatest frontend -- --template vue # 进入项目目录并安装依赖 cd frontend npm install这条命令会生成一个标准的 Vue 3 工程包含package.json、vite.config.js、src目录等。如果模板下载慢可以把 npm 源切换到国内镜像再执行。装完依赖后先跑一次npm run dev确认前端服务能正常启动默认端口一般是 5173也可以自己在vite.config.js里修改。需要注意一个细节现代 Node.js 版本和 Vite 版本之间是有兼容性要求的。Vite 4 需要 Node.js 14.18 以上Vite 5 则要求 Node.js 18 以上。所以初始化之前最好先用node -v确认一下版本不然一上来就报语法错误容易误判是配置问题其实是环境版本问题。我在排错部分会单独讲这个坑。依赖安装完成后项目结构大概是这样的frontend/ ├── node_modules/ ├── public/ ├── src/ │ ├── assets/ │ ├── components/ │ ├── App.vue │ └── main.js ├── index.html ├── package.json └── vite.config.js这个结构就是 Vite 默认给出的标准结构对于大多数中大型项目来说还不够但作为起步已经够用了。你可以在src下继续划分子目录api放接口封装、router放路由配置、store放状态管理、views放页面组件。这样目录清晰后面加功能也好找。2.2 项目目录结构与代码组织建议很多初学者容易忽略目录规划觉得工程能跑起来就行。但前后端分离项目只要稍微扩大一点接口文件、页面组件、工具函数就会铺得到处都是。我常用的前端目录结构是src/ ├── api/ # 接口请求的统一封装 │ ├── modules/ # 按业务模块拆分接口 │ ├── request.js # axios/fetch 实例封装 │ └── config.js # 接口地址定义 ├── assets/ # 静态资源 ├── components/ # 通用组件 ├── router/ # 路由配置 ├── store/ # 状态管理 ├── views/ # 页面组件 ├── utils/ # 工具函数 ├── hooks/ # 组合式函数 ├── App.vue └── main.js接口相关的代码一定要单独放在一个目录里不要在每个页面组件里直接写fetch(/api/xxx)那样改一个后端地址就要翻遍全项目。统一封装的好处是你可以在request.js里设置基础路径、超时时间、拦截器以后做权限验证、错误提示、Token 刷新都只需要改这一处。如果团队有后端同学前期最好就对接口路径的规范达成一致比如所有接口统一以/api开头业务模块通过路径首段区分例如/api/user/list、/api/order/detail。这样的约定能让代理配置只写一次避免每个接口单独处理路径。2.3 封装请求层并验证基础连通性前端工程跑起来后下一步是封装一个最基础的请求层用来验证前端是否能正常调用后端接口。我拿 axios 举例安装它npm install axios然后在src/api/request.js里写一段基础的封装import axios from axios // 创建请求实例 const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器可以在这里附加 Token request.interceptors.request.use( (config) { return config }, (error) Promise.reject(error) ) // 响应拦截器统一处理错误码 request.interceptors.response.use( (response) { return response.data }, (error) { // 统一提示错误信息 return Promise.reject(error) } ) export default request注意这里baseURL写的是/api不是完整的后端地址。原因是我们打算在本地开发阶段通过 Vite 的代理把/api开头的请求转发到后端服务这样前端代码里不出现具体 IP 和端口将来部署时也只需要调整代理层代码不用改。写完之后可以随便调一个后端接口验证连通性如果后端接口还没写好先用后面的后端口试验。这里的关键点不是接口逻辑有多复杂而是要把这条请求链路打通确保请求能从浏览器发出、通过代理到达后端、再带着数据返回。只要这条路通了后面写业务代码就是顺水推舟的事。3. 后端工程搭建与本地服务启动3.1 快速创建一个 Spring Boot 工程后端这边我直接讲 Spring Boot 的快速创建方法。最省事的方式是去 Spring Initializr 网站生成一个基础工程选好构建工具Maven 或 Gradle、语言Java、Spring Boot 版本然后勾选依赖Spring Web 是必须的如果要用数据库根据情况加 Spring Data JPA、MyBatis 或 MySQL Driver如果不是马上接数据库先只勾一个 Spring Web 就够。生成之后把压缩包解压到本地的backend文件夹用 IDE 打开。首次加载依赖会花几分钟Maven 会把所有依赖包下载到本地仓库。如果下载很慢同样需要配置国内镜像源在pom.xml里添加镜像配置或者修改本地的settings.xml。这一步往往被新手忽略等看到 “Could not transfer artifact” 这类报错才发现是网络问题。工程创建之后目录结构是标准的 Maven 结构backend/ ├── pom.xml └── src/main/ ├── java/com/example/demo/ │ ├── DemoApplication.java │ └── ... └── resources/ └── application.properties我先会在application.properties里把服务端口固定下来server.port8080然后写一个最简的接口确认服务能正常启动// 写一个 Controller 用于验证 RestController RequestMapping(/api/demo) public class DemoController { GetMapping(/hello) public MapString, String hello() { MapString, String result new HashMap(); result.put(message, backend is running); return result; } }启动主类后浏览器访问http://localhost:8080/api/demo/hello能看到返回的 JSON 数据就说明后端服务已经起来了。3.2 配置跨域 CORS 的两种方式后端服务跑通之后马上会遇到的就是跨域问题。虽然我前面说本地开发优先用前端代理但偶尔也会有需求直接用 axios 的完整地址请求后端或者后端需要支持其他端的直接访问这时候后端设置 CORS 就很有必要了。Spring Boot 里启用全局 CORS 配置最简洁的方式是在配置类里写一个WebMvcConfigurer的实现Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }三段解释很重要第一addMapping(/api/**)指定哪些路径需要支持跨域第二allowedOriginPatterns(*)表示允许任何来源访问本地联调时没问题但生产环境一定要收窄改成具体的域名列表第三allowCredentials(true)表示允许携带 Cookie但注意如果设置了这个allowedOriginPatterns就不能用*必须指定明确的源否则浏览器会拒绝请求。跨域问题本质上就是浏览器同源策略的限制服务器收到的请求其实已经到达了后端但因为响应头里没有带上允许跨域的字段所以浏览器把响应拦截了。理解了这一点排查跨域问题就不会被“页面报错”误导了先看后端日志如果后端没有收到请求是代理或网络问题如果后端收到了请求但浏览器报跨域才是 CORS 配置问题。3.3 轻量后端方案用 Node.js 做接口服务如果团队不打算用 Java或者只是想快速搭一个 Mock 接口做前端联调我建议直接用 Node.js Express。这个方案的好处是前端开发者本身就熟悉 JavaScript不需要额外学习 Java 语法而且启动速度比 Spring Boot 快很多。创建一个 Node.js 后端服务的步骤mkdir backend-node cd backend-node npm init -y npm install express然后创建一个index.jsconst express require(express) const app express() app.use(express.json()) app.get(/api/demo/hello, (req, res) { res.json({ message: backend is running }) }) app.listen(8080, () { console.log(Server running at http://localhost:8080) })启动node index.js一个最简单的后端服务就完成了。Express 没有自动热更新代码改了需要手动重启可以装一个nodemon做自动重启或者用 Node.js 18 以上自带的--watch参数node --watch index.js。我自己做本地 Mock 时经常用这个方案因为它可以零成本模拟后端的各种返回状态比如随机延迟、错误码、分页数据等。等后续真实后端开发完成前端只需要把代理目标地址换成真实服务的地址就行请求层代码完全不用改。4. 前后端联调核心机制与配置实践4.1 开发代理的原理与配置方法前端跑在 5173 端口后端跑在 8080 端口浏览器直接发请求到 8080 一定会跨域。开发代理的核心思路是让前端开发服务器也就是 Vite 启动的那个服务帮我们转发请求浏览器只和前端服务器通信前端服务器作为中间人再去请求后端再把结果返回给浏览器。因为服务器之间的请求不受浏览器同源策略限制所以跨域问题就消失了。Vite 的代理配置在vite.config.js里示例写法如下export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })target就是后端服务的地址changeOrigin: true的作用是把请求头里的Host字段改成目标地址的域名避免后端根据 Host 做判断时误认为请求来自不认识的来源。配置完成后前端代码里发请求到/api/demo/helloVite 会自动转发到http://localhost:8080/api/demo/hello。这里推荐把代理目标和本地后端地址集中在项目的.env或者配置文件里不要散落在多个地方。后面如果有同事加入开发他只需要改一个配置文件就能把请求指向自己的本地后端这是团队协作里的一个小细节但体验差别很大。4.2 代理的路径重写规则某些场景下后端接口路径和前端请求路径不是一致的需要做路径重写。例如后端实际接口是/api/v1/demo/hello而前端请求的路径是/api/demo/hello就可以通过rewrite来转换proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, /api/v1) } }这个配置的意思是把请求路径开头的/api替换成/api/v1然后再转发给后端。需要说明的是增加 rewrite 后容易出现路径理解混乱所以更推荐的做法是前端和后端统一使用一套路径规范不在本地代理里做大量的路径改写。代理层越简单越好太多的改写规则会让排查请求路径问题变得很头疼。还有一个经常踩的坑代理配置里的路径前缀必须和前端请求的路径前缀完全匹配。比如前端请求的是/api/user/list代理配置的匹配路径是/api没问题但如果你不小心把代理匹配路径写成/api/带斜杠或者/apiUser这种就可能出现匹配不上的情况。遇到代理不生效的时候先看路径前缀是不是配置错了。4.3 生产环境下的接口前缀与部署衔接本地开发可以通过代理解决跨域但代码一旦部署到测试环境或生产环境情况就变了。通常生产环境会有单独的网关或 Nginx 做反向代理把/api开头的请求转发到后端服务的集群地址。所以前端代码里保持/api前缀请求是最稳妥的因为本地开发时 Vite 代理可以处理这个前缀生产环境 Nginx 也可以按这个前缀做路由。如果你使用了 Nginx常见的配置片段是location /api/ { proxy_pass http://backend-service:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }Nginx 按路径前缀把请求转发给后端服务和 Vite 代理做的事情本质上是一样的。这也就是为什么我在前后端分离环境里一直强调让前端代码的请求路径以/api开头是最省事的做法。以后不管是本地调试、测试环境还是上生产路径规则都不用改只需要调整对应的代理层目标地址。5. 联调与 Mock 数据的落地策略5.1 后端尚未完成时前端如何先跑起来前后端分离开发经常遇到一种情况后端接口还在开发中前端已经要把页面写完了。这时候如果不做任何处理前端页面会因为请求失败而无法完整展示开发效率会严重受影响。我常用的做法是搭建一层 Mock 数据层。Mock 数据不是把假数据硬编码在前端代码里而是要尽量模拟真实的接口行为。有两种主流方式第一种是用 Node.js 写一个 Mock 服务在本地启动一个假的接口服务返回预设的 JSON 数据第二种是使用现成的 Mock 工具比如初始化项目时本地集成vite-plugin-mock之类的插件在 Vite 启动时自动注册对应的接口。以 vite 的 mock 插件为例思路很简单在项目里创建一个mock目录定义接口和数据export default [ { url: /api/user/list, method: get, response: () { return { code: 0, data: [ { id: 1, name: 测试用户1 }, { id: 2, name: 测试用户2 } ] } } } ]这样前端启动开发服务器时请求/api/user/list就能直接拿到 Mock 数据不需要后端配合。等到后端接口真正完成只需要停掉 Mock把代理指到真实后端地址即可。重点是不变的部分是请求路径和数据结构前端代码不需要动。5.2 接口数据结构约定的常见模式做 Mock 数据时顺便把接口返回结构约定下来这是一个很值得投入的做法。我见过太多项目不同接口返回的数据结构五花八门有的成功直接返回数组有的返回对象里套一层data错误码有的是code字符串有的是status数字。团队的联调效率很大程度上取决于这一层约定。一个比较通用的接口返回范式是{ code: 0, message: success, data: { list: [], total: 0 } }其中code表示业务状态码0表示成功非0表示业务异常message是给用户或开发者看的提示信息data是真正的业务数据。所有接口都遵守这个模式之后前端的响应拦截器才可以把对code的判断集中处理而不需要在每个页面里重复判断。如果前后端就这个结构达成一致后面的 Mock、联调、异常处理都会省心很多。5.3 前端请求层的拦截器与统一错误处理前面提到响应拦截器这里展开讲讲为什么需要统一错误处理。一个实际项目里接口可能出现的异常类型很多网络超时、后端 500、业务异常、登录失效。如果每个接口都单独处理这些情况代码会非常冗余。回到request.js里的响应拦截器我通常会在里面做三件事对 HTTP 层错误统一弹出提示例如HTTP 500时提示“服务器异常请稍后重试”。对业务层code非 0 的情况统一处理比如code等于某个值时跳转登录页其他情况展示错误信息。对需要携带 Token 的请求在请求拦截器里加上认证信息。这套逻辑写一次全项目受益。很多环境搭建完之后开发效率不高原因往往不是环境本身的问题而是接口层的封装没做好导致每个页面都在处理重复的异常逻辑。所以环境搭建阶段把请求层封装好比急着写业务页面更重要。6. 常见问题排查与避坑记录6.1 端口占用问题搭环境时最常见的一个坑就是端口被占用。Vite 默认端口 5173Spring Boot 默认 8080如果本机上已经跑了其他服务启动会直接报错。排查步骤很简单先看报错信息里有没有port或EADDRINUSE关键字然后用命令查端口占用情况。macOS/Linux 可以用lsof -i :8080Windows 可以用netstat -ano | findstr :8080查到占用进程的 PID 之后确认是不是可结束的进程再决定 kill 掉还是换端口。我个人的习惯是不要一股脑 kill有些人本机跑着数据库或其他开发工具完全没必要为了一个临时服务把别的服务干掉。更稳妥的方法是给当前服务换个端口例如 Spring Boot 改server.port8081Vite 改server.port5174。6.2 代理不生效与请求 404 的排查思路代理配置好了但请求还是 404这是另一个高频问题。遇到这种情况先把浏览器开发者工具的 Network 面板打开看请求的 URL 到底发到了哪里。如果请求 URL 是http://localhost:5173/api/demo/hello说明走的是代理如果 URL 是http://localhost:8080/api/demo/hello说明代理没生效大概率是代码或配置没重启。注意修改vite.config.js之后Vite 不需要手动重启但有时插件缓存会导致配置没生效稳妥的做法是重新执行npm run dev。还有一种隐蔽的情况请求发到了 5173代理也配置了但还是 404。这时候检查后端服务的路径是否和代理转发的路径匹配。比如后端接口实际路径是/api/demo/hello代理转发后变成/api/v1/demo/hello后端没有这个路径自然就是 404。所以排查一个 404要同时看三个环节前端的请求路径、代理的转发规则、后端的路由路径。6.3 Node 版本与依赖版本兼容问题Node 和构建工具的版本兼容问题也是环境搭建的重灾区。Vite 不同大版本对 Node 版本的要求不同如果你电脑上装的是老版本 Node跑最新的 Vite 模板就会报各种意想不到的语法错误比如 “SyntaxError: Unexpected token ?”。遇到这类报错先不要怀疑代码写错了先执行node -v看看版本然后查一下当前项目使用的构建工具要求什么 Node 版本。如果版本不符合可以用 Node 版本管理工具比如nvm或fnm这类工具切换到合适的版本。这里要提醒的是不要为了迁就本机环境去降低项目依赖的版本而是要让本机环境符合项目要求。团队合作时最好在项目的package.json里声明engines字段同时提供一份.nvmrc文件指定 Node 版本新同事拉代码后执行nvm use就能切到一致的环境省掉很多“在我电脑上明明是好的”这种问题。6.4 依赖安装慢与安装失败的处理依赖安装慢是很多开发者刚接触前端项目时的噩梦。解决方案主要是配置镜像源不用多解释。我使用的做法是在项目根目录创建.npmrc文件写入镜像配置这样项目内所有人共享避免每个人各自去改全局配置。.npmrc内容很简单registryhttps://registry.npmmirror.com另外如果npm install装到一半报错不要直接重复执行同一命令很可能是缓存出了问题。先清理缓存npm cache clean --force或者删除node_modules和package-lock.json后重新安装。记住一条原则不要手动去改node_modules里的文件也不要随意升级依赖大版本否则很容易出现“本地好好的提交代码后同事一拉就报错”的情况。6.5 环境变量与敏感信息管理最后说说环境变量。前端工程里接口地址、上传地址这类配置不应该硬编码在源码里而是放到环境变量文件中。Vite 项目支持.env文件常见的用法是在项目根目录创建.env.development和.env.production# .env.development VITE_API_BASE/api # .env.production VITE_API_BASE/api注意只有以VITE_开头的变量才会被 Vite 注入到前端代码中这是 Vite 刻意设计的机制避免把敏感的环境变量暴露给前端。如果你需要把某个接口密钥放到前端那它本质上就是会被用户看到的敏感信息应该放在后端而不是前端环境变量里。这一点在前后端分离架构里尤其重要——前端的任何变量都能被浏览器端用户查看真正的敏感操作一定要放到后端。7. 个人实操体会与效率建议我自己从最早写前后端不分离的老项目过渡到前后端分离架构最明显的感受是环境搭建其实只占很小的比例真正决定开发效率的是团队的约定和封装的完善程度。有几条经验每次搭环境都会用上。第一本地开发一定要把代理配好而不是图省事在后端直接放开所有跨域。代理是本地开发环境的事CORS 是架构层面的策略两者混为一谈后面会出问题。第二接口路径统一加/api前缀前后端都遵守会让生产环境 Nginx 的配置变得很简单。第三从项目第一天就把请求层、环境变量、Mock 数据的位置规划好后面加功能会顺畅得多。如果你现在正要给新项目搭一套前后端分离环境我的建议是不要一上来就追求“大而全”的工程化配置先跑通一个最简单的请求链路再做权限验证、多环境配置这些增强。等这条链路稳定了再逐步加上 ESLint、Prettier、单元测试、自动化部署这些东西。快速搭建的核心是“快”但快不是靠少写配置而是靠合理的默认值和清晰的约定让所有人都能按照同一套流程工作。希望这篇分享能帮你把环境搭建这个过程从“玄学”变成“按步操作就能成功”的例行工作。