从 SPA 到全栈:AI 时代的前端架构升级实践

Vibe Coding 浪潮席卷而来的今天,AI 辅助开发已经不再是新鲜事。笔者所在团队维护着一个内部业务系统(技术栈:React 18 + Vite + React Router),前端独立部署,后端由 Java 同学负责。这套架构运行了两年多,一直相安无事。

直到有一天,组织架构调整,后端同学被调去支援其他项目(AI创新项目),老板拍板:前端同学顶上后端的活儿。 好家伙,说得轻巧,前后端代码都不在一个仓库,让前端同学怎么顶?

现状分析:前后端分离之痛

先来看看原来的项目结构:

scss 复制代码
前端仓库 (frontend-repo)
├── src/
│   ├── pages/
│   ├── components/
│   └── utils/
└── package.json

后端仓库 (backend-repo)
├── src/main/java/
│   ├── controller/
│   ├── service/
│   └── mapper/
└── pom.xml

看起来很标准对吧?但问题来了:

痛点一:AI 辅助开发的先天不足

用过 CursorCodeBuddy 这类 AI 编程工具的同学都知道,AI 需要理解上下文才能给出靠谱的建议。当你让 AI 帮你实现一个完整功能时,它需要同时看到:

  • 前端的组件结构和 API 调用
  • 后端的接口定义和业务逻辑
  • 数据库的表结构

但是,前后端分离的架构下,AI 只能看到半边天。让它帮你写个表单提交功能,它只能帮你写前端调用,后端接口得你自己跑到另一个仓库里去补。这就像让一个人蒙着一只眼睛打乒乓球------不是不能打,就是费劲。

痛点二:前端同学的上手成本

前端同学接手后端代码,第一反应是:这 Spring Boot 的注解也太多了吧?@RestController@Autowired@Transactional... 光是理解这些就得花不少时间。

更要命的是,本地调试还得:

  1. 先启动 MySQL
  2. 再启动 Redis
  3. 配置一堆环境变量
  4. 最后启动 Spring Boot

前端同学看到这套流程,内心 OS:我就改个接口返回值,至于吗?

痛点三:联调效率低下

前后端分离开发时,联调是个老大难问题:

  • 前端:接口好了吗?
  • 后端:好了,你试试
  • 前端:报错了,返回格式不对
  • 后端:我看看... 改好了
  • 前端:还是不行,字段名不一致
  • (循环往复...)

来回切换仓库、对着接口文档核对字段,这种低效的协作模式在 AI 时代显得尤为刺眼。

破局:全栈架构升级

经过一番调研,笔者决定将项目升级为 Express + React + Vite 的全栈架构。为什么选这套?

  1. Express:轻量、灵活,前端同学学习成本低,写 JavaScript 就能搞后端
  2. TypeScript 全栈:前后端共享类型定义,编译期就能发现问题
  3. Vite:开发体验一流,HMR 快得飞起
  4. 单一仓库:AI 终于能看到全貌了

最终的项目结构长这样:

bash 复制代码
fullstack-web-app/
├── client/                 # 前端代码
│   ├── pages/             # 页面组件
│   ├── components/        # 可复用组件
│   ├── hooks/             # React Hooks
│   ├── utils/             # 工具函数
│   ├── App.tsx            # 根组件
│   └── main.tsx           # 前端入口
│
├── server/                 # 后端代码
│   ├── middleware/        # Express 中间件
│   ├── utils/             # 工具函数
│   └── server.ts          # 服务端入口
│
├── env.ts                  # 环境变量
├── package.json           # 统一依赖管理
└── tsconfig.json          # TypeScript 配置

一眼望去,前端后端都在这儿了,AI 表示很满意。更重要的是前端写 nodejs 天然无障碍!

技术选型详解

一、后端框架:Express

为什么不用 NestJS 或者 Koa

NestJS 功能确实强大,但那套装饰器和依赖注入的玩法,跟 Spring Boot 有异曲同工之妙。前端同学刚从 Java 的"注解地狱"逃出来,别又给整进去了。

Koa 挺好,但生态不如 Express 丰富。选 Express 就图一个:中间件多、文档全、前端同学一看就懂

服务端入口 server.ts 的核心结构:

typescript 复制代码
import express from "express";
import "express-async-errors";

export async function startup() {
  const app = express();

  // HTTP 日志(仅 API)
  app.use("/api", serveHttpLogger());
  
  // API 路由
  app.use("/api", serveApi());
  
  // 静态资源服务
  if (isProd) {
    app.use("/assets", serveAssets());
  }
  
  // 前端路由
  if (isProd || isDebug) {
    app.use(serveIndex());
  } else {
    // 开发模式:集成 Vite
    app.use("/", await serveClientVite());
  }
  
  // 全局错误处理
  app.use(serveErrorHandler());

  app.listen(port, () => {
    logger.info(`Server running on port ${port}`);
  });
}

express-async-errors 这个库必须夸一下,有了它,async/await 里的错误会自动被全局错误处理中间件捕获,再也不用写一堆 try-catch 了。

二、本地开发与热更新

开发体验是生产力的关键。这套架构的开发模式是这样的:

json 复制代码
{
  "scripts": {
    "dev": "cross-env NODE_ENV=local tsx watch --inspect=9442 server/server.ts"
  }
}

一条命令启动,背后做了这些事:

  1. tsx watch:监听 TypeScript 文件变化,服务端代码改了自动重启
  2. Vite Dev Server:前端代码改了,浏览器自动热更新(不刷新页面)
  3. 统一端口:前后端都走 3003 端口,不用配代理

Vite 的集成是通过中间件实现的:

typescript 复制代码
import { createServer, createViteRuntime } from "vite";

export async function serveClientVite() {
  const vite = await createServer({
    configFile: resolve(__dirname, "../client/vite.config.ts"),
    server: { middlewareMode: true },
    appType: "custom",
  });

  const router = Router();
  
  // Vite 中间件处理前端资源
  router.use(vite.middlewares);

  // 所有非 API 请求返回 index.html(SPA 路由支持)
  router.use("*", async (req, res, next) => {
    const url = req.originalUrl;
    let template = fs.readFileSync(
      resolve(__dirname, "../client/index-dev.html"),
      "utf-8"
    );
    template = await vite.transformIndexHtml(url, template);
    res.status(200).set({ "Content-Type": "text/html" }).end(template);
  });

  return router;
}

这套方案的好处是:

  • 前端同学还是熟悉的 Vite 开发体验
  • 不需要额外配置跨域代理
  • API 和页面请求走同一个端口,调试方便

三、环境隔离

环境管理是个容易被忽视但很重要的环节。笔者设计了三种环境:

环境 NODE_ENV 特点
本地开发 local Vite Dev Server,完整 HMR
联调测试 development 使用构建后的前端资源
生产环境 production 静态资源 + API 服务

环境变量管理使用 dotenv,并且在启动时强制校验必需变量:

typescript 复制代码
// env.ts
import "dotenv/config";

export const { NODE_ENV, PORT, DATA_DIR } = process.env;
export const DEV = NODE_ENV === "development";
export const LOCAL = NODE_ENV === "local";

// 启动校验
for (const [key, value] of Object.entries({ NODE_ENV, DATA_DIR })) {
  if (!value) {
    throw new Error(`请设置 ${key} 环境变量`);
  }
}

少了哪个环境变量,启动就报错,避免线上出问题了才发现配置没写。

四、构建流程

构建分两步:

1. 前端构建

bash 复制代码
npm run build:client

Vite 会把前端代码打包到 client/dist 目录,资源文件名带 hash,方便 CDN 缓存。

这里有个小细节,Vite 默认的 hash 算法生成的文件名可能包含 -,部分 CDN 对此支持不好。所以我自定义了 hash 算法:

typescript 复制代码
// vite.config.ts
function customMd5HashAlgorithm(data: Buffer): string {
  // 只使用十六进制字符,兼容 CDN
  return createHash("md5").update(data).digest("hex").slice(0, 8);
}

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        hashCharacters: customMd5HashAlgorithm,
      },
    },
  },
});

2. 后端部署

后端代码不需要编译,直接用 tsx 运行 TypeScript。生产环境启动命令:

bash 复制代码
npm start

五、服务日志

日志系统使用 Winston + DailyRotateFile

typescript 复制代码
const logger = winston.createLogger({
  level: LOG_LEVEL,
  format: winston.format.combine(
    winston.format.timestamp({
      format: () => dayjs().format("YYYY-MM-DD HH:mm:ss.SSS"),
    }),
    winston.format.json()
  ),
  transports: [
    // 控制台输出(带颜色)
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      ),
    }),
    // 文件输出(按小时轮转)
    new DailyRotateFile({
      dirname: LOG_DIR,
      filename: "app-%DATE%.log",
      datePattern: "YYYY-MM-DD-HH",
      maxSize: "100m",
      maxFiles: "7d",
    }),
  ],
});

HTTP 请求日志也做了定制,记录请求耗时、响应大小等关键信息:

typescript 复制代码
// 日志格式示例
{
  "timestamp": "2026-03-19 14:30:25.123",
  "level": "info",
  "method": "POST",
  "url": "/api/submit",
  "status": 200,
  "duration": "45ms",
  "responseSize": "1.2KB"
}

六、Docker 部署

项目提供了 Dockerfile,一键部署:

dockerfile 复制代码
FROM node:20-slim

# 时区设置
RUN rm -f /etc/localtime \
    && ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

WORKDIR /app
COPY . ./
ENV DATA_DIR=/app/data

RUN npm install --force --registry=https://registry.npmmirror.com

EXPOSE 3003
ENTRYPOINT ["npm", "run", "start"]

基于 node:20-slim,镜像体积小,启动快。

项目设计文档

整体架构

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                    全栈 Web 应用架构                          │
├─────────────────────────────────────────────────────────────┤
│  前端 (client/)                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  React 18 + TypeScript + React Router v7             │    │
│  │  Vite 7 构建 + Less 样式 + HMR 热更新                 │    │
│  └─────────────────────────────────────────────────────┘    │
│                           ↓ HTTP API                         │
│  后端 (server/)                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Express 4 + TypeScript + tsx 运行时                  │    │
│  │  Winston 日志 + 中间件链式处理                         │    │
│  └─────────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────────┤
│  开发工具: ESLint + Husky + lint-staged                      │
│  部署方式: Docker (node:20-slim)                             │
└─────────────────────────────────────────────────────────────┘

目录职责

目录 职责
client/pages/ 页面组件,一个文件对应一个路由
client/components/ 可复用 UI 组件
client/hooks/ 自定义 React Hooks
client/utils/ 前端工具函数(请求封装、XSS 过滤等)
server/middleware/ Express 中间件(路由、日志、错误处理等)
server/utils/ 后端工具函数(日志、格式化等)

开发流程

  1. 启动开发环境

    bash 复制代码
    npm run dev
  2. 添加新页面

    • client/pages/ 创建页面组件
    • client/App.tsx 添加路由
  3. 添加新接口

    • server/middleware/serveApi.ts 添加路由处理
    • 前端使用 client/utils/request.ts 调用
  4. 构建部署

    bash 复制代码
    npm run build:client  # 构建前端
    docker build -t my-app .  # 构建镜像

总结:AI 时代的全栈复兴

回到最初的问题:为什么要从 SPA 升级到全栈架构?
答案是:AI。

当 AI 成为开发的重要辅助工具时,代码的可理解性变得前所未有的重要。AI 需要看到完整的上下文才能给出高质量的建议:

  • 前端表单结构 → 后端参数校验
  • 数据库表结构 → API 返回格式
  • 业务逻辑 → 错误处理

前后端分离的架构,人为地把这些关联信息切割到了不同的仓库,AI 只能"盲人摸象"。

而全栈架构,把所有相关代码放在一个仓库里,AI 可以:

  • 根据后端接口自动生成前端调用代码
  • 根据数据库模型自动生成表单验证
  • 根据业务逻辑自动补全错误处理

这不是技术倒退,而是在新工具面前的架构演进。

所谓分久必合,合久必分

当然,全栈架构不是银弹。对于大型团队、复杂业务,微服务架构仍然有其价值。但对于中小型项目、快速迭代的业务,全栈架构 + AI 辅助开发,绝对是效率最优解。
笔者在此澄清一点,原有的 Java 后端服务仍然在线上提供支持,只是新增的功能涉及到后端开发时会改为 nodejs 实现 。

最后预测一下:在 AI 时代,全栈开发者 会越来越吃香。不是说要精通前后端所有技术,而是要有全局视角,能够在 AI 的辅助下,快速完成端到端的功能开发。

前端同学们,是时候往全栈方向卷一卷了~


本文项目源码已开源,欢迎 Star:fullstack-web-app

相关推荐
莪_幻尘3 小时前
你的 AI Skill 越多越蠢?Token 上下文爆炸的求生指南
前端·ai编程
lichenyang4534 小时前
从 has.echo 到异步 API 注册表:一次 ASCF API 回调不触发的排查复盘
前端
林瞅瞅4 小时前
Nuxt3 项目部署 Nginx 防盗链后特定 JS 文件 403 问题修复方案
前端
镜舟科技4 小时前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
轻口味4 小时前
别被模型宣传骗了,真实 Agent 任务一跑就知道
agent·ai编程
kyriewen4 小时前
别再每次都 Google 了:我整理了前端日常最常踩的 10 个 Git 坑,附速查表
前端·javascript·git
AlbertZein4 小时前
别被模型宣传骗了,真实 Agent 任务一跑就知道
aigc·openai·ai编程
一颗奇趣蛋4 小时前
Web 视频开发完全指南:从入门到精通
前端
非洲农业不发达5 小时前
windows终端体验大升级,让你拥有macos级别的美化
前端·后端