首页 / Astro 教程 / Server Islands 服务端岛屿

Astro 教程

Server Islands 服务端岛屿

本教程共 56 篇 · 第 31 篇 · 更新于 2026-08-07 · 约 9 分钟阅读

AstroAstro 教程服务端岛屿Server Islandsserver:defer按需渲染群岛架构插槽

本节目标:学会用 server:defer 把页面里慢的、个性化的服务端内容拆成「服务端岛屿」,让主内容先出来,动态部分再补上。

服务端岛屿解决什么问题

前面讲了客户端岛屿:它负责「交互」,JS 在浏览器里水合。还有一种情况没解决——动态、个性化的服务端内容

比如电商详情页:商品主体很少变,但页头有用户头像、有专属优惠、有用户评价。这些内容要去服务端现取、现算。要是等它们全取完再渲染整页,主内容就被拖慢了。

服务端岛屿(Server Islands)就是为此而生。它能把「昂贵或慢的服务端代码」挪到主渲染流程之外,让高性能的静态 HTML 和动态的服务端组件轻松组合。

做法很简单:给页面上任意 Astro 组件加上 server:defer 指令,它就变成一块独立的水服务端岛屿。

---
import Avatar from '../components/Avatar.astro';
---

<Avatar server:defer />
Note

用服务端岛屿需要装好一个「适配器(adapter)」来做延迟渲染。因为延迟渲染本质上是一次按需渲染(on-demand rendering),得有服务端能力支撑。

服务端岛屿组件长什么样

服务端岛屿就是一个普通的、服务端渲染的 Astro 组件,被要求「等内容齐了再渲染」。页面会立刻带着占位内容渲染出来,等岛屿自己的内容就绪,再在客户端把它取回来显示。

装好适配器后,给任意组件加 server:defer 即可。岛屿内部能做任何按需渲染页面能做的事:去取内容、读 cookie 等。

---
import { getUserAvatar } from '../sessions';

const userSession = Astro.cookies.get('session');
const avatarURL = await getUserAvatar(userSession);
---

<img alt="用户头像" src={avatarURL} />

上面这个头像组件,会等拿到 session、取到头像 URL 之后才渲染。但因为有 server:defer,页面主体不会等它。

给服务端岛屿传 props 的限制

和客户端岛屿一样,传给服务端岛屿的 props 必须能「序列化」。函数没法传,因为序列化不了;带循环引用的对象也不行。

下面这些类型被支持:普通对象、numberstringArrayMapSetRegExpDateBigIntURLUint8ArrayUint16ArrayUint32ArrayInfinity

用插槽做 fallback 占位

延迟渲染期间,页面不能空着。你可以往服务端岛屿里塞「默认加载内容」,用名为 fallback 的插槽(slot)实现。

占位内容会在页面初次加载时和其余内容一起渲染,等岛屿内容就绪再被替换。可以放:通用头像、占位提示文案、加载动画(spinner)等。

---
import Avatar from '../components/Avatar.astro';
import GenericAvatar from '../components/GenericAvatar.astro';
---

<Avatar server:defer>
  <GenericAvatar slot="fallback" />
</Avatar>

这样用户先看到通用头像,过一小会儿才换成自己的。既快又不会「开天窗」。

它是怎么工作的

服务端岛屿的实现大多发生在构建期:组件内容被替换成一小段脚本。每个标了 server:defer 的岛屿,会被拆成自己的一条特殊路由,脚本在运行时去请求它。

当你构建站点时,Astro 会省略掉这个组件、在它的位置注入一段脚本,连同你标了 slot="fallback" 的占位内容。页面在浏览器加载时,这些组件会被请求到一个特殊端点(endpoint),由它渲染并返回 HTML。

所以用户能瞬间看到页面最关键的部分。动态岛屿加载前,占位内容会短暂出现。每个岛屿彼此独立加载,一个慢了,不会拖住别的个性化内容。

这个渲染模式是为「可移植」而设计的,不依赖任何特定服务端设施。从 Docker 里的 Node.js 服务,到你选的无服务器(serverless)厂商,它都能跑。

缓存与 URL 长度

服务端岛屿的数据通过 GET 请求获取,props 作为加密字符串放在 URL 查询里。这让你能用标准的 Cache-Control HTTP 头做缓存。

不过浏览器把 URL 长度限制在大约 2048 字节,既为实用,也为避免拒绝服务问题。如果你的查询串超长,Astro 会改成发 POST 请求,把所有 props 放进请求体。

POST 请求浏览器默认不缓存,因为它用于提交数据,可能造成数据或安全问题。所以你项目里已有的缓存逻辑可能在超长时失效。尽量只传必要的 props,别把整个对象或数组丢给服务端岛屿,保持查询串短小。

在服务端岛屿里拿到页面 URL

多数情况下,岛屿通过 props 拿到所在页面信息,和普通组件一样。但要注意:服务端岛屿运行在「脱离页面请求」的独立上下文里。

在岛屿组件里,Astro.urlAstro.request.url 返回的 URL 长这样:/_server-islands/Avatar,而不是浏览器里当前页面的地址。如果你在做预渲染(prerender),也拿不到查询参数当 props 传。

想拿页面 URL 的信息,可以读 Referer 请求头,它装着加载这个岛屿的页面地址:

---
const referer = Astro.request.headers.get("Referer");

if (!referer) {
  throw new Error("缺少 Referer 头");
}

const url = new URL(referer);
const productId = url.searchParams.get("product");
---

复用加密密钥

Astro 会用加密技术给传给服务端岛屿的 props 加密,防止敏感数据意外泄露。加密依赖每次构建生成的一个新随机密钥,嵌进服务端包里。

多数部署平台会自动让前后端密钥同步。但如果你用滚动部署、多区域托管,或 CDN 缓存了含服务端岛屿的页面,前后端可能短暂用不同密钥,旧页面加密的 props 就解不开。

这种情况下,用 Astro CLI 生成一个可复用的编码密钥,设为环境变量:

astro create-key

把这个值配成 ASTRO_KEY 环境变量(比如写进 .env),并放进 CI/CD 或托管平台的构建设置里。这样生成的包永远复用同一把密钥,加解密就能对上。

服务端岛屿和整页按需渲染的区别

有人会问:既然按需渲染(第 36 章)也能让页面在请求时生成,干嘛还要服务端岛屿?关键在「粒度」。

整页按需渲染是把「一整个页面」交给服务器在请求时现做。而服务端岛屿只把页面里「慢的那一块」交给服务器,其余部分仍是提前生成好的静态内容。

这么做的甜头有两点。第一,主内容能更早到达浏览器,用户先看到能看的部分;第二,主内容可以被 CDN 或缓存层放心地缓存,因为它不依赖个人信息。只有那块岛屿是「因人而变」的,才单独现取。

所以一个很常见、也很省钱的组合是:整站用 output: 'static'(全静态),再给少数个性化位置加 server:defer 岛屿。你不必为了一点点个性化内容,就把整站改成按需渲染、背上整台服务器的成本。

小结

服务端岛屿是「静态海洋 + 动态岛屿」的另一半:用 server:defer 拆出慢的、个性化的服务端内容,用 slot="fallback" 占位,主内容先出来、动态部分后补。下一章我们看视图过渡,认识 v7 里的 <ClientRouter />