Next.js Server Actions
Next.js@14 中默认开启 Server Actions。
Next.js 先打通了 React 在客户端与服务端“渲染”分界。Server Actions 则打通了客户端与服务端“数据”分界。
渲染分界
客户端与服务端渲染一致,就是常说的 SSR。
数据分界
客户端与服务端之间互通需要一个载体。一般为服务端暴露的一个 http 实现的接口方法,客户端通过 fetch 或 xhr 调用。
使用 server actions 则不需要再关注 http 方法地址与需要接受的数据格式。就像调用本地函数,数据则通过函数的入参传输。不用再关注 http 接口所接受的数据格式与处理返回格式。
虽然 Next.js 在幕后是使用 http 的 post 方法进行传输。就约等于 Next.js 帮我们实现了 http 接口的创建,返回数据处理。重建 Next.js 应用的心智模型。
server actions 因为服务端不再暴露 http 的方法接口地址,只有该 server actions 函数能进行数据的调用。并且返回的数据结构为 React 流(参考文档)。虽然可以通过手动添加 http request header Next-Action 属性来达到伪造请求。但一定程度上迷惑破译者。
官方解释
Behavior
- Server actions can be invoked using the
action
attribute in a<form>
element:- Server Components support progressive enhancement by default, meaning the form will be submitted even if JavaScript hasn't loaded yet or is disabled.
- In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration.
- After hydration, the browser does not refresh on form submission.
- Server Actions are not limited to
<form>
and can be invoked from event handlers,useEffect
, third-party libraries, and other form elements like<button>
. - Server Actions integrate with the Next.js caching and revalidation architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server roundtrip.
- Behind the scenes, actions use the
POST
method, and only this HTTP method can invoke them. - The arguments and return value of Server Actions must be serializable by React. See the React docs for a list of serializable arguments and values.
- Server Actions are functions. This means they can be reused anywhere in your application.
- Server Actions inherit the runtime from the page or layout they are used on.
体验
在使用 Next.js 开发文件管理系统时,去掉了客户端调用 http 接口的封装过程。例如接口新增参数、减少参数,改变返回类型等对接口进行更改时,借助 TypeScript 或者 JSDoc 的类型描述则可有较好的开发提示。
可以直接通过 server actions 调用 node.js 的 fs 下的方法。也就是老被吐槽的,可能会自己干掉自己。
删除方法
'use server'
import fs from 'fs'
export const del = () => {
fs.rmSync('/')
}
调用删除方法
'use client'
import { del } from './del'
export function DelBtn() {
return <button onClick={() => del()}/>
}
客户端组件可以用在任何地方进行调用,例如组件初始化、销毁;各种 dom 事件。
Server Actions 调用时返回一个 Promise,所以可以结合使用 ahooks 的 useRequest 或者 swr 提升数据消费体验。例如常用的“缓存”、“屏幕聚焦重新请求”等功能。
并不是 Next.js 不再需要 http 的接口了。例如实现自己的流式传输数据时还是需要 http 接口的。例如图片文件的查看、下载文件内容,上传文件等操作。
默认情况下 Server Actions 的 body 大小为 1mb。可通过修改 bodysizelimit 参数增加或减少。
next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
bodySizeLimit: '2mb',
},
},
}
调用截图
当鼠标 hover 到顶部面包屑导航时,出现当前选中目录下所有文件夹的下拉菜单。使用 Server Actions 获取菜单内容。
请求载荷
[
{
"path": "/Downloads/ways",
"only_dir": "1",
"has_file_stat": "1"
}
]
响应
0:["$@1",["development",null]]
1:[{"name":"JPG2","is_directory":true,"stat":{"dev":16777220,"mode":16877,"nlink":3,"uid":501,"gid":20,"rdev":0,"blksize":4096,"ino":120655456,"size":96,"blocks":0,"atimeMs":1703242044321.7495,"mtimeMs":1703242019496.5264,"ctimeMs":1703242044324.9827,"birthtimeMs":1703242019496.5264,"atime":"$D2023-12-22T10:47:24.322Z","mtime":"$D2023-12-22T10:46:59.497Z","ctime":"$D2023-12-22T10:47:24.325Z","birthtime":"$D2023-12-22T10:46:59.497Z"}},]
伪造 fetch 请求,会发现返回当前页面,而不是数据。
request headers
POST /path/Downloads/ways/PSD HTTP/1.1
Accept: text/x-component
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en-US;q=0.7,en;q=0.6
Connection: keep-alive
Content-Length: 63
Content-Type: text/plain;charset=UTF-8
Cookie: readdir-sort=desc_date; display-type=card; card-column=3; window=%257B%2522width%2522%253A747%252C%2522height%2522%253A1122%257D; viewport-size={%22width%22:747%2C%22height%22:1122}
DNT: 1
Host: localhost:3000
Next-Action: 706aae5c02f31f5f481711066003ad67eeb6dc82
Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22path%22%2C%7B%22children%22%3A%5B%5B%22path%22%2C%22Downloads%2Fways%2FPSD%22%2C%22oc%22%5D%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D
Origin: http://localhost:3000
Referer: http://localhost:3000/path/Downloads/ways/PSD
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
通过分析 http request headers,Server Actions 在 header 中插入了下面的关属性。
...
Next-Action: 706aae5c02f31f5f481711066003ad67eeb6dc82
...
fetch 主动将该值插入 headers 内则可手动成功调用 Server Actions。
fetch('/path/Downloads/ways/PSD', {method:'POST', headers:{'Next-Action':'706aae5c02f31f5f481711066003ad67eeb6dc82'}, body: JSON.stringify([{path: "", only_dir: "1", has_file_stat: "1"}])})
使用开发工具搜索 706aae5c02f31f5f481711066003ad67eeb6dc82 关键词,发现如下结果
生产环境下则为被压缩混淆