Astro

astro
分类编程
作者Agentic Awesome Skills 社区
许可MIT
评分4.90/5
使用4.6K

Astro Web 框架

概述

Astro 是一个专为内容丰富型网站(如博客、文档、作品集、营销页面和电商网站)设计的 Web 框架。其核心创新在于 群岛架构 (Islands Architecture):默认情况下,Astro 向浏览器发送零 JavaScript。交互式组件会被选择性地作为独立的“群岛”进行注水 (Hydration)。Astro 支持在同一个项目中同时使用 React、Vue、Svelte、Solid 等多种 UI 框架,让你能为每个组件选择最合适的工具。

何时使用此技能

  • 构建博客、文档网站、营销页面或作品集时
  • 性能和 Core Web Vitals 是最高优先级时
  • 项目包含大量 Markdown 或 MDX 文件的内容时
  • 需要 SSG(静态)输出且对动态路由有可选 SSR 需求时
  • 用户询问关于 .astro 文件、Astro.props、内容集合 (Content Collections) 或 client: 指令时

工作原理

第一步:项目搭建

bash
npm create astro@latest my-site
cd my-site
npm install
npm run dev

根据需要添加集成:

bash
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 适配器

项目结构:

code
src/
  pages/          ← 基于文件的路由 (.astro, .md, .mdx)
  layouts/        ← 可复用的页面外壳
  components/     ← UI 组件 (.astro, .tsx, .vue 等)
  content/        ← 类型安全的内容集合 (Markdown/MDX)
  styles/         ← 全局 CSS
public/           ← 静态资源 (原样复制)
astro.config.mjs  ← 框架配置

第二步:Astro 组件语法

.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>

第三步:基于文件的页面与路由

code
src/pages/index.astro          → /
src/pages/about.astro          → /about
src/pages/blog/[slug].astro    → /blog/:slug (动态)
src/pages/blog/[...path].astro → /blog/* (全匹配)

使用 getStaticPaths 的动态路由:

astro
---
// 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 />

第四步:内容集合 (Content Collections)

内容集合让你能够以类型安全的方式访问 Markdown 和 MDX 文件:

typescript
// 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 };

code
astro
---
// 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>

code
### 第 5 步:Islands(孤岛)—— 选择性注水

默认情况下,UI 框架组件会渲染为不含 JS 的静态 HTML。使用 client: 指令来进行注水(Hydrate):

astro
---
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)" />

code
### 第 6 步:布局 (Layouts)
astro
---
// 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>

code
astro
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout title="About Us">
<h1>About Us</h1>
<p>Welcome to our company...</p>
</BaseLayout>

code
### 第 7 步:SSR 模式(按需渲染)

通过设置适配器为动态页面启用 SSR:

javascript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
output: 'hybrid', // 'static' | 'server' | 'hybrid'
adapter: vercel(),
});

code
使用 export const prerender = false 将单个页面设置为 SSR 模式。

示例

示例 1:带有 RSS 订阅的博客

typescript // 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: 'My Blog',
description: 'Latest posts',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.date,
link: /blog/${post.slug}/,
})),
});
}

code
### 示例 2:API 接口 (SSR)
typescript
// 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 required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}

await addToNewsletter(email);
return new Response(JSON.stringify({ success: true }), { status: 200 });
};

code
### 示例 3:作为孤岛的 React 组件
tsx
// 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">Search</button>
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</form>
);
}

code
astro
---
import SearchBox from '../components/SearchBox.tsx';
---
<!-- 立即激活(Hydrated)—— 此岛屿组件具有交互性 -->
<SearchBox client:load />
``

最佳实践

  • ✅ 尽可能将组件保持为静态 .astro 文件 —— 仅对必须交互的部分进行激活(Hydrate)
  • ✅ 对所有 Markdown/MDX 内容使用内容集合(Content Collections) —— 以获得类型安全和自动验证
  • ✅ 对于首屏以下(below-the-fold)的组件,优先使用 client:visible 而非 client:load 以减少初始 JS 体积
  • ✅ 使用 import.meta.env 处理环境变量 —— 公共变量请加上 PUBLIC_ 前缀
  • ✅ 添加 astro:transitions 中的 <ViewTransitions /> 以实现无需完整 SPA 即可流畅的页面导航
  • ❌ 不要给每个组件都加上 client:load —— 这会抵消 Astro 的性能优势
  • ❌ 不要将秘密密钥放在会被客户端模板使用的 .astro frontmatter 中
  • ❌ 在静态模式下,动态路由不要遗漏 getStaticPaths —— 否则构建将失败

安全与保障注意事项

  • .astro 文件中的 frontmatter 代码仅在服务端运行,绝不会暴露给浏览器。
  • 仅对非敏感值使用 import.meta.env.PUBLIC_*。私有环境变量(无 PUBLIC_ 前缀)永远不会发送到客户端。
  • 使用 SSR 模式时,在进行数据库查询或 API 调用前,请验证所有 Astro.request 输入。
  • 在使用 set:html 渲染用户提供的内容前请进行清理(Sanitize) —— 因为它会绕过自动转义。

常见陷阱

  • 问题: React/Vue 组件的 JavaScript 在浏览器中不运行
解决方案: 添加
client: 指令(如 client:loadclient: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 功能

局限性

  • 仅在任务明确符合上述范围时使用此技能。
  • 不要将输出结果视为特定环境验证、测试或专家评审的替代方案。
  • 如果缺少必要的输入、权限、安全边界或验收标准,请停止并请求澄清。