SPA + SSR 双模式:Vite 嵌入 Next.js 的架构
学习目标
理解 LobeHub 如何用 Vite SPA + Next.js SSR 构建双模式前端,包括多入口构建、Debug Proxy 开发模式和路线注册同步机制。
前置知识
本章无通用知识前置,直接聚焦 LobeHub 的工程实践。
项目实践
双模式架构
多入口构建
Vite 配置了 4 个独立入口,每个入口生成独立的 SPA 应用:
// 伪代码:Vite 多入口配置export default defineConfig({ build: { rollupOptions: { input: { web: "src/spa/entry.web.tsx", mobile: "src/spa/entry.mobile.tsx", desktop: "src/spa/entry.desktop.tsx", popup: "src/spa/entry.popup.tsx", }, }, },});每个入口使用 react-router-dom 配置独立的路线树。
Roots vs Features 路由约定
LobeHub 使用 roots vs features 的分离模式:
src/routes/(roots):只放路线段文件(_layout/index.tsx、index.tsx、[id]/index.tsx),必须薄,只从@/features/*导入和组合布局src/features/:业务组件按领域组织(如Pages、PageEditor、Home),包含布局块、hooks 和领域特定 UI
禁止行为:不要在 src/routes/ 内新建 features/ 文件夹。
关键陷阱:路线注册同步
Desktop 路线树必须在两个配置文件中注册:
src/spa/router/desktopRouter.config.tsxsrc/spa/router/desktopRouter.config.desktop.tsx如果只更新其中一个:会导致白屏,因为另一个构建路径找不到该路线。
LobeHub 用自动化测试保护这个不变量:
desktopRouter.sync.test.tsx该测试验证两个配置文件的路线树完全同步。
Debug Proxy 开发模式
运行 bun run dev:spa 后,终端会打印 Debug Proxy URL:
Debug Proxy: https://app.lobehub.com/_dangerous_local_dev_proxy?debug-host=http%3A%2F%2Flocalhost%3A9876这个 URL 加载生产后端(app.lobehub.com)的 SPA 页面,但将 Vite dev server 作为 HMR 源。开发者可以在本地修改代码并看到实时更新,同时使用真实的服务器配置。
构建流水线
bun run build # 完整构建 → bun run build:spa # Vite 构建 SPA → bun run build:spa:copy # 复制 SPA 产物到 Next.js 静态目录 → bun run build:next # Next.js SSR 构建SPA 模板生成:build:spa:copy 不仅复制 SPA 产物,还生成 HTML 模板供 Next.js SSR 页面嵌入。
问题与规避
| 问题 | 场景 | 规避策略 |
|---|---|---|
| 白屏 | 只更新了一个 desktopRouter config | 运行 desktopRouter.sync.test.tsx 检测 |
| SPA 模板过期 | 复制步骤未执行 | 构建脚本强制执行 build:spa:copy |
| Mobile HMR 不工作 | 使用了错误的 dev 命令 | 使用 dev:spa:mobile 而非 dev:spa |
| Next.js 缓存导致过时 UI | 开发中切换分支 | rm -rf .next 清理缓存 |
设计取舍
为什么用 SPA + SSR 双模式而非纯 SSR?
纯 SSR 的每次页面切换都需要服务器往返,延迟高。SPA 通过客户端路由实现即时切换,支持 PWA 离线使用。
代价:两套构建系统维护成本高,SPA 模板需要复制到 Next.js 静态目录。
为什么用 Roots vs Features 分离?
传统的 Next.js 项目中,业务逻辑和页面组件混在 app/ 或 pages/ 目录下,随时间推移变得难以维护。Roots vs Features 分离确保:
- 路线文件只负责布局组合
- 业务逻辑在 features 中按领域组织
- 新成员能快速定位代码
代价:增加了一层抽象,新成员需要理解约定。
参考来源
- LobeHub Vite 配置 —
vite.config.ts - LobeHub SPA 入口 —
src/spa/entry.*.tsx - LobeHub SPA 路由 —
src/spa/router/ - LobeHub 路线同步测试 —
src/spa/router/desktopRouter.sync.test.tsx