Next.js SSG + Django 静态服务混合部署
Next.js SSG + Django 静态服务混合部署
学习目标
理解 Khoj 如何将 Next.js 构建的静态文件通过 Django 的静态文件系统服务,以及为什么保留 Jinja2 遗留模板。
项目实践
构建与部署流水线
前端技术选型
| 技术 | 用途 |
|---|---|
| Next.js | React 框架,SSG 静态导出 |
| Bun | 包管理器 + 构建运行时(替代 Node.js) |
| Radix UI | 无样式基础组件库 |
| Tailwind CSS | 原子化 CSS 框架 |
| Phosphor Icons | 图标库 |
| React Hook Form + Zod | 表单验证 |
| Excalidraw | 手绘风格白板 |
为什么选 Next.js SSG 而非 CSR SPA?
| 维度 | SSG | CSR |
|---|---|---|
| 首屏加载 | 快(预渲染 HTML) | 慢(需要下载 JS 后渲染) |
| SEO | 友好(静态 HTML) | 差(需要 JS 渲染) |
| 部署 | 无需 Node.js 运行时 | 需要 Node.js 或 CDN |
| 动态功能 | 需要通过 API 获取数据 | 客户端直接调用 API |
Khoj 选择 SSG 的核心原因是:构建后无需 Node.js 运行时。Next.js 的 next export 将所有页面生成为纯静态 HTML/CSS/JS 文件,Django 只需通过 StaticFiles 服务这些文件即可。这意味着整个部署只需要一个 uvicorn 进程,不需要同时运行 Node.js 和 Python 两个服务。
Jinja2 遗留模板共存
Khoj 在 src/khoj/interface/web/ 中保留了一组 Jinja2 模板:
src/khoj/interface/web/├── home/│ ├── css/│ └── index.html # 旧版首页├── base_config.html # 配置页面基础模板├── error.html # 错误页面└── .well-known/ # WebFinger 等这些模板服务于设置页面、配置向导等管理功能。不迁移到 Next.js 的原因:
- 这些页面访问量低,迁移投入产出比不高
- Jinja2 模板直接由 Django 渲染,无需构建步骤
- 保持与 Django Admin 的一致性
Docker 构建中的前端阶段
# Stage 2: 构建 Next.js 前端FROM oven/bun:1-alpine AS web-app
WORKDIR /home/khojCOPY src/interface/web/package.json src/interface/web/bun.lock ./RUN bun install --frozen-lockfile
COPY src/interface/web/ ./RUN bun run build && bun run export# 产出:out/ 目录选择 Bun 而非 npm/yarn 的原因:
- 构建速度更快(Bun 的 JavaScript 引擎比 V8 快)
- 项目已经使用 uv 管理 Python 包,使用 Bun 统一管理 JS 包
- Docker 镜像更小(alpine 基础镜像)
问题与规避
| 问题 | 影响 | 规避策略 |
|---|---|---|
| Next.js SSG 不支持 SSR | 无法在服务端动态渲染页面 | 所有动态数据通过客户端 fetch API 获取 |
next export 废弃 | Next.js 14+ 不推荐 export | 使用 output: 'export' 配置替代 |
| 静态文件路径对齐 | Docker 构建中复制到 Django 静态目录时路径可能不一致 | 构建脚本中硬编码目标路径 |
| Bun 版本兼容性 | 某些 npm 包可能不兼容 Bun | 使用 --frozen-lockfile 确保依赖版本一致 |
设计取舍
为什么不让 Next.js 独立运行(而非静态导出)?
Next.js 独立运行需要 Node.js 进程,意味着部署时需要同时运行 uvicorn(Python)和 next(Node.js)两个进程。这不仅增加了部署复杂度,还需要处理两个进程之间的端口分配和反向代理配置。静态导出后,Django 一个进程搞定所有——HTTP 路由和静态文件服务。
为什么不在构建后直接复制到 CDN?
静态文件通过 Django collectstatic 收集后,可以在生产环境中配置 CloudFront/Cloudflare 等 CDN 加速。但在自部署场景中,直接从本地文件系统服务静态文件是最简单的方式。Khoj 的设计优先考虑自部署的简便性,而非云规模的性能。