# 新时代的 PHP：RSC 的边界错位与工程代价

原文：https://blog.grtsinry43.com/posts/rsc-boundary-mismatch/

作者：grtsinry43

发布时间：2025-12-13T16:09:44Z

内容更新时间：2025-12-13T16:09:44Z

讽刺的是，这篇文章，也就是你看到这个网站，正在运行在 Next.js 上，但由于无法承受的问题和维护压力，我已经很认真地把“放弃 Next.js”提上日程了。

Next.js 本身我正在用、也在骂。原因不复杂：这两年最“新时代 PHP”的东西，不是某个新框架，而是 **前端在不自知的情况下，被推去承接后端该承担的安全边界与运行时复杂度**——而 React Server Components（RSC）正是这件事的放大器。

我会用一种比较“理性地骂”的方式，把事情讲清楚：

**RSC 到底是什么；它为什么天然把边界搞得很危险；最近这次 RSC 相关的严重漏洞到底发生在什么位置；Next.js 与 Vercel 又是怎样把这些能力包装成“工程默认选项”；以及这些选择的工程代价最终转为了什么。**

* * *

## 零、先叠甲：“新时代 PHP”到底在说什么

在正文开始前，我想先给这篇文章定个调，以免误伤友军：

1. **我并不否定 Next.js 在效率上的统治力**：对于独立开发者、验证型 MVP（最小可行性产品）、或者内容型网站（博客、文档），Next.js + Vercel 依然是目前地球上最快的上线组合。这种“一把梭”的爽感，我是认可的。
2. **“新时代 PHP”不是贬义词，而是架构描述**：正如 PHP 曾凭借“请求即脚本”的低门槛撑起了半个互联网，RSC 也在试图降低全栈门槛。但我所担忧的，是它在 **不经意间** 复刻了当年 PHP 混合开发时期的“安全黑洞”与“耦合泥潭”。
3. **我的视角偏向“严谨工程”**：作为一名习惯了 Spring Boot/Java 后端架构，或者是 ktor fiber fastify，同时主要学习前端领域的开发者，我对 **“边界（Boundary）”** 极为敏感。这篇文章的核心冲突，在于 **“前端追求极致 DX（开发体验）”** 与 **“后端追求极致安全与稳定”** 之间的天然矛盾。

* 以前：前端是前端，后端是后端。接口契约、鉴权、审计、限流、WAF、灰度，都在后端层解决。

* 现在（RSC/Server Actions 的兴起）：把“后端能力”塞进 React/Next 的文件与组件里，用 `use server` 把函数变成“远程可调用逻辑”。官方甚至明确表达过“你可以不需要手写 API Route”。 [Next.js13 的叙述](https://nextjs.org/docs/13/app/api-reference/functions/server-actions)

这不是“全栈”那么简单，这是 **把接口面向公网的语义，无感的形式伪装成了组件内部的语义**。语义伪装的后果通常只有两种：安全事故，或者工程事故——最好两者别一起来。

## 一、RSC 到底是什么：它不是 SSR 的升级，而是“协议 + 运行时分裂”，也就是，边界错位从协议层就开始了

如果只看铺天盖地的宣传，RSC 很容易被误解成“更快的 SSR”或“把组件放到服务器渲染”。但 React 官方对 Server Components 的定义里有个关键点：**它运行在一个“与客户端应用或 SSR 服务器分离的环境”**，并且输出的不只是 HTML。[Server Components](https://react.dev/reference/rsc/server-components)

更准确地说，RSC 带来三件事：

1. **组件分层变成运行时分层**

   * Client Components：在浏览器跑，拿到 JS bundle。

   * Server Components：在服务器跑（可能是构建时，也可能是请求时），可以直接访问数据库/文件系统等服务器资源。所以你可以看到，官方甚至搞了一个教程建议你在 RSC 写 sql，然后使用 Suspense 做流式传输。[App Router: Getting Started | Next.js](https://nextjs.org/learn/dashboard-app/getting-started)

2. **传输的不是页面，而是“可恢复的组件树数据”（Flight payload）** 
   你可以把它理解为：服务端把一棵 React 树编码为一种可流式传输的数据结构，客户端再把它解码/拼装回 React 状态，也就是本身他就和 SSR 是完全不同的。

3. **组件开始携带“能力”**  
   一旦你允许“Server 侧函数可被触发”（例如 Server Actions/Server Functions），你就不再只是渲染 UI：你是在暴露一套“从浏览器直达服务器逻辑”的通道，那这里就很危险了，因为这就从获取运行结果，变成了一种类似 RPC（远程过程调用）。[Next.js](https://nextjs.org/docs/14/app/building-your-application/data-fetching/server-actions-and-mutations)

这第三点，就是边界错位的根源。

* * *

## 二、Flight 协议的现实：它必然涉及“反序列化”，而反序列化就必然是风险点，配合 JS 本身的设计那就无所不能了
---------------------------------------

很多人听到“反序列化漏洞”就条件反射：又是 Java 那套？
但这里不是对象流，而是 **协议层为了让 React 树可恢复，必须做的解码过程**。

React 的实现里，你能看到它对 payload 的处理方式：`JSON.parse` 并不是直接 parse 成普通 JSON，而是带了一个自定义 reviver（`_fromJSON`），用来把特殊标记字符串（例如 `$` 开头）解释成 Promise、Server Reference、Map、Set、FormData、Date、BigInt 等结构。

https://github.com/facebook/react/blob/323b6e98a76fe6ee721f10d327a9a682334d1a97/packages/react-server/src/ReactFlightReplyServer.js

例如：

```ts
const value = JSON.parse(chunk.value, chunk._response._fromJSON);
```

以及 reviver 里对 `$` 前缀的分派：

```ts
if (value[0] === '$') { 
// Promise / Server Reference / Map / Set / FormData / Date / BigInt ... 
}
```

这些代码只是说明一个事实：

> **RSC 的正常工作方式，就是在服务器端“解码外部输入”并恢复出可执行/可解析的结构。** 
> 你一旦把“可触发的服务器函数”也绑在同一套通道上，安全边界就从“HTTP API”滑向“协议解码器”。

这就是架构选择的必然后果，毕竟安全总要有模块负责。

Java 的 ObjectInputStream 踩过，Python 的 Pickle 踩过，现在轮到 JS 的 Flight 了。

* * *

## 三、这次离谱事件的原因：不是业务逻辑，而是解码器被当成了能力网关，责任边界到了前端
---------------------------------------

不久之前，React 官方发布公告：**RSC 存在一个“未认证的远程代码执行（RCE）漏洞”，可通过利用 React 解码发送到 React Server Function 端点的 payload 触发。**

[React](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components)

第三方安全团队的分析进一步强调了它的性质：这不是某个业务接口没鉴权，而是 Flight/RSC 相关的“处理链路”出现了可被利用的点，导致在默认配置下就可能被打穿。[wiz.io](https://www.wiz.io/blog/critical-vulnerability-in-react-cve-2025-55182)

并且，这类问题会天然产生“生态级连坐”：

* React 层：CVE-2025-55182（React/Flight/RSC 相关）

* 框架层：Next.js 等实现 RSC/Server Actions 的框架会一起进入受影响范围

~~在这个事故发生的时候我还刚好甲流，发烧很难受，但是还不得不强撑着起来更新依赖~~

在这次事件中，**责任边界发生了变化**：

以前你写后端 API，你会默认：

* 输入校验是业务层的事

* 鉴权/鉴别来源是网关/中间件/后端统一层处理的

* 序列化/反序列化是你可控的

而在 RSC/Server Actions 的组合里，很多工程的默认形态是：

* 浏览器发一个“协议包”

* 框架解码恢复结构

* 框架再把它路由到某个服务器函数

**解码器变成网关**，这就会让“协议实现细节”直接进入你的威胁模型。[React](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components)

* * *

## 四、继续复盘，事情的严重：Next.js 把能力包装成默认姿势，甚至开箱即用：于是前端开始背后端的锅
------------------------------------

于是这个时候主推 App Router 的 Next.js 出场了。

如果 Next.js 只做“传统前端 + SSR”，其实还没那么灾难。真正的拐点在于：Next.js 把 RSC/Server Actions 作为 App Router 时代的主叙事之一，并明确描述 Server Actions 是 **在服务器执行的异步函数，并且可在 Server/Client Components 中使用**。[Next.js](https://nextjs.org/docs/14/app/building-your-application/data-fetching/server-actions-and-mutations?utm_source)

那事情就变得微妙了：

> 你把函数写在前端项目里，但它会在服务器跑；你在组件里调用它，但它是“后端入口”。

到这里，“前端插手后端开发”的现实就出现了：

* 这条链路一旦出安全问题，你不能再说“后端会兜底”，因为后端入口就在 Next 项目里。

* 作为前端开发，你不能再说“我们只写页面”，因为你实际部署的是一段“可被触发的服务器逻辑”。

* 你甚至很难用传统后端的方式给它加网关、加 WAF、加统一鉴权层，因为框架已经替你决定了请求应该如何被解释。

Next.js 当然意识到了风险，于是也着重添加了安全相关的配置和能力。[Next.js](https://nextjs.org/docs/app/guides/data-security?utm_source=chatgpt.com "Guides: Data Security")
但这件事本身看起来啊就很荒谬：**你看起来在写“组件”，实际上你在配置后端的安全策略。**

而现实里，因为 Nextjs 将开发后端的门槛降得太低了，很多“纯前端开发者”并不具备这套安全心智模型：他们会把它当成“更舒服的表单提交方式”，直到某天看到公告写着“Unauthenticated RCE”。[React](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components)

这就是强制前端插手后端的后果，前端想把后端写成语法糖。

**然后再把后端能力塞进前端默认工程，假装这只是 DX（开发体验）。**

* * *

## 五、再说 Vercel：用 Next.js 做壳，引流平台能力，然后把复杂度外包给用户
-------------------------------------------

这里我不否认 Vercel 的产品力，它确实把部署体验做得极致，并且它也提供防护体系（包括平台级防护、以及可配置 WAF/Firewall）。[Vercel](https://vercel.com/docs/vercel-firewall)

但问题在于：**Next.js 的很多“卖点”，在工程上并不是“框架能力”，而是“平台叠加能力”。**

举两个典型例子：

### 1）ISR：听起来是框架特性，实际是缓存/失效/函数编排的一整套系统

Next.js 文档会告诉你如何做 ISR、如何触发再生成、如何 revalidate。[Next.js](https://nextjs.org/docs/app/guides/incremental-static-regeneration?utm_source=chatgpt.com "How to implement Incremental Static Regeneration (ISR) ")
Vercel 的文档则会进一步解释 ISR 在其平台上会如何落到具体的 Function/缓存与配置上。[Vercel](https://vercel.com/docs/incremental-static-regeneration)

工程现实是：

* 你以为你在用“Next.js 特性”；

* 你实际在绑定“某个平台对缓存失效与边缘分发的具体实现”。

这不是不能用，而是你应该清楚：**这类特性会把你从“框架迁移”推到“平台迁移”。**

### 2）“中间件”这个词本身就带歧义：它并不在你的服务中间，而是在 Edge Runtime

Next.js 早期版本 Middleware 只支持 Edge runtime，不能用 Node.js runtime。[Next.js](https://nextjs.org/docs/14/pages/building-your-application/routing/middleware)
而 Edge Runtime 又意味着：你拿到的是 Web API 语义、受限的运行环境，与 Node.js 的能力集不同。[Next.js](https://nextjs.org/docs/app/api-reference/edge)

所以很多时候你写 Middleware 的体验是：

* 名字叫“中间件”，你以为它像后端的 middleware/filter；

* 实际它更像“边缘网关脚本”，能力受限、调试困难、行为受运行位置影响。

把它叫 middleware，不是错，但它会天然误导大量开发者建立错误心智模型：以为自己在写“后端中间件”，其实是在写“边缘逻辑”。

这样，你以为它值得信赖，然而实际上只是展示他们平台能力的一个手段。

说的更直白些，你以为你写的是标准 Web 代码，实际上你写的是 **Vercel 专用的 DSL**。

* * *

## 六、工程代价清单：不是学一下就会，而是你要多维护一个世界观
---------------------------------

到这里，骂点就不应该停留在“Vercel 坏”“Next 坏”“RSC 坏”，而是要落到工程代价——因为这才是你会长期为之付费的部分。

### 1）运行时分裂：Node、Edge、Client、Server Component 环境并存

Next.js 自己就把 runtime 拆成 Node 与 Edge 两套，并说明能力差异。[Next.js](https://nextjs.org/docs/app/api-reference/edge?utm_source=chatgpt.com "Edge Runtime - API Reference")
而 RSC 又把组件分成 Server/Client 两类。[React](https://react.dev/reference/rsc/server-components?utm_source=chatgpt.com "Server Components")

你最终维护的是一个矩阵：

| 写法             | 运行位置    | API 能力     | 风险点              |
| ---------------- | ----------- | ------------ | ------------------- |
| Client Component | 浏览器      | 浏览器 API   | XSS/依赖链          |
| Server Component | 服务器/构建 | 服务器资源   | 数据泄露/边界混乱   |
| Middleware       | Edge        | Web API 子集 | 行为不一致/调试困难 |
| Server Action    | 服务器      | 后端入口     | 鉴权/来源/反序列化  |

作为全栈框架的使用者，它只是事实：你需要为每个格子建立正确预期，否则问题会以“看起来像 bug”的方式出现。

### 2）安全边界内陷：从“API 网关/后端鉴权”变成“框架配置 + 协议解码链路”

这次事件已经告诉我们：攻击面不止是业务 API，而是 RSC/Flight/Server Functions 的解码链路。[React](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components)

Nextjs 成功证明了 **全栈不难，难的是把责任边界划清楚。**

### 3）平台耦合：迁移成本从“换框架”上升到“换平台能力组合”

ISR、Edge、WAF、以及各种“默认最佳实践”，在 Vercel 上确实丝滑。[Vercel](https://vercel.com/docs/incremental-static-regeneration?utm_source=chatgpt.com "Incremental Static Regeneration (ISR) ")
但当你想把它搬走（自建、换云、甚至只是加一层代理），你就会发现：你迁移的不只是代码，还有一堆隐含假设。

* * *

## 七、最后补一刀：AI 生成代码默认 Next.js 起手，是在扩大这种错位
---------------------------------------

现在很多 AI 生成前端项目的默认姿势，基本都是：Next.js + App Router + Server Actions + Shadcn/ui + 一堆平台最佳实践模板，拿过来一把梭哈。Vercel 自己也在把生成 UI/生成代码与 Next.js 工作流绑定（ v0 就是他们家的代表，额不是 VoidZero 那个 v0，是 v0.dev）。

问题不是 AI 生成了 Next.js，而是：

* **AI 会把“默认最流行模板”当成“默认最正确架构”**

* 它很少提醒你：我创建的是全栈项目，这里的 Server Actions 是后端入口、这里的 Middleware 跑在 Edge、这里的 ISR 会平台耦合、这里的安全边界需要你自己负责

* 于是新手更容易在不理解 RSC 的情况下，把项目推到全栈默认形态

当理解成本被隐藏，风险成本就会延后爆炸。然后大家再回来补课：什么是 Flight、什么是 reviver、什么是来源校验、什么是边缘运行时差异——这就是工程代价，一个激进框架背后的，一个把后端当前端语法糖来写的工程代价。

* * *

## 总结：我为什么说这是“新时代 PHP”
-------------------

我不反对 RSC，也不否认它在某些场景能带来性能与工程收益。我反对的是：

1. **把后端能力伪装成组件语法糖**，让不具备后端工程化能力的人去写后端入口。

2. **把协议解码链路推成事实网关**，让安全问题从业务层上移到框架实现细节。

3. **用平台体验裹挟框架路线**，让特性越来越像为平台服务，迁移越来越像掏空重来。

之所以人们在放弃 Next.js，不是因为它写不了什么，恰恰相反他有面面俱到的能力；而是因为我不想再把一个前端工程，硬凑成一个后端工程，然后在漏洞公告出现时才意识到：原来我部署的不是页面，是一段可被触发的服务器逻辑。

写这篇文章，并非要号召大家明天就卸载 Next.js 回去写 JSP。 技术的螺旋上升总伴随着代价。React 团队探索 RSC 的勇气值得敬佩，Vercel 想要统一 Web 开发流的野心也令人惊叹。但在铺天盖地的营销叙事中，我们作为一线工程师，必须保持 **冷眼旁观** 的能力。

我们不能因为 Vercel 演示里的 Demo 很炫酷，就忽略了背后那个正在泄漏数据库连接的 Lambda；也不能因为 `use server` 写起来很爽，就忘记了此时此刻你正在裸奔。 **理性地评判，是为了在它把我们的工程搞崩之前，先搞清楚底线在哪里。**
