# React + Go 内容平台技术选型

> 状态：提案
>
> 更新时间：2026-07-19
>
> 适用范围：SuBlog 从 Markdown 静态博客演进为可运营的内容系统。

## 1. 目标与边界

### 目标

- 提供公开的文章阅读、搜索、归档、标签、系列、SEO 和评论体验。
- 提供受权限控制的运营后台，支持文章草稿、发布、定时发布、修订、媒体上传和用户管理。
- 保留当前博客的视觉识别：技术图标环绕头像、主题切换动画、轻量内容阅读体验。
- 将业务规则、鉴权和数据一致性收敛到 Go 服务，前端不持有业务写入逻辑。

### 不在第一阶段解决

- 多租户、计费、复杂工作流和实时协同编辑。
- 微服务、消息队列和 gRPC。
- 直接把 Nuxt/Vue 组件转换为 React 组件。

首版采用模块化单体：一个 Next.js 应用、一个 Go API、一个 PostgreSQL 实例和一个 S3 兼容对象存储。先保证内容链路可靠，再按真实运营需求拆分。

## 2. 结论

采用以下组合：

| 层级 | 选型 | 决策 |
| --- | --- | --- |
| Web 前端 | Next.js App Router + React + TypeScript | 采用 |
| 样式与基础组件 | Tailwind CSS + shadcn/ui | 采用 |
| 视觉动效 | Magic UI | 采用，仅用于体验增强 |
| 后台服务 | Go + Gin | 采用 |
| 数据库 | PostgreSQL | 采用 |
| 媒体存储 | S3 协议对象存储，开发环境使用 MinIO | 采用 |
| 内容编辑器 | 首版 Markdown；富文本需求明确后引入 Tiptap | 延后 |
| API 契约 | OpenAPI 3.1 + TypeScript 客户端生成 | 采用 |
| 服务端状态 | TanStack Query，仅用于后台的客户端交互 | 采用 |

Next.js 的 App Router 默认使用服务端组件，适合将公开文章页在服务端从 Go API 获取数据后输出 HTML，同时把编辑器、筛选和管理表单限制在客户端组件中。[Next.js Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components)

shadcn/ui 的组件代码进入项目而非被黑盒依赖包封装，适合作为长期维护的后台设计系统；Magic UI 则只用于图标云、主题切换等差异化效果，不作为后台基础组件。[shadcn/ui Introduction](https://ui.shadcn.com/docs) [Magic UI Icon Cloud](https://magicui.design/docs/components/icon-cloud)

## 3. 总体架构

```mermaid
flowchart LR
  Visitor["读者"] --> Web["Next.js Web\n公开站点与运营后台"]
  Operator["运营人员"] --> Web
  Web -->|"SSR / HTTPS API"| API["Go API\n鉴权与业务规则"]
  API --> DB[("PostgreSQL")]
  API --> Storage[("S3 / MinIO\n媒体资源")]
  API --> Events["内容发布事件"]
  Events --> Revalidate["Next.js 缓存失效端点"]
  Revalidate --> Web
```

生产环境使用同一主域名，反向代理按路径分流：

```text
https://example.com/        -> Next.js
https://example.com/admin/  -> Next.js
https://example.com/api/v1/ -> Go API
```

这样浏览器访问 API 不需要 CORS，Go 可使用 `HttpOnly`、`Secure`、`SameSite=Lax` 的会话 Cookie。Next.js 仅负责渲染、路由和 API 调用适配；文章发布、权限判断、文件签名和数据写入只能由 Go 服务完成。

## 4. React 前端设计

### 4.1 框架职责

| 区域 | 渲染方式 | 数据方式 | 说明 |
| --- | --- | --- | --- |
| 首页、文章页、归档、标签、系列 | Server Components 优先 | Next 服务端请求 Go API | SEO、首屏和分享信息优先 |
| 搜索、评论、主题切换、头像图标云 | Client Components | 按需调用 API | 仅加载必需的浏览器逻辑 |
| `/admin` | Client Components 为主 | TanStack Query 调用 Go API | 表格、筛选、编辑和乐观更新 |
| 登录页 | Server + Client Components | Go 会话 API | 不在浏览器保存访问令牌 |

TanStack Query 管理后台的远程数据缓存、重新获取和写入后的失效；公开页面不为此引入客户端状态层，直接使用 Next 的服务端数据获取和缓存策略。[TanStack Query Queries](https://tanstack.com/query/latest/docs/framework/react/guides/queries)

### 4.2 UI 与视觉系统

以 Tailwind CSS 的语义变量作为唯一主题来源，先定义 `background`、`foreground`、`card`、`muted`、`border`、`primary` 等 token，再由 shadcn/ui 和业务组件共同消费。不要将页面色值散落在组件中。

Magic UI 的定位是“效果层”，使用范围限定为首页品牌区、空状态和少量内容提示；文章正文、后台表格、表单和弹窗保持克制、信息密度优先。

#### 现有视觉资产迁移

| 现有能力 | 当前实现 | React 迁移方案 | 可复用范围 |
| --- | --- | --- | --- |
| 技术图标环绕头像 | `vue-icon-cloud` + CSS | Magic UI `IconCloud` | 图标 slug、图片 URL、尺寸与视觉参数可复用；Vue 组件代码不可复用 |
| 主题切换 | Vue `useColorMode` + View Transitions API | `next-themes` 管理主题状态，Magic UI `AnimatedThemeToggler` 负责动画 | 动画原点、时长、`prefers-reduced-motion` 降级和 CSS token 可复用 |
| 深浅色样式 | CSS 变量 | `globals.css` 的 Tailwind/shadcn token | 色彩语义和视觉值可复用，选择器需重写 |

现有项目并没有直接使用 Magic UI：头像云是 Vue 专用组件，主题切换是自行封装的 View Transitions API。React 端可直接采用 Magic UI 对应组件的实现路径，官方提供了 `IconCloud` 和基于 View Transitions API 的主题切换组件。[Magic UI Theme Toggler](https://magicui.design/docs/components/animated-theme-toggler)

### 4.3 内容编辑策略

首版以 Markdown 为正文规范，延续现有文章资产并降低导入风险：

- 后台编辑器保存 `body_markdown`，预览由与公开页面一致的渲染器生成。
- 文章的标题、摘要、标签、发布时间、封面和状态使用结构化字段保存。
- 每次保存创建修订快照，发布操作产生不可变的发布记录。

当存在非技术作者、复杂嵌入内容或协同编辑的明确需求时，再评估 Tiptap。Tiptap 提供 React 集成和可组合扩展，但其 Markdown 能力仍处于 beta 文档范围，因此不将它作为首版 Markdown 数据规范的前提。[Tiptap Editor](https://tiptap.dev/docs/editor/getting-started/overview)

## 5. Go 后端设计

### 5.1 模块边界

```text
apps/api/
├── cmd/api/                 # 程序入口、依赖装配、优雅退出
├── internal/
│   ├── identity/            # 用户、角色、会话、登录限制
│   ├── content/             # 文章、标签、系列、草稿、修订、发布
│   ├── media/               # 上传策略、文件元数据、对象存储
│   ├── site/                # 站点设置、导航、主题等运营配置
│   ├── audit/               # 管理操作审计日志
│   ├── platform/            # 数据库、缓存、对象存储、时钟、ID
│   └── transport/http/      # Gin 路由、鉴权中间件、请求/响应 DTO
├── migrations/              # 仅追加的数据库迁移
└── openapi/                 # OpenAPI 源文件与生成配置
```

业务模块拥有自己的服务、仓储接口和测试；HTTP handler 只做参数绑定、鉴权调用和响应映射。禁止 handler 直接拼接 SQL，禁止前端绕过 Go 直接访问数据库或对象存储。

### 5.2 首版数据模型

| 实体 | 关键字段 | 说明 |
| --- | --- | --- |
| `users` | `id`、`email`、`status` | 运营用户身份 |
| `roles` / `user_roles` | `role`、`permission` | RBAC，首版至少管理员、编辑、只读 |
| `sessions` | `token_hash`、`expires_at`、`user_id` | 服务端会话，不将访问令牌写入 localStorage |
| `posts` | `slug`、`title`、`summary`、`body_markdown`、`status`、`published_at` | 文章当前版本 |
| `post_revisions` | `post_id`、`content`、`created_by` | 草稿与发布前可回滚快照 |
| `tags` / `post_tags` | `slug`、`name` | 标签关系 |
| `media` | `object_key`、`mime_type`、`size`、`created_by` | 文件元数据，文件本体在对象存储 |
| `audit_logs` | `actor_id`、`action`、`target`、`metadata` | 发布、删除、授权等关键操作记录 |

文章状态固定为 `draft`、`scheduled`、`published`、`archived`。状态流转在 `content` 模块校验，数据库事务内同时更新文章、修订和审计日志。

### 5.3 API 与契约

- 使用 `/api/v1/public/*` 提供公开只读接口，例如文章详情、归档、标签、站点配置。
- 使用 `/api/v1/admin/*` 提供后台接口，并按角色授权。
- 以 OpenAPI 3.1 为唯一接口契约，从契约生成 React TypeScript 客户端；Go handler 的集成测试验证响应符合契约。
- 发布、下线、删除、媒体上传等写入接口要求会话、CSRF 防护、审计日志和幂等键。
- 发布成功后，Go 使用签名事件调用 Next.js 缓存失效端点；不得依赖固定时间等待缓存过期。

## 6. 仓库结构

迁移完成后采用 pnpm workspace 与 Go 模块并存的单仓库：

```text
sublog/
├── apps/
│   ├── web/                 # Next.js
│   │   ├── app/
│   │   │   ├── (public)/
│   │   │   └── admin/
│   │   ├── components/
│   │   │   ├── ui/          # shadcn/ui 受控源码
│   │   │   ├── effects/     # Magic UI 及品牌动效
│   │   │   └── content/
│   │   ├── lib/api/         # 生成的 API client 与查询封装
│   │   └── styles/
│   └── api/                 # Go 服务
├── contracts/openapi/       # API 契约
├── deploy/                  # Compose、反向代理、生产部署说明
├── scripts/migrate-content/ # 现有 Markdown 导入工具
└── docs/
```

现有 `content/posts/<slug>/index.md` 仅作为迁移输入和归档备份；迁移完成后，运行时内容的唯一来源是 Go API + PostgreSQL。

## 7. 分阶段迁移

### 阶段 0：技术验证

完成一条最小闭环：Go 提供一篇文章的公开读取接口，Next.js 服务端渲染文章详情，Magic UI 图标云和主题切换在 React 中正确运行。

验收：SEO 元信息可见、首次加载无主题闪烁、禁用动效偏好下主题可正常切换。

### 阶段 1：公开内容迁移

1. 建立 PostgreSQL 迁移、`posts`、`tags`、`post_revisions` 和公开 API。
2. 编写一次性导入脚本，读取现有 frontmatter 和 Markdown 正文。
3. 重建首页、文章详情、列表、归档、标签和系列页面。
4. 保留原 URL，缺失页面使用 301 重定向，逐页比对标题、描述、封面和正文。

验收：全部已发布文章可访问；迁移前后的 URL、SEO 字段和图片链接无回归。

### 阶段 2：运营后台

1. 建立登录、会话、RBAC 与审计日志。
2. 实现文章列表、草稿编辑、预览、发布、定时发布和修订回滚。
3. 接入对象存储的预签名上传与媒体选择器。
4. 接入内容发布事件与 Next.js 缓存失效。

验收：编辑只能操作授权资源；发布后公开页与 sitemap 在可预期时间内更新；审计记录可追踪。

### 阶段 3：运营能力完善

- 后台搜索、内容质量检查、死链检测与站点设置。
- 评论审核、内容推荐和访问统计。
- 基于实际作者需求决定是否引入 Tiptap、协同编辑或内容审批流。

## 8. 关键风险与验证

| 风险 | 影响 | MVP 验证 |
| --- | --- | --- |
| Markdown 导入丢失格式 | 历史内容不可逆受损 | 保留原文件，导入后逐篇 hash 与人工抽检 |
| Next 缓存未失效 | 发布后读者仍看到旧内容 | 发布接口后调用签名失效端点，编写端到端测试 |
| 前后端职责混杂 | 规则分散且难以审计 | 代码审查禁止 Next Route Handler 承载业务写入 |
| 富文本过早引入 | 数据格式和编辑器维护成本暴涨 | 首版只验证 Markdown 编辑与预览 |
| 动效影响后台可用性 | 性能与无障碍下降 | Magic UI 仅在品牌区域使用，尊重 `prefers-reduced-motion` |

## 9. 测试与上线标准

- Go：领域服务单元测试、HTTP + PostgreSQL 集成测试、迁移升级测试、权限矩阵测试。
- Web：组件测试覆盖主题与编辑表单；Playwright 覆盖登录、草稿、发布、撤回、文章访问和上传。
- 契约：CI 生成 TypeScript API client，并检测 OpenAPI 变更。
- 发布：健康检查、数据库备份、对象存储生命周期规则、错误监控和审计日志保留策略齐备后才接入真实运营数据。

## 10. 最终决策

React + Go 适合本项目向内容平台演进，但必须以“Next.js 负责体验与渲染、Go 负责全部业务与数据”为边界。优先迁移公开阅读链路，再实现后台闭环；不要在第一阶段同时重写全部页面、接入富文本和实现复杂工作流。
