跳转到内容

Next.js SSG + Django 静态服务混合部署

Next.js SSG + Django 静态服务混合部署

学习目标

理解 Khoj 如何将 Next.js 构建的静态文件通过 Django 的静态文件系统服务,以及为什么保留 Jinja2 遗留模板。

项目实践

构建与部署流水线

前端技术选型

技术用途
Next.jsReact 框架,SSG 静态导出
Bun包管理器 + 构建运行时(替代 Node.js)
Radix UI无样式基础组件库
Tailwind CSS原子化 CSS 框架
Phosphor Icons图标库
React Hook Form + Zod表单验证
Excalidraw手绘风格白板

为什么选 Next.js SSG 而非 CSR SPA?

维度SSGCSR
首屏加载快(预渲染 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/khoj
COPY 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 的设计优先考虑自部署的简便性,而非云规模的性能。

参考来源