Astro Islands 架构深入:选择性注水的革命性方案

4278 字
21 分钟
Astro Islands 架构深入:选择性注水的革命性方案

前言#

传统的 SPA 框架有一个根本性的问题:即使页面上 90% 的内容是静态的,也要为 100% 的内容加载和执行 JavaScript。这意味着一篇博客文章的「点赞按钮」需要和整个页面的渲染框架一起下载、解析、执行。

Astro 的 Islands 架构(岛屿架构)彻底翻转了这个范式——默认零 JavaScript,只为需要交互的组件注入最少量的 JS。这不是渐进增强的老调重弹,而是一套基于「选择性注水(Partial Hydration)」的现代化解决方案。

本文将深入 Islands 架构的原理、Astro 的 client:* 指令体系、多框架集成机制,以及它带来的真实性能优势。

一、Islands 架构的核心思想#

1.1 从 SPA 到 MPA 再到 Islands#

传统 MPA(Multi-Page Application):
┌─────────────────────────────┐
│ 服务端渲染 HTML │ ← 每个页面都是完整 HTML
│ (零或极少 JavaScript) │ ← 交互能力有限
└─────────────────────────────┘
SPA(Single-Page Application):
┌─────────────────────────────┐
│ JavaScript 接管一切 │ ← 客户端渲染
│ (加载大量 JS bundle) │ ← 首屏慢,FCP/LCP 差
└─────────────────────────────┘
SSR + Hydration(Next.js/Nuxt 模式):
┌─────────────────────────────┐
│ 服务端渲染 HTML + 全页注水 │ ← 首屏快,但 TTI 慢
│ (仍然加载全量 JS) │ ← 注水过程阻塞交互
└─────────────────────────────┘
Islands Architecture(Astro 模式):
┌─────────────────────────────┐
│ ┌──────┐ 静态 ┌────────┐ │
│ │Island│ HTML │ Island │ │ ← 静态部分零 JS
│ │ (Vue)│ │(Svelte)│ │ ← 每个岛屿独立注水
│ └──────┘ └────────┘ │ ← 可以混合多框架
│ 静态 HTML 内容 │
│ ┌────────────────────┐ │
│ │ Island (Vanilla) │ │
│ └────────────────────┘ │
└─────────────────────────────┘

1.2 什么是「岛屿」#

在 Islands 架构中,页面被分为两种区域:

  • 静态海洋(Static Sea):纯 HTML/CSS,不需要任何 JavaScript。这是页面的主体——导航栏、文章内容、页脚等。
  • 交互岛屿(Interactive Islands):需要 JavaScript 来实现交互的组件。如搜索框、评论区、轮播图、点赞按钮等。

关键原则:每个岛屿独立加载、独立注水、互不影响。

1.3 选择性注水 vs 全页注水#

// 传统全页注水(Next.js / Nuxt 模式)
// 整个页面的组件树都需要在客户端重新激活
// 服务端输出:
// <html>
// <body>
// <header>...</header> ← 需要 JS 注水
// <nav>...</nav> ← 需要 JS 注水
// <main>
// <article>5000字文章</article> ← 需要 JS 注水(虽然是纯静态的!)
// <LikeButton /> ← 需要 JS 注水
// </main>
// <footer>...</footer> ← 需要 JS 注水
// </body>
// </html>
// 客户端需要下载整个应用的 JS bundle,然后:
// hydrate(<App />, document.getElementById('root'))
// ↑ 遍历整个组件树,绑定事件、恢复状态
// Astro 选择性注水:
// 只有 LikeButton 需要注水,其他都是纯 HTML
// 服务端输出:
// <html>
// <body>
// <header>...</header> ← 纯 HTML,零 JS
// <nav>...</nav> ← 纯 HTML,零 JS
// <main>
// <article>5000字文章</article> ← 纯 HTML,零 JS
// <astro-island> ← 独立注水
// <LikeButton />
// </astro-island>
// </main>
// <footer>...</footer> ← 纯 HTML,零 JS
// </body>
// </html>

二、Astro 的组件模型#

2.1 .astro 组件#

Astro 组件(.astro 文件)是服务端组件,它们在构建时执行,输出纯 HTML:

src/components/ArticleCard.astro
---
// 这里是服务端代码(frontmatter),在构建时执行
interface Props {
title: string;
excerpt: string;
date: string;
slug: string;
tags: string[];
}
const { title, excerpt, date, slug, tags } = Astro.props;
// 可以调用数据库、API 等(构建时执行)
const formattedDate = new Date(date).toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
---
<!-- 这里是模板,编译为纯 HTML -->
<article class="card">
<a href={`/posts/${slug}`}>
<h2>{title}</h2>
<time datetime={date}>{formattedDate}</time>
<p>{excerpt}</p>
<div class="tags">
{tags.map(tag => (
<span class="tag">{tag}</span>
))}
</div>
</a>
</article>
<style>
/* Scoped CSS - 自动添加唯一作用域 */
.card {
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1.5rem;
transition: box-shadow 0.2s;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.tag {
display: inline-block;
padding: 0.25rem 0.5rem;
background: #edf2f7;
border-radius: 0.25rem;
font-size: 0.875rem;
}
</style>

关键点:.astro 组件永远不会发送 JavaScript 到客户端。 它们是纯粹的模板引擎。

2.2 框架组件作为岛屿#

当你需要交互时,可以使用任何前端框架的组件,并通过 client:* 指令将其标记为岛屿:

src/pages/blog/[slug].astro
---
import Layout from '@/layouts/Layout.astro';
import ArticleContent from '@/components/ArticleContent.astro';
// 框架组件
import LikeButton from '@/components/LikeButton.vue';
import CommentSection from '@/components/CommentSection.svelte';
import ShareMenu from '@/components/ShareMenu.vue';
import TableOfContents from '@/components/TableOfContents.vue';
const { slug } = Astro.params;
const post = await getPost(slug);
---
<Layout title={post.title}>
<article>
<!-- 静态内容:零 JS -->
<h1>{post.title}</h1>
<time>{post.date}</time>
<ArticleContent content={post.content} />
<!-- 交互岛屿:各自独立加载 JS -->
<LikeButton client:visible postId={post.id} />
<ShareMenu client:idle url={Astro.url.href} title={post.title} />
<TableOfContents client:media="(min-width: 1024px)" headings={post.headings} />
<CommentSection client:visible postId={post.id} />
</article>
</Layout>

三、client:* 指令深入#

3.1 所有指令及其行为#

Astro 提供了 5 种 client:* 指令,控制岛屿何时被注水:

<!-- 1. client:load - 页面加载后立即注水 -->
<!-- 适用于:首屏可见且需要立即交互的组件 -->
<SearchBar client:load />
<!-- 2. client:idle - 浏览器空闲时注水(requestIdleCallback) -->
<!-- 适用于:不紧急但最终需要的组件 -->
<ShareMenu client:idle />
<!-- 3. client:visible - 组件进入视口时注水(IntersectionObserver) -->
<!-- 适用于:折叠下方的组件,用户滚动到才加载 -->
<CommentSection client:visible />
<!-- 4. client:media - 满足媒体查询条件时注水 -->
<!-- 适用于:响应式场景,如只在桌面端加载的侧边栏 -->
<DesktopSidebar client:media="(min-width: 1024px)" />
<!-- 5. client:only - 跳过 SSR,只在客户端渲染 -->
<!-- 适用于:依赖浏览器 API 的组件(如 canvas、WebGL) -->
<ThreeJSScene client:only="vue" />

3.2 指令的内部实现原理#

Astro 的 client:* 指令在编译时被转换为特定的加载脚本。以下是其内部机制的简化版本:

// Astro 内部:岛屿注水的运行时代码(简化)
// astro-island 自定义元素
class AstroIsland extends HTMLElement {
connectedCallback() {
// 读取指令类型
const directive = this.getAttribute('client');
// 读取组件信息
const componentUrl = this.getAttribute('component-url');
const componentExport = this.getAttribute('component-export') || 'default';
const rendererUrl = this.getAttribute('renderer-url');
const props = JSON.parse(this.getAttribute('props') || '{}');
// 根据指令类型决定注水时机
switch (directive) {
case 'load':
this.hydrate(componentUrl, rendererUrl, componentExport, props);
break;
case 'idle':
this.hydrateOnIdle(componentUrl, rendererUrl, componentExport, props);
break;
case 'visible':
this.hydrateOnVisible(componentUrl, rendererUrl, componentExport, props);
break;
case 'media':
this.hydrateOnMedia(
this.getAttribute('client-value'),
componentUrl, rendererUrl, componentExport, props
);
break;
}
}
async hydrate(componentUrl, rendererUrl, exportName, props) {
// 1. 动态加载组件代码和框架渲染器
const [componentModule, renderer] = await Promise.all([
import(/* @vite-ignore */ componentUrl),
import(/* @vite-ignore */ rendererUrl),
]);
const Component = componentModule[exportName];
// 2. 使用对应框架的渲染器进行注水
// renderer 是框架特定的(如 @astrojs/vue 提供的渲染器)
await renderer.default(this, Component, props, this.innerHTML);
}
hydrateOnIdle(componentUrl, rendererUrl, exportName, props) {
// 使用 requestIdleCallback 延迟注水
const cb = () => this.hydrate(componentUrl, rendererUrl, exportName, props);
if ('requestIdleCallback' in window) {
requestIdleCallback(cb);
} else {
// fallback: 200ms 后执行
setTimeout(cb, 200);
}
}
hydrateOnVisible(componentUrl, rendererUrl, exportName, props) {
// 使用 IntersectionObserver 在组件可见时注水
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
observer.disconnect();
this.hydrate(componentUrl, rendererUrl, exportName, props);
break;
}
}
},
{ rootMargin: '200px' } // 提前 200px 开始加载
);
observer.observe(this);
}
hydrateOnMedia(query, componentUrl, rendererUrl, exportName, props) {
// 使用 matchMedia 在媒体查询匹配时注水
const mql = matchMedia(query);
if (mql.matches) {
this.hydrate(componentUrl, rendererUrl, exportName, props);
} else {
const handler = (e) => {
if (e.matches) {
mql.removeEventListener('change', handler);
this.hydrate(componentUrl, rendererUrl, exportName, props);
}
};
mql.addEventListener('change', handler);
}
}
}
customElements.define('astro-island', AstroIsland);

3.3 编译产物分析#

当 Astro 编译一个包含岛屿的页面时,产物结构如下:

<!-- 构建后的 HTML(简化) -->
<astro-island
uid="abc123"
component-url="/_astro/LikeButton.vue_vue_type_script_setup_true_lang.BxK2n.js"
component-export="default"
renderer-url="/_astro/client.Cm9k1.js"
props='{"postId":[0,42]}'
client="visible"
>
<!-- 服务端预渲染的 HTML(用户立即可见) -->
<button class="like-btn">
<svg>...</svg>
<span>42</span>
</button>
</astro-island>

注意几个关键点:

  1. 组件代码按需加载:只有当注水条件满足时,才会下载组件 JS
  2. 服务端预渲染:岛屿内部有完整的 HTML,用户无需等待 JS 就能看到内容
  3. Props 序列化:组件 props 被序列化为 JSON,注水时传入

3.4 选择合适的指令#

// 决策指南
// ✅ client:load - 首屏关键交互
// 场景:搜索框、导航菜单、需要立即可用的表单
// 代价:阻塞页面加载,慎用
// ✅ client:idle - 次优先级交互
// 场景:分享按钮、主题切换、非紧急工具栏
// 原理:requestIdleCallback,在浏览器空闲时执行
// 通常在页面加载后 50-200ms 内触发
// ✅ client:visible - 折叠下方内容(最常用)
// 场景:评论区、相关文章、无限滚动、图表
// 原理:IntersectionObserver,滚动到附近才加载
// 设置了 200px 的 rootMargin,提前预加载
// ✅ client:media - 响应式条件
// 场景:桌面端侧边栏、移动端底部导航
// 原理:matchMedia,只在条件满足时注水
// ✅ client:only - 纯客户端组件
// 场景:Canvas/WebGL、使用 window/document 的组件
// 注意:不会 SSR,SEO 不友好,仅在必要时使用

四、多框架集成#

4.1 安装多框架支持#

Astro 的一大杀手级特性是同一个项目中可以使用多个框架

Terminal window
# 安装集成
npx astro add vue
npx astro add svelte
npx astro add solid-js
npx astro add lit
astro.config.mjs
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';
import svelte from '@astrojs/svelte';
import solidJs from '@astrojs/solid-js';
import lit from '@astrojs/lit';
export default defineConfig({
integrations: [
vue(),
svelte(),
solidJs(),
lit(),
],
});

4.2 混合使用不同框架#

src/pages/dashboard.astro
---
// 根据组件特点选择最合适的框架
// Vue - 复杂的表单和状态管理
import DataForm from '@/components/vue/DataForm.vue';
// Svelte - 轻量级动画组件
import AnimatedChart from '@/components/svelte/AnimatedChart.svelte';
// Solid - 高频更新的实时数据
import LiveTicker from '@/components/solid/LiveTicker.tsx';
// Lit - Web Component,可复用于任何框架
import CustomTooltip from '@/components/lit/CustomTooltip.ts';
const dashboardData = await fetchDashboardData();
---
<Layout>
<h1>Dashboard</h1>
<!-- 每个组件使用最适合它的框架 -->
<section class="form-section">
<DataForm client:load initialData={dashboardData.form} />
</section>
<section class="charts">
<AnimatedChart client:visible data={dashboardData.chart} />
</section>
<section class="live-data">
<LiveTicker client:load endpoint="/api/ticker" />
</section>
<!-- Lit Web Component 可以在任何地方使用 -->
<CustomTooltip client:idle text="这是一个提示">
<button>Hover me</button>
</CustomTooltip>
</Layout>

4.3 框架渲染器的工作原理#

每个框架集成都提供了一个「渲染器」,负责服务端渲染和客户端注水:

// @astrojs/vue 的渲染器(简化)
// server.js - 服务端渲染
import { createSSRApp, h } from 'vue';
import { renderToString } from 'vue/server-renderer';
export async function renderToStaticMarkup(Component, props, slotted) {
const app = createSSRApp({
render() {
return h(Component, props, {
default: () => slotted?.default ? h('astro-slot', { innerHTML: slotted.default }) : undefined,
});
},
});
const html = await renderToString(app);
return { html };
}
// client.js - 客户端注水
import { createSSRApp, h } from 'vue';
export default async function clientEntrypoint(
element, // astro-island DOM 元素
Component, // Vue 组件
props, // 序列化的 props
slotHTML // 插槽内容的 HTML
) {
// 创建 Vue 应用并注水到已有的 DOM 上
const app = createSSRApp({
render() {
return h(Component, props, {
default: slotHTML
? () => h('astro-slot', { innerHTML: slotHTML })
: undefined,
});
},
});
// 注水(hydrate)而不是全新渲染(mount)
app.mount(element, true); // true = hydrate mode
}

4.4 跨框架通信#

岛屿之间默认是隔离的,但可以通过多种方式通信:

// 方案一:Nano Stores(Astro 推荐的跨框架状态共享方案)
// npm install nanostores @nanostores/vue @nanostores/solid
// stores/cart.ts - 框架无关的 Store
import { atom, computed } from 'nanostores';
export interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
export const $cartItems = atom<CartItem[]>([]);
export const $cartTotal = computed($cartItems, (items) =>
items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
export function addToCart(item: Omit<CartItem, 'quantity'>) {
const items = $cartItems.get();
const existing = items.find(i => i.id === item.id);
if (existing) {
$cartItems.set(
items.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i)
);
} else {
$cartItems.set([...items, { ...item, quantity: 1 }]);
}
}
export function removeFromCart(id: string) {
$cartItems.set($cartItems.get().filter(i => i.id !== id));
}
<!-- Vue 组件中使用 -->
<script setup>
import { useStore } from '@nanostores/vue';
import { $cartItems, $cartTotal, addToCart } from '@/stores/cart';
const cartItems = useStore($cartItems);
const total = useStore($cartTotal);
</script>
<template>
<div class="cart">
<div v-for="item in cartItems" :key="item.id">
{{ item.name }} x{{ item.quantity }} - ¥{{ item.price * item.quantity }}
</div>
<p>总计: ¥{{ total }}</p>
</div>
</template>
// Solid 组件中使用同一个 Store
import { useStore } from '@nanostores/solid';
import { $cartTotal, addToCart } from '@/stores/cart';
function AddToCartButton(props: { product: { id: string; name: string; price: number } }) {
const total = useStore($cartTotal);
return (
<button onClick={() => addToCart(props.product)}>
加入购物车 (当前总计: ¥{total()})
</button>
);
}
// 方案二:Custom Events(轻量级通信)
// 适合简单的事件通知场景
// 发送事件(任意框架组件内)
function notifyCartUpdate(item: CartItem) {
const event = new CustomEvent('cart:update', {
detail: item,
bubbles: true,
});
document.dispatchEvent(event);
}
// 监听事件(任意框架组件内)
// Vue
import { onMounted, onUnmounted } from 'vue';
onMounted(() => {
const handler = (e: CustomEvent) => {
console.log('Cart updated:', e.detail);
};
document.addEventListener('cart:update', handler);
onUnmounted(() => document.removeEventListener('cart:update', handler));
});

五、性能优势量化分析#

5.1 真实场景对比#

以一个典型的技术博客页面为例:

页面组成:
- 导航栏(静态)
- 文章标题和元信息(静态)
- 文章正文 5000 字(静态)
- 代码高亮块 x 10(静态,构建时高亮)
- 目录导航(交互:滚动高亮)
- 点赞按钮(交互:API 调用)
- 评论区(交互:表单+列表)
- 相关文章推荐(静态)
- 页脚(静态)
传统 SSR (Nuxt/Next) 的 JS 产物:
├── framework-runtime.js ~45KB (gzip) ← 框架运行时
├── app.js ~30KB (gzip) ← 应用代码
├── vendor.js ~25KB (gzip) ← 第三方依赖
├── page-blog-slug.js ~15KB (gzip) ← 页面组件
└── Total: ~115KB (gzip) ← 全页注水
Astro Islands 的 JS 产物:
├── toc-widget.js ~3KB (gzip) ← 目录组件
├── like-button.js ~2KB (gzip) ← 点赞组件
├── comment-section.js ~8KB (gzip) ← 评论组件
├── vue-runtime (shared) ~15KB (gzip) ← 框架运行时(仅注水组件需要)
└── Total: ~28KB (gzip) ← 只加载需要的
JS 减少: 75%+

5.2 Core Web Vitals 影响#

指标对比(典型博客页面):
传统 SSR Astro Islands 提升
FCP (First 0.8s 0.6s 25%
Contentful Paint)
LCP (Largest 1.2s 0.8s 33%
Contentful Paint)
TBT (Total 350ms 50ms 86%
Blocking Time)
TTI (Time to 2.5s 0.9s 64%
Interactive)
CLS (Cumulative 0.05 0.02 60%
Layout Shift)

TBT 的巨大差异是关键:传统 SSR 需要解析和执行大量 JS 来完成注水,这期间主线程被阻塞。Astro 只需注水少量岛屿组件,主线程几乎不被阻塞。

5.3 性能优化技巧#

---
// 技巧 1: 预加载关键岛屿的 JS
// 在 <head> 中预加载即将需要的组件
---
<head>
<!-- 预加载 client:load 组件的 JS -->
<link rel="modulepreload" href="/_astro/SearchBar.BxK2n.js" />
</head>
<!-- 技巧 2: 使用 transition:persist 保持岛屿状态 -->
<!-- 在 View Transitions 中,岛屿状态不会丢失 -->
<nav transition:persist>
<ThemeToggle client:load transition:persist />
</nav>
<!-- 技巧 3: 服务端数据预取,避免客户端瀑布 -->
---
// 在服务端获取数据,作为 props 传入
const comments = await fetchComments(post.id);
---
<CommentSection client:visible initialComments={comments} postId={post.id} />
<!-- 评论区注水后已有数据,无需额外请求 -->
---
// 技巧 4: 合理拆分岛屿粒度
// ❌ 不好:整个侧边栏作为一个大岛屿
// <Sidebar client:load /> ← 加载了很多不需要交互的内容的 JS
// ✅ 好:只把需要交互的部分作为岛屿
---
<aside>
<!-- 静态内容 -->
<h3>作者信息</h3>
<p>张三,前端工程师</p>
<!-- 只有搜索框需要 JS -->
<SearchWidget client:idle />
<!-- 静态分类列表 -->
<h3>分类</h3>
<ul>
{categories.map(cat => <li><a href={cat.url}>{cat.name}</a></li>)}
</ul>
<!-- 只有订阅表单需要 JS -->
<NewsletterForm client:visible />
</aside>

六、实战:构建一个完整的博客#

6.1 项目结构#

my-blog/
├── astro.config.mjs
├── src/
│ ├── components/
│ │ ├── astro/ # 静态 Astro 组件
│ │ │ ├── Header.astro
│ │ │ ├── Footer.astro
│ │ │ ├── PostCard.astro
│ │ │ └── Prose.astro
│ │ ├── vue/ # Vue 交互组件
│ │ │ ├── SearchBar.vue
│ │ │ ├── ThemeToggle.vue
│ │ │ └── CommentForm.vue
│ │ └── svelte/ # Svelte 轻量组件
│ │ ├── LikeButton.svelte
│ │ └── ReadingProgress.svelte
│ ├── content/
│ │ └── posts/ # Markdown 文章
│ ├── layouts/
│ │ └── PostLayout.astro
│ ├── pages/
│ │ ├── index.astro
│ │ └── posts/
│ │ └── [...slug].astro
│ └── stores/
│ └── theme.ts # Nano Stores
├── public/
└── package.json

6.2 文章页面实现#

src/pages/posts/[...slug].astro
---
import { getCollection, type CollectionEntry } from 'astro:content';
import PostLayout from '@/layouts/PostLayout.astro';
import Prose from '@/components/astro/Prose.astro';
// 交互岛屿
import ReadingProgress from '@/components/svelte/ReadingProgress.svelte';
import LikeButton from '@/components/svelte/LikeButton.svelte';
import SearchBar from '@/components/vue/SearchBar.vue';
import CommentForm from '@/components/vue/CommentForm.vue';
export async function getStaticPaths() {
const posts = await getCollection('posts');
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
type Props = { post: CollectionEntry<'posts'> };
const { post } = Astro.props;
const { Content, headings } = await post.render();
// 服务端获取点赞数和评论
const [likeCount, comments] = await Promise.all([
fetch(`${import.meta.env.API_URL}/likes/${post.slug}`).then(r => r.json()),
fetch(`${import.meta.env.API_URL}/comments/${post.slug}`).then(r => r.json()),
]);
---
<PostLayout title={post.data.title}>
<!-- 阅读进度条 - 页面加载即需要 -->
<ReadingProgress client:load />
<!-- 搜索 - 空闲时加载 -->
<SearchBar client:idle slot="header-actions" />
<article>
<header>
<h1>{post.data.title}</h1>
<time datetime={post.data.published.toISOString()}>
{post.data.published.toLocaleDateString('zh-CN')}
</time>
<div class="tags">
{post.data.tags.map(tag => (
<a href={`/tags/${tag}`} class="tag">{tag}</a>
))}
</div>
</header>
<!-- 文章内容 - 纯静态 HTML,零 JS -->
<Prose>
<Content />
</Prose>
<!-- 点赞 - 滚动到可见时加载 -->
<LikeButton
client:visible
slug={post.slug}
initialCount={likeCount.count}
/>
<!-- 评论 - 滚动到可见时加载 -->
<CommentForm
client:visible
slug={post.slug}
initialComments={comments}
/>
</article>
</PostLayout>

6.3 View Transitions 集成#

src/layouts/PostLayout.astro
---
import { ViewTransitions } from 'astro:transitions';
import Header from '@/components/astro/Header.astro';
import ThemeToggle from '@/components/vue/ThemeToggle.vue';
---
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{Astro.props.title}</title>
<!-- View Transitions API -->
<ViewTransitions />
</head>
<body>
<Header>
<!-- 主题切换在页面导航时保持状态 -->
<ThemeToggle client:load transition:persist />
</Header>
<main transition:animate="slide">
<slot />
</main>
</body>
</html>

七、Islands 架构的局限性#

7.1 不适合的场景#

❌ 高度交互的 SPA 应用(如 Figma、Google Docs)
→ 整个页面都是交互式的,Islands 没有优势
❌ 需要复杂客户端路由的应用
→ Islands 是 MPA 模式,页面导航是全页刷新(虽然 View Transitions 缓解了这个问题)
❌ 岛屿之间需要大量频繁通信
→ 跨岛屿状态同步有额外复杂度
❌ 需要离线支持的 PWA
→ MPA 模式下 Service Worker 配置更复杂

7.2 何时选择 Islands#

✅ 内容驱动的网站(博客、文档、营销页面)
✅ 电商产品页面(大量静态描述 + 少量交互)
✅ 新闻/媒体网站(内容为主)
✅ 企业官网/落地页
✅ 对 Core Web Vitals 有严格要求的项目
✅ 需要极致首屏性能的场景

总结#

Islands 架构代表了前端性能优化的一次范式转移:

  1. 默认零 JS:页面主体是纯 HTML,性能天花板极高
  2. 选择性注水:5 种 client:* 指令精确控制何时加载 JS
  3. 框架无关:同一页面可以混用 Vue、Svelte、Solid 等
  4. 渐进增强:即使 JS 加载失败,用户仍能看到完整内容
  5. 极致性能:典型场景下 JS 减少 75%+,TBT 降低 80%+

Astro 不是要取代 Vue 或 Svelte——它是让你在正确的地方使用正确的工具。如果你的项目是内容驱动的,Islands 架构几乎是 2026 年的最优选择。

“The best JavaScript is no JavaScript. The second best is the least JavaScript.” —— Islands 架构的哲学

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Astro Islands 架构深入:选择性注水的革命性方案
https://boke.hackerdream.xyz/posts/astro-islands-architecture/
作者
晴天
发布于
2026-03-23
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
125
分类
17
标签
287
总字数
257,955
运行时长
0
最后活动
0 天前

目录