🏝️ Islands 架构

什么是 Islands 架构

Islands 架构(岛屿架构)是一种现代前端架构模式,由 Jason Miller 在 2019 年提出。它的核心思想是:页面大部分内容是静态 HTML("海洋"),只有少数交互式组件("岛屿")需要 JavaScript hydration。

💡 核心理念

默认发送静态 HTML,只在需要交互的地方"激活"JavaScript,减少不必要的客户端渲染。

架构演进对比

架构 特点 缺点
传统 MPA 服务端渲染、每次跳转刷新页面 体验差、交互弱
CSR (SPA) 客户端渲染、无缝跳转 首屏慢、SEO 差、JavaScript 负担重
SSR 服务端渲染、SEO 好 服务器压力大、TTI 慢(需要 hydration)
Islands 静态 HTML + 局部 hydration 需要新框架支持、学习曲线

Islands 架构工作原理

1. 服务端渲染

页面在服务端渲染成纯 HTML,不包含任何 JavaScript。

<!-- 服务端返回的 HTML --> <html> <body> <header> <nav>...静态导航...</nav> </header> <main> <article> <h1>文章标题</h1> <p>文章内容...</p> </article> </main> <aside data-island="comments"> <div class="comments"> <p>暂无评论</p> </div> </aside> <script type="module" src="/islands/comments.js"></script> </body> </html>

2. 渐进式 Hydration

只有标记为"岛屿"的组件才会加载并执行 JavaScript。

// islands/comments.js import { render } from 'preact'; import Comments from '../components/Comments'; // 只 hydrate 标记为岛屿的元素 const island = document.querySelector('[data-island="comments"]'); if (island) { render(<Comments />, island); }

Islands 架构的优势

优势 说明
🚀 更快的首屏 无需等待 JavaScript 下载执行,HTML 立即可见
📦 更小的 JS 体积 只加载交互组件的 JavaScript
🔍 SEO 友好 完整 HTML 内容,搜索引擎可索引
♿ 可访问性好 无 JavaScript 也能访问核心内容
💰 服务器成本低 大部分内容可 CDN 缓存
📱 低端设备友好 减少 JavaScript 解析执行负担

支持 Islands 架构的框架

框架 语言 特点
Astro JavaScript/TypeScript 框架无关、支持 React/Vue/Svelte 组件、最流行
Fresh TypeScript 基于 Deno、零配置、内置 Islands
Qwik TypeScript 可恢复性(Resumability)、零 hydration
Marko JavaScript 流式 SSR、细粒度 hydration
11ty + Alpine JavaScript 静态站点 + 轻量交互

Astro 示例

1. 安装 Astro

# 创建新项目 npm create astro@latest my-islands-app # 安装 React 集成 npx astro add react # 启动开发服务器 npm run dev

2. 页面结构

--- // src/pages/index.astro import Layout from '../layouts/Layout.astro'; import Header from '../components/Header.astro'; import ProductCard from '../components/ProductCard.jsx'; import Cart from '../components/Cart.jsx'; --- <Layout title="商店"> <Header /> <!-- 静态 Astro 组件 --> <main> <h1>热门商品</h1> <!-- 交互式 React 组件(Island)--> <ProductCard client:load /> <ProductCard client:load /> <!-- 仅在可见时加载 --> <Cart client:visible /> <!-- 空闲时加载 --> <Analytics client:idle /> </main> </Layout>

3. 交互指令

指令 说明
client:load 页面加载时立即 hydrate
client:idle 浏览器空闲时 hydrate
client:visible 元素进入视口时 hydrate
client:media 匹配媒体查询时 hydrate
client:only 仅客户端渲染(跳过 SSR)

Fresh 示例

1. 项目结构

my-fresh-app/ ├── routes/ │ ├── index.tsx # 首页路由 │ ├── products/ │ │ └── [id].tsx # 动态路由 │ └── api/ │ └── cart.ts # API 路由 ├── islands/ │ ├── Counter.tsx # 交互岛屿 │ └── ProductCard.tsx # 产品卡片岛屿 ├── components/ │ └── Header.tsx # 静态组件 └── main.ts # 入口文件

2. 路由文件

// routes/index.tsx import { Handler } from '$fresh/server.ts'; import Counter from '../islands/Counter.tsx'; export default async function handler(req: Request) { return ( <> <h1>欢迎使用 Fresh!</h1> <!-- 自动作为 Island 处理 --> <Counter initialCount={0} /> </> ); }

3. Island 组件

// islands/Counter.tsx import { useState } from 'preact/hooks'; export default function Counter({ initialCount }: { initialCount: number }) { const [count, setCount] = useState(initialCount); return ( <div> <p>计数:{count}</p> <button onClick={() => setCount(count + 1)}> +1 </button> </div> ); }

Qwik 的可恢复性

Qwik 提出了"可恢复性"(Resumability)概念,完全消除了 hydration:

<!-- Qwik 生成的 HTML --> <div q:container="container" q:version="1.0.0" q:state='{"count":0}' > <button q:on:click="handleClick_abc123" > 点击我 </button> </div> // JavaScript 按需加载 // 只有点击时才加载 handleClick 函数

最佳实践

  • 默认静态:除非必要,否则使用静态 HTML
  • 小岛原则:交互组件尽量小且独立
  • 延迟加载:使用 client:visibleclient:idle
  • 组件通信:使用 Custom Events 或全局状态
  • SEO 优先:核心内容必须是静态 HTML
  • 渐进增强:无 JavaScript 时功能降级但不失效

适用场景

场景 推荐度 说明
内容网站 ⭐⭐⭐⭐⭐ 博客、文档、新闻站点
电商网站 ⭐⭐⭐⭐⭐ 产品列表、详情页、购物车
营销页面 ⭐⭐⭐⭐⭐ Landing Page、活动页
SaaS 应用 ⭐⭐⭐⭐ Dashboard、管理后台
实时应用 ⭐⭐⭐ 聊天、协作文档(需大量交互)
游戏/创意应用 ⭐⭐ 需要持续 JavaScript 运行

2026 年新趋势

趋势 描述
部分 Hydration 只 hydrate 组件树中必要的部分
流式 Islands Islands 通过流式传输渐进式加载
边缘 Islands 在边缘节点渲染和缓存 Islands
AI 生成 Islands AI 自动识别并提取交互组件
零 JS Islands 使用 CSS 和 HTML 属性实现简单交互