Astro Web Framework
概述
Astro 是一個專為內容豐富的網站(如部落格、文件、作品集、行銷頁面和電子商務)設計的網頁框架。其核心創新是島嶼架構:預設情況下,Astro 不向瀏覽器傳送任何 JavaScript。互動式元件會以孤立的「島嶼」形式選擇性地進行水合(hydrate)。Astro 支援在同一專案中同時使用 React、Vue、Svelte、Solid 等 UI 框架,讓你為每個元件挑選最合適的工具。
何時使用此技能
- 在建置部落格、文件網站、行銷頁面或作品集時使用
- 當效能和核心網頁指標(Core Web Vitals)是首要考量時使用
- 當專案包含大量 Markdown 或 MDX 檔案時使用
- 當需要靜態站點生成(SSG)輸出,並可選擇 SSR 來處理動態路由時使用
- 當使用者詢問
.astro檔案、Astro.props、內容集合或client:指令時使用
運作方式
步驟 1:專案設定
npm create astro@latest my-site
cd my-site
npm install
npm run dev
視需要新增整合套件:
npx astro add tailwind # Tailwind CSS
npx astro add react # React 元件支援
npx astro add mdx # MDX 支援
npx astro add sitemap # 自動產生 sitemap.xml
npx astro add vercel # Vercel SSR 適配器
專案結構:
src/
pages/ ← 基於檔案的路由 (.astro、.md、.mdx)
layouts/ ← 可重複使用的頁面外殼
components/ ← UI 元件 (.astro、.tsx、.vue 等)
content/ ← 型別安全的內容集合 (Markdown/MDX)
styles/ ← 全域 CSS
public/ ← 靜態資產(按原樣複製)
astro.config.mjs ← 框架設定
步驟 2:Astro 元件語法
.astro 檔案頂部有一個代碼圍欄(僅限伺服器端),下方是範本:
---
// src/components/Card.astro
// 此區塊僅在伺服器端執行 — 絕不會在瀏覽器中執行
interface Props {
title: string;
href: string;
description: string;
}
const { title, href, description } = Astro.props;
---
<article class="card">
<h2><a href={href}>{title}</a></h2>
<p>{description}</p>
</article>
<style>
/* 自動作用域至此元件 */
.card { border: 1px solid #eee; padding: 1rem; }
</style>
步驟 3:基於檔案的頁面與路由
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/blog/[slug].astro → /blog/:slug (動態)
src/pages/blog/[...path].astro → /blog/* (捕獲所有)
搭配 getStaticPaths 的動態路由:
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content />
步驟 4:內容集合
內容集合讓你以型別安全的方式存取 Markdown 和 MDX 檔案:
// src/content/config.ts
import { z, defineCollection } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter(p => !p.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
<time>{post.data.date.toLocaleDateString()}</time>
</li>
))}
</ul>
步驟 5:島嶼 — 選擇性水合
預設情況下,UI 框架元件會渲染成靜態 HTML,不含任何 JavaScript。使用 client: 指令來進行水合:
---
import Counter from '../components/Counter.tsx'; // React 元件
import VideoPlayer from '../components/VideoPlayer.svelte';
---
<!-- 靜態 HTML — 不向瀏覽器傳送 JavaScript -->
<Counter initialCount={0} />
<!-- 在頁面載入時立即進行水合 -->
<Counter initialCount={0} client:load />
<!-- 當元件滾動到可視區域時進行水合 -->
<VideoPlayer src="/demo.mp4" client:visible />
<!-- 僅在瀏覽器空閒時進行水合 -->
<Analytics client:idle />
<!-- 僅在特定媒體查詢條件下進行水合 -->
<MobileMenu client:media="(max-width: 768px)" />
步驟 6:版面配置
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = 'My Astro Site' } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<nav>...</nav>
<main>
<slot /> <!-- 頁面內容在此渲染 -->
</main>
<footer>...</footer>
</body>
</html>
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="About Us">
<h1>關於我們</h1>
<p>歡迎來到我們的公司...</p>
</BaseLayout>
步驟 7:SSR 模式(按需渲染)
透過設定適配器來啟用 SSR 以處理動態頁面:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'hybrid', // 'static' | 'server' | 'hybrid'
adapter: vercel(),
});
使用 export const prerender = false 為個別頁面啟用 SSR。
範例
範例 1:含 RSS Feed 的部落格
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: '我的部落格',
description: '最新文章',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.date,
link: `/blog/${post.slug}/`,
})),
});
}
範例 2:API 端點 (SSR)
// src/pages/api/subscribe.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const { email } = await request.json();
if (!email) {
return new Response(JSON.stringify({ error: '需要提供 Email' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
await addToNewsletter(email);
return new Response(JSON.stringify({ success: true }), { status: 200 });
};
範例 3:React 元件作為島嶼
// src/components/SearchBox.tsx
import { useState } from 'react';
export default function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
async function search(e: React.FormEvent) {
e.preventDefault();
const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
setResults(data);
}
return (
<form onSubmit={search}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">搜尋</button>
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</form>
);
}
---
import SearchBox from '../components/SearchBox.tsx';
---
<!-- 立即進行水合 — 這個島嶼是可互動的 -->
<SearchBox client:load />
最佳實踐
- ✅ 盡量將大多數元件保持為靜態
.astro檔案 — 僅對必須互動的元件進行水合 - ✅ 對所有 Markdown/MDX 內容使用內容集合 — 可獲得型別安全性和自動驗證
- ✅ 對於摺疊以下的元件,優先使用
client:visible而非client:load,以減少初始 JavaScript - ✅ 使用
import.meta.env來處理環境變數 — 公開變數請加上PUBLIC_前綴 - ✅ 加入
astro:transitions中的<ViewTransitions />來實現流暢的頁面導航,無需完整的 SPA - ❌ 不要在每個元件上都使用
client:load— 這會破壞 Astro 的效能優勢 - ❌ 不要在會用於客戶端範本的
.astrofrontmatter 中放入機密資訊 - ❌ 在靜態模式下,不要忘記為動態路由添加
getStaticPaths— 否則建置會失敗
安全性備註
.astro檔案中的 frontmatter 程式碼僅在伺服器端執行,絕不會暴露給瀏覽器。- 僅對非敏感值使用
import.meta.env.PUBLIC_*。私有環境變數(無PUBLIC_前綴)絕不會發送給客戶端。 - 使用 SSR 模式時,在執行資料庫查詢或 API 呼叫之前,請驗證所有
Astro.request的輸入。 - 在使用
set:html渲染之前,請清理所有使用者提供的內容 — 它會繞過自動跳脫。
常見陷阱
-
問題: React/Vue 元件的 JavaScript 在瀏覽器中未執行 解決方案: 添加
client:指令(如client:load、client:visible等)— 沒有指令時,元件僅渲染為靜態 HTML。 -
問題: 開發期間,內容更新後
getStaticPaths的資料仍為舊資料 解決方案: Astro 的開發伺服器會監視內容檔案 — 如果content/config.ts的變更未反映,請重新啟動。 -
問題:
Astro.props的型別為any— 沒有自動完成功能 解決方案: 在 frontmatter 中定義Props介面或型別,Astro 會自動推斷它。 -
問題:
.astro元件的 CSS 洩漏到其他元件中 解決方案:.astro<style>標籤中的樣式會自動設定作用域。僅在有意針對子元素時才使用:global()。
相關技能
@sveltekit— 當你需要一個具有反應式 UI 的全端框架(相較於 Astro 的內容焦點)@nextjs-app-router-patterns— 當你需要一個以 React 為主的全端框架@tailwind-patterns— 使用 Tailwind CSS 為 Astro 網站設定樣式@progressive-web-app— 為 Astro 網站添加 PWA 功能
限制
- 僅在任務明確符合上述範圍時使用此技能。
- 請勿將輸出視為針對特定環境的驗證、測試或專家審查的替代方案。
- 如果缺乏所需的輸入、權限、安全邊界或成功標準,請停下來要求澄清。