Astro Islands 架構師
Astro 的理念是「預設輸出 HTML,僅對需要 JS 的部分進行水合」。對於行銷網站、文件、以及內容密集型網站,這能產出比 React/Next.js 小得多的套件。整個關鍵在於選擇哪些元件是靜態的,哪些是島嶼。
使用時機
- 行銷網站、部落格、文件入口——內容即產品。
- 從一個用戶端 JavaScript 過重的 Next.js 行銷網站遷移。
- 多框架——在 Vue 或 Svelte 元件旁同時使用 React 元件庫。
- 搭配 MDX 與型別安全的前置資料進行大量內容編寫。
- 無需 SPA 即可實現跨頁面視圖轉場。
核心能力
頁面結構
---
// src/pages/blog/[slug].astro — 前置資料在建置/SSR 階段執行
import { getCollection, getEntry } from 'astro:content';
import Layout from '../../layouts/Default.astro';
import Newsletter from '../../components/Newsletter.tsx';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((p) => ({ params: { slug: p.slug }, props: { post: p } }));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<Layout title={post.data.title}>
<h1>{post.data.title}</h1>
<article><Content /></article>
{/* 島嶼 — 僅此元件會傳送 JS */}
<Newsletter client:visible />
</Layout>
--- 分隔線內的程式碼是伺服器端程式碼。在 <...> 中,預設輸出靜態 HTML。帶有 client:* 指令的元件會成為島嶼。
Client 指令——選擇正確的指令
| 指令 | 水合時機 | 用途 |
|---|---|---|
client:load | 頁面載入時 | 網頁上方互動區域(購物車、搜尋)。 |
client:idle | 瀏覽器閒置時 | 非關鍵互動(聊天小工具)。 |
client:visible | 滾動到可視區域時 | 網頁下方區域(新聞訂閱、留言)。 |
client:media="(min-width: 768px)" | 媒體查詢符合時 | 僅限桌面的小工具。 |
client:only="react" | 跳過 SSR;僅用戶端渲染 | 在掛載時需要瀏覽器 API 的元件。 |
client:idle 是大多數非關鍵島嶼的預設值。client:load 是重武器,請保留使用。
內容集合
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content', // markdown/MDX 檔案
schema: z.object({
title: z.string().max(120),
description: z.string().max(160),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
cover: z.object({ src: z.string(), alt: z.string() }).optional(),
}),
});
export const collections = { blog };
現在 getCollection('blog') 具有完整的型別推斷。前置資料錯誤會在建置階段失敗,而非上線後才出錯。
const posts = (await getCollection('blog'))
.filter((p) => !p.data.draft)
.sort((a, b) => +b.data.date - +a.data.date);
多框架
npx astro add react vue svelte
---
import ReactSearch from '../components/Search.tsx';
import VueChart from '../components/Chart.vue';
import SvelteToggle from '../components/Toggle.svelte';
---
<ReactSearch client:idle />
<VueChart client:visible />
<SvelteToggle client:load />
每個框架的執行環境會獨立打包,並且僅在使用了該框架的頁面上載入。對於一個大部分內容都是靜態的文件網站,這可能意味著大部分頁面都不需要傳送任何 React JavaScript。
伺服器島嶼(Astro 4+)
<Layout>
<Header />
<PersonalizedRecommendations server:defer>
<p slot="fallback">正在載入推薦...</p>
</PersonalizedRecommendations>
<Footer />
</Layout>
server:defer 會立即渲染頁面的其餘部分,並透過一個單獨的請求串流式傳輸延遲元件的 HTML。對於那些若採用整體 SSR 會強迫整個頁面都進行伺服器渲染的個人化內容非常有用。
影像最佳化
---
import { Image } from 'astro:assets';
import cover from '../assets/cover.jpg';
---
<Image src={cover} alt="封面照片" widths={[400, 800, 1200]} sizes="(max-width: 768px) 400px, 1200px" />
Astro 會產生 srcset,使用 Sharp 進行縮放,並僅傳送所需的圖片。將圖片放在 src/assets/ 下以獲得完整最佳化;public/ 中的圖片則不會被處理。
視圖轉場
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
...
</html>
<a href="/about" transition:name="hero">關於</a>
無需 SPA 即可實現跨頁面轉場。瀏覽器負責大部分工作;Astro 則協調元素匹配。
搭配元件的 MDX
---
title: 我的文章
date: 2026-04-30
---
import Callout from '../../components/Callout.astro';
# {frontmatter.title}
<Callout type="warn">
這是伺服器端渲染的警告。沒有 JavaScript。
</Callout>
在 MDX 中匯入的元件會在建置/SSR 階段執行,除非它們是島嶼。
混合 SSR
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'hybrid',
adapter: cloudflare(),
});
---
// src/pages/api/search.ts — 在邊緣執行
export const prerender = false;
export async function GET({ request }) {
const url = new URL(request.url);
const q = url.searchParams.get('q') ?? '';
const results = await search(q);
return new Response(JSON.stringify(results), { headers: { 'content-type': 'application/json' } });
}
---
prerender = false 將特定路由設為 SSR;網站其餘部分保持靜態。
反模式
到處使用 client:load
症狀: Lighthouse 的 JavaScript 套件膨脹;Astro 的水合優勢消失。
診斷: 工程師因為 client:load 最熟悉而將其設為預設。
修正: 預設使用 client:idle 或 client:visible。將 client:load 保留給網頁上方的互動元件。
在客戶端元件中抓取資料
症狀: 每個頁面都有載入中的旋轉動畫;SEO 受損。
診斷: 資料抓取移到 React 元件中,而非放在 Astro 的前置資料區域。
修正: 在 --- 分隔線中抓取資料,在伺服器端渲染,然後將資料作為 props 傳遞給島嶼。
未定義型別的前置資料
症狀: 建置成功;但上線後標題顯示 undefined。
診斷: 沒有內容集合架構;前置資料的拼寫錯誤未被發現。
修正: 使用 defineCollection 搭配 zod schema。前置資料錯誤會使建置失敗。
混合使用 public/ 和 src/assets/ 存放圖片
症狀: 部分圖片有最佳化,部分則以原始大小傳送。
診斷: public/ 中的檔案未經處理直接提供;只有 src/assets/ 會經過 Sharp 處理。
修正: 將圖片移到 src/assets/。到處使用 <Image> 元件。public/ 僅用於 OG 圖片和 favicon。
嘗試將 Astro 用於 SPA
症狀: 不斷與水合機制搏鬥;島嶼不斷擴大,直到整個頁面都變成互動式。 診斷: 選錯了框架。Astro 擅長處理內容;SPA 需要 Next/Remix。 修正: 當大部分頁面都是互動式時,選擇 Next/Remix。將 Astro 用於行銷和文件網站。
過度使用 client:only
症狀: 每次頁面載入時發生版面位移(CLS);內容出現前有「閃爍」現象。
診斷: 跳過 SSR 意味著瀏覽器在 JavaScript 執行前看到一個空的佔位符。
修正: 僅在元件真的無法在伺服器端渲染時才使用 client:only。提供一個能近似最終大小的佔位符。
品質檢查清單
- 預設 client 指令為
client:idle或client:visible;client:load的使用應有正當理由。 - 所有內容都透過
defineCollection定義了型別架構。 - 圖片存放在
src/assets/下並使用<Image>元件。 - 僅在元件實際存在時才加入多框架整合(
@astrojs/react等)。 - 在最慢的代表性頁面上,最大內容繪製(LCP)低於 2 秒。
- 每個頁面設定了首次載入的 JavaScript 預算;CI 在預算超標時失敗。
- MDX 的靜態部分使用 Astro 元件;僅在需要互動的地方使用島嶼。
- 已在主要導覽路徑上測試過視圖轉場功能。
不適用於
- 完整 SPA — Next.js, Remix, TanStack Start 更適合。
- Astro 外掛/整合開發 — 獨立技能(目前尚無專屬技能)。
- Vite 建置調校 — Astro 底層使用 Vite。→ 套件大小、HMR、外掛生命週期問題請參考
vite-build-optimizer。 - 重度客戶端狀態應用(儀表板、即時工具)— JavaScript 太多,島嶼效益有限。
- 原生行動應用 — Astro 僅限於網頁。