普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月19日首页

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

作者 码云之上
2026年3月19日 16:27

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

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

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

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

前端仓库 (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 终于能看到全貌了

最终的项目结构长这样:

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 的核心结构:

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 了。

二、本地开发与热更新

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

{
  "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 的集成是通过中间件实现的:

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,并且在启动时强制校验必需变量:

// 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. 前端构建

npm run build:client

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

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

// 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。生产环境启动命令:

npm start

五、服务日志

日志系统使用 Winston + DailyRotateFile

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 请求日志也做了定制,记录请求耗时、响应大小等关键信息:

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

六、Docker 部署

项目提供了 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,镜像体积小,启动快。

项目设计文档

整体架构

┌─────────────────────────────────────────────────────────────┐
│                    全栈 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. 启动开发环境

    npm run dev
    
  2. 添加新页面

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

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

    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

Clipboard_Screenshot_1773908826.png

❌
❌