BFF 与 Next.js 的联系与组合
最近在写一个Next.js的应用,其中API Routes被我当做轻量BFF,去做一些数据加工与处理,那么BFF 与 Next.js到底有什么样的联系呢?
一、概念先厘清
1. BFF(Backend for Frontend)
BFF 是为前端专门提供的一层后端,职责包括:
- 接口聚合:把多个后端/微服务的调用合并成前端需要的少数接口。
- 数据裁剪与适配:把后端 DTO 转成前端需要的结构,减少字段、改名、扁平化等。
- 鉴权与会话:在 BFF 统一做登录态校验、权限校验,前端只关心「调一个接口」。
- 协议与形态统一:前端只和 BFF 用 REST/JSON 等约定好的方式通信,不直接面对内部 RPC、消息队列等。
BFF 不负责 HTML 渲染,只负责「给前端提供数据接口」。
2. Next.js 与 SSR
Next.js 是一个 React 全栈框架,除了做前端 SPA,还提供:
- 服务端渲染(SSR):在服务器上执行 React,生成 HTML 再返回给浏览器,首屏即完整内容,利于 SEO 和首屏性能。
-
API Routes:在
pages/api下写接口,运行在 Node 里,对外提供 HTTP API,可当「小后端」用。
所以:
- SSR 解决的是「谁在哪儿把页面渲染出来」的问题(服务器渲染 vs 浏览器渲染)。
- BFF 解决的是「谁给前端提供接口、怎么聚合与适配」的问题。
二者一个偏「渲染与页面」,一个偏「接口与数据」,可以单独用,也可以一起用。
二、A项目中的 BFF 层:
在我们工作中,前端使用的接口由JAVA编写的BFF分层应用提供,在分层应用中去调用真正的内网接口。
2.1 架构概览
-
Web 层:Controller 暴露 HTTP 接口,路径多为
/xxx/api。 - Service 层:Facade 封装对下游业务服务的调用。
前端/移动端只认 BFF 的 URL,不直接调业务中台。
2.2 Controller:面向前端的接口与鉴权
以xxx申报记录查询为例(MobilexxxController):
@Controller
@RequestMapping("/xxx/xxxx/api")
public class MobilexxxController extends MobilexxxBaseController {
@RequestMapping(value = "/query", method = RequestMethod.POST)
@ResponseBody
public MobilexxxDTO<Object> query(@RequestBody MobileuxxDTO<ZxxxRequestDTO> requestDTO) {
// 1. 获取用户账户信息
AccountDTO accountDTO = getAccount();
// 2. 验证用户会话(BFF 统一鉴权)
validateHelper.accountSessionCheck(accountDTO, ...);
// 3. 从统一入参中取出业务数据,并注入用户信息
ZxxxRequestDTO request = requestDTO.getData();
request.setXm(accountDTO.getAccount().getName());
// ...
// 4. 调用 Facade,再统一封装返回格式
Response<List<ZxxxResponseDTO>> response = mobilexxxFacade.query(request);
return ResponseHandler.procResponseResult(response);
}
}
可以看到 BFF 在这里做了:
-
统一入参/出参:
MobilexxxDTO/MobileuxxDTO,前端只面对一种请求/响应形态。 -
会话与鉴权:
getAccount()、validateHelper.accountSessionCheck()在 BFF 完成,前端无感知。 - 数据注入:把当前用户姓名、证件号等写入请求再交给下游,前端不用传敏感信息。
2.3 Facade:聚合下游服务
Facade 不实现业务,只做「转发 + 聚合」:
@Service
public class MobilexxxFacade {
@Autowired
private RestfulClient restfulClient;
@Autowired
private RestfulUrlHelper restfulUrlHelper;
public Response<List<ZxxResponseDTO>> query(ZxxRequestDTO request) {
return restfulClient.postForJson(
restfulUrlHelper.getxxxnwUrl("xxxx/query"),
request, Response.class, List.class, ZxxResponseDTO.class);
}
}
其他 Facade同理:BFF 通过 RestfulClient 调远端服务,对前端只暴露「一个接口、一种协议」。
小结:app 这一层是典型的 BFF——独立部署的 Java 服务,只提供 API,不负责页面渲染;职责是鉴权、聚合、适配,方便前端/移动端调用。
三、B项目中的 Next.js 与 SSR:
xxx-Next 是 Next.js 应用,同时用到了 SSR 和 API Routes(充当轻量 BFF)。
3.1 SSR:首屏服务端取数与渲染
使用 getServerSideProps 每次请求时在服务端拉取数据并渲染:
export default function GetData(props: PropsType) {
const { err, data, msg = '' } = props
// ...
return (
// ...
)
}
export async function getServerSideProps(context: any) {
const { id = '' } = context.params
const data = await getDataById(id) // 服务端请求接口
return { props: data }
}
流程是:
- 用户请求
/data/xxx; - Next 在服务器执行
getServerSideProps,调用getDataById(id); - 拿到数据后,在服务器上渲染 React,得到 HTML;
- 把 HTML 和序列化好的
props一起返回给浏览器;
这样首屏就是「带数据的完整 HTML」,无需等客户端再发请求才出内容,这就是 SSR。这里 Next.js 的角色是「渲染层 + 在服务端发起数据请求」,数据来源也可以换成调用上面那套 Java BFF。
3.2 API Routes:"薄" BFF
点击提交按钮时,指向的是 Next 自己的接口:
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// 数据加工
const data = genData(req.body) // 把表单 body 转成后端需要的结构
const resData = await postData(data)
// ...
}
这里 Next.js 的 API Route 做的是:
- 接收: 传入的数据,如表单数据;
-
转换:
genData(req.body)把表单字段整理成接口需要的结构; -
转发:
postData(data)再请求真正的后端。
也就是说,API Route 在这里扮演了一层薄 BFF:对前端暴露简单的一个 POST /api/data,内部做参数适配和转发,前端不直接调外部后端。
3.3 数据流小结
-
读:浏览器 → Next 服务端 →
getServerSideProps里getDataById→ 外部接口→ 服务端渲染 HTML → 返回。 -
写:浏览器 POST 到 Next 的
/api/data→ API Route 整理参数并postData()→ 再请求后端→ 根据结果处理。
SSR 解决「从哪拿数据、在哪渲染」;API Route 解决「提交时谁来做参数转换和转发」。两者都在 Next 里,但职责不同。
四、BFF 与 Next.js(SSR)的区别
| 维度 | BFF(如 app 中的 Java 层) | Next.js(SSR + API Routes) |
|---|---|---|
| 主要职责 | 为前端提供聚合/适配后的 API | 渲染 HTML 页面 + 可选的 API 端点 |
| 是否渲染 | 不渲染,只返回 JSON 等数据 | 服务端执行 React,输出 HTML |
| 典型部署 | 独立服务(如 Java 进程、容器) | Node 进程,同一应用里既有页面也有 API |
| 技术栈 | 任意后端语言(本项目为 Java) | Node + React(Next.js) |
| 鉴权位置 | 常在 BFF 统一做 | 可在 API Route 或 getServerSideProps 里做 |
| 适用场景 | 多端复用同一套接口、多后端聚合 | 需要 SEO、首屏性能的前端应用 |
简言之:BFF 是「专门给前端的接口层」;Next.js SSR 是「在服务器上把页面渲染出来的方式」。一个偏数据与接口,一个偏页面与渲染。
五、BFF 与 Next.js 的联系与组合
-
Next.js API Routes 本身可以当作轻量 BFF
如 xxx-Next 的/api/data:接收整理参数,再调后端。适合逻辑简单、不需要多语言/多后端聚合的场景。 -
Next.js 的 SSR 可以消费 BFF
在getServerSideProps或getStaticProps里,用getDataById这类函数去请求「真正的 BFF」(例如 app 里的xxx/xxx/api),而不是直接调业务中台。这样:- 鉴权、聚合在 BFF 完成;
- Next 只负责「要什么数据、怎么渲染」,职责清晰。
-
组合方式示例
- 把 xxx-Next 里
ajax.ts的HOST或getDataById的 URL 指向 app 的 BFF 地址,数据就从 Java BFF 来。 - 提交仍可先到 Next 的
/api/data,再由 API Route 调 BFF 的提交接口,这样前端只和 Next 打交道,BFF 由 Next 在服务端/API 里调用。
- 把 xxx-Next 里
于是可以形成:浏览器 ↔ Next.js(SSR + API Routes)↔ BFF(app)↔ 业务服务。BFF 管「接口与数据」,Next 管「页面与一次转发/适配」。
六、总结
- BFF:面向前端的接口层,做聚合、适配、鉴权,不负责渲染;本项目中 app 的 Controller + Facade 是典型实现。
-
Next.js SSR:在服务端按请求拉数据并渲染 React 成 HTML,首屏快、利于 SEO;xxx-Next 的
getServerSideProps就是 SSR。 - 区别:BFF 是「接口层」,Next.js(SSR)是「渲染方式」;BFF 可独立于前端技术栈部署,Next 是前端框架自带服务端能力。
- 联系:Next 的 API Routes 可当薄 BFF 用;SSR 的数据来源可以、也适合来自 BFF;二者组合能同时获得「清晰的数据接口层」和「更好的首屏与 SEO」。
结合项目:app 负责「给前端/移动端提供统一、安全、好用的 API」;xxx-Next 负责「用 Next.js 把页面做出来(SSR),并用 API Route 做提交时的薄 BFF」。理解这一点,就能在架构上分清 BFF 与 Next.js(SSR)各自解决什么问题、如何配合使用。