Next.js Route Handler、BFF 与 Webhook
Next.js Route Handler 可作为 Web-facing BFF 和 Webhook 接收入口,为浏览器或外部系统提供协议转换、数据聚合、鉴权、缓存失效与代理能力,同时不替代真正的 Domain Backend。
[!info] related notes
- 所属 MOC:
- 相关概念:
- 易混淆概念:
- 相关资源:
Next.js Route Handler、BFF 与 Webhook
覆盖范围
这篇笔记解释 Next.js Route Handler 在两类真实系统边界中的位置:
Browser / Web Client
→ Next.js BFF
→ Domain Backend
以及:
Third-party Service
→ Webhook
→ Next.js Route Handler
→ Application / Cache / Domain Service
重点不是重新定义 BFF 或 Webhook,而是说明 route.ts 如何成为 Next.js 中的 Web-facing HTTP Adapter。
Route Handler 作为 BFF
Next.js 可以位于浏览器与后端服务之间:
Browser
↓
Next.js Route Handler
↓
Go / Java / Python Domain Service
↓
DB / Agent Runtime
这时 Route Handler 的价值可能包括:
- 统一 Web Origin
- cookie/session 到后端 token 的转换
- 浏览器专用 DTO
- 聚合多个服务
- 隐藏内部服务拓扑
- Web 特有的 rate limit / audit / headers
- 简化前端调用数量
- 对接 Next.js Cache / Revalidation
这属于 Backend For Frontend 的一种实现方式。
BFF 不等于 Domain Backend
不要因为 Next.js 能写 route.ts,就把独立后端的领域职责全部搬进 Web 层。
例如 BodySense:
Next.js Web / optional BFF
↓
Go Backend
├── session
├── event
├── permission
├── domain workflow
└── persistence boundary
↓
Python Agent Runtime
Next.js 更适合:
Web-facing composition / protocol adapter
而 Go 继续拥有真正的业务 authority。
因此:
Route Handler can proxy a Domain Service
≠
Route Handler becomes the Domain Service
具体 ownership 见 数据所有权与服务边界。
数据聚合
如果 Dashboard 的浏览器端需要:
Profile
Assessment
Subscription
分别来自不同服务,可以:
Browser
↓ GET /api/dashboard
Next.js BFF
├→ User Service
├→ Assessment Service
└→ Billing Service
↓
Browser-oriented DTO
这可以减少浏览器理解后端拓扑的成本。
但聚合不是越多越好。如果 BFF 变成所有业务逻辑的唯一巨大入口,会再次形成“万能后端”。
Webhook 为什么天然属于 Route Handler
Webhook 的调用方不是你的 React UI,而是外部系统:
Stripe
CMS
GitHub
Payment Provider
External Automation
它们需要一个稳定的公开 HTTP URL:
POST /api/webhooks/stripe
POST /api/webhooks/cms
因此最自然的是:
External Service
↓ HTTP POST
Route Handler
↓
verify authenticity
↓
parse / validate event
↓
Application / Service
这也是为什么 Stripe 付款成功通知不应该首先想到 Server Action:Server Action 是 React 应用内部的 server-function mutation 边界,而 Stripe 只能通过公开 HTTP Endpoint 访问系统。
Webhook 必须验证真实性
Webhook Endpoint 是公开的,不能因为 URL 看起来很随机就相信请求。
正确模型:
HTTP Request
↓
verify signature / secret / timestamp
↓
validate event shape
↓
idempotency / replay handling
↓
execute application action
否则攻击者可能自己构造:
"payment_succeeded"
"article_published"
伪造业务事件。
因此 Webhook 安全至少要考虑:
- 签名验证
- 原始 body 是否参与签名
- 时间戳 / replay 防护
- event id 幂等
- stable error response
- server-side observability
Webhook 与 Cache Revalidation
上一节的 event-driven invalidation 在这里有了真实 HTTP 入口。
例如 CMS:
Editor publishes article
↓
CMS sends Webhook
↓
POST /api/webhooks/cms
↓
Route Handler verifies signature
↓
revalidateTag("articles", "max")
↓
Next server cache converges
所以:
External Business Event
→ HTTP Boundary
→ Cache Invalidation
它把 Next.js Tag-based Revalidation 从“一个函数调用”升级成完整系统链路。
Route Handler 代理 SSE / Streaming
BodySense 可以有两种拓扑:
A. Browser → Go SSE
或:
B. Browser → Next.js Route Handler → Go SSE
方案 B 可能带来:
- 同源 cookie / auth
- 隐藏后端地址
- Web 协议转换
- 统一错误格式
- observability 聚合
但也增加:
- 多一层网络 hop
- proxy buffer / timeout 风险
- 连接生命周期复杂度
- 部署平台长期连接约束
- 故障点增加
所以:
有 Route Handler
≠
所有后端请求都应该代理
只有 Web-facing boundary 真正需要 BFF 时才增加这一层。
Client Runtime 与 BFF 的常见组合
例如 TanStack Query:
Browser / TanStack Query
↓
/api/notifications
↓
Next.js Route Handler
↓
notificationService
例如 BodySense:
Initial page read
→ Server Component
→ Go API
Browser runtime interaction
→ Route Handler/BFF(按需)
→ Go API
Live stream
→ direct Go SSE 或 Next proxy SSE(按架构需求)
这三个路径不要求统一成一种调用方式。
BFF 的核心边界
BFF 可以:
auth adaptation
aggregation
protocol translation
UI-specific projection
web cache coordination
但不应该因为“前端方便”就绕过:
Domain authorization
transaction invariant
workflow state machine
authoritative service ownership
最短记忆
Route Handler
= Web-facing HTTP Adapter
给浏览器
→ 可以是 BFF
给第三方系统
→ 可以是 Webhook Endpoint
但:
BFF ≠ Domain Backend
Proxy ≠ 必须
Webhook ≠ 普通可信 POST