个人博客站点,Next.js 15 前端 + FastAPI 代理层 + Flask 后端。
npm install NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000 npm run dev
访问 http://localhost:30010。
pip install -r requirements.txt FLASK_BACKEND=http://127.0.0.1:5000 ZHIPU_API_KEY=your_key uvicorn proxy_server:app --reload
# 设置生产环境变量后构建
NEXT_PUBLIC_API_BASE_URL=https://your-api-server.com \
NEXT_PUBLIC_ADMIN_REGISTER_KEY=your_register_key \
npm run build
构建脚本会做两件事:next build 生成静态导出到 out/,然后复制到 .cnb.build/。
产物是纯静态 HTML+JS+CSS,不依赖 Node.js 运行时,随便找个 Web 服务器托管就行。
Nginx(推荐)
server { listen 80; server_name your-domain.com; root /var/www/.cnb.build; index index.html; # SPA fallback:前端路由全走 index.html location / { try_files $uri $uri/ /index.html; } # 静态资源缓存 location /_next/static/ { expires 1y; add_header Cache-Control "public, immutable"; } }
CNB 云原生流水线
项目已集成 Cloud Native Build 流水线,.cnb.build/ 就是产物目录。推送到对应分支后自动触发构建和部署。
其他托管方案
| 平台 | 说明 |
|---|---|
| Vercel | 直接导入仓库即可,注意在 Settings 里设 NEXT_PUBLIC_API_BASE_URL |
| GitHub Pages | 用 next build 后把 out/ 推到 gh-pages 分支 |
| CDN / OSS | 上传整个 .cnb.build/ 到对象存储,配置 SPA fallback |
| Docker | 基于 nginx 镜像,COPY 产物进去 |
关键点:不管用什么托管,动态路由(
/post/[id]、/tag/[tag]、/archive/...)必须配置 SPA fallback——所有未匹配的路径都返回index.html,否则刷新 404。
如果后端 API 不可用时需要离线降级展示内容:
npm run fetch:snapshot # 从 API 拉取最新数据到 public/snapshots/
npm run sync:snapshot # 同步更新已有快照
建议在 CI 流水线的构建阶段执行一次 fetch:snapshot,确保每次部署都带最新的数据备份。
代理层是 Python 服务,需要单独部署:
pip install -r requirements.txt
# 生产环境启动(用 gunicorn 或 uvicorn)
FLASK_BACKEND=http://your-flask-backend:5000 \
ZHIPU_API_KEY=your_key \
ALLOWED_ORIGINS=https://your-domain.com \
PROXY_TIMEOUT=30 \
uvicorn proxy_server:app --host 0.0.0.0 --port 8000
推荐用 systemd / supervisor 守护进程,或打包成 Docker 镜像:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY proxy_server.py . EXPOSE 8000 CMD ["uvicorn", "proxy_server:app", "--host", "0.0.0.0", "--port", "8000"]
| 阶段 | 变量 | 必填 |
|---|---|---|
| 前端构建 | NEXT_PUBLIC_API_BASE_URL | 是 |
| 前端构建 | NEXT_PUBLIC_ADMIN_REGISTER_KEY | 管理功能必填 |
| 代理运行 | FLASK_BACKEND | 是 |
| 代理运行 | ZHIPU_API_KEY | AI 功能必填 |
| 代理运行 | ALLOWED_ORIGINS | 生产环境建议设具体域名 |
| 代理运行 | PROXY_TIMEOUT | 否,默认 30s |
浏览器 → CDN / 静态托管 (HTML+JS+CSS) ↓ API 请求 FastAPI 代理 (proxy_server.py) ↙ ↘ Flask 后端 智谱 GLM-4 /api/v1/* /api/ai/v1/* /api/v0/glm/* (兼容旧路由)
前端完全静态导出(output: 'export'),不依赖 Node.js 运行时。API Key 全部由代理层持有,前端不暴露任何密钥。
| 层面 | 用了什么 |
|---|---|
| 框架 | Next.js 15 (App Router) + React 18 |
| 语言 | TypeScript (strict mode) |
| 样式 | Tailwind CSS v4 |
| 状态 | Zustand,持久化到 localStorage |
| 实时通信 | SSE(@microsoft/fetch-event-source) |
| AI 能力 | 智谱 GLM-4,流式对话 + 输入建议 |
| 代理 | FastAPI + httpx |
| 部署 | CNB 流水线 → 静态站 + 代理服务 |
构建时注入前端:
| 变量 | 干嘛的 | 默认值 |
|---|---|---|
NEXT_PUBLIC_API_BASE_URL | 代理服务地址 | "" |
NEXT_PUBLIC_ADMIN_REGISTER_KEY | 管理员注册密钥 | "" |
FastAPI 代理运行时:
| 变量 | 干嘛的 | 默认值 |
|---|---|---|
FLASK_BACKEND | Flask 后端地址 | http://127.0.0.1:5000 |
ZHIPU_API_KEY | 智谱 AI 密钥 | "" |
ALLOWED_ORIGINS | CORS 白名单,逗号分隔 | * |
PROXY_TIMEOUT | 代理超时秒数 | 30 |
/ 首页 /post/[id] 文章详情 /archive 归档总览 /archive/[year]/[month] 按年月归档 /tag/[tag] 标签聚合页 /category/[category] 分类聚合页 /glmchat AI 对话 /music 音乐播放器 /contact 联系页 /control/* 管理后台(需登录)
src/ ├── app/ 页面路由(App Router) │ ├── layout.tsx 根布局 │ ├── page.tsx 首页 │ ├── post/[id]/ 文章(客户端渲染) │ ├── tag/, archive/, category/ │ └── control/ 管理后台 ├── components/ 组件 │ ├── ui/ 通用 UI(通知、侧边栏) │ ├── MusicPlayer/ 播放器组件族 │ ├── Comment/ 评论组件 │ └── v1/ 聊天 UI(旧版) ├── hooks/ 自定义 Hooks │ ├── useAppStore.ts 全局状态(Zustand) │ ├── useSiteStats.ts 站点统计 │ ├── useAudioPlayer.ts 音频播放控制 │ └── useSnapshotData.ts SSG 数据降级策略 ├── lib/ 核心模块 │ ├── apiService.ts API 调用封装 │ ├── apiEndpoints.ts 端点定义(自动生成) │ ├── ai.ts AI 服务对接 │ ├── auth.ts 认证管理 │ ├── fetcher.ts HTTP 请求基础库 │ ├── crypto.ts 加密工具 │ ├── pluginManager.ts 插件系统 │ └── security.ts 安全相关 ├── services/ 业务服务 │ ├── statsService.ts 统计数据查询 │ └── statsStreamService.ts SSE 实时统计流 ├── types/ 类型定义 ├── config/ 配置(主题、封面图) └── utils/ 工具函数 proxy_server.py FastAPI 代理入口 requirements.txt Python 依赖 next.config.ts 静态导出配置
as any,API 响应统一在边界处做类型转换types/,重复逻辑提取为共享模块Apache-2.0