chore: import zh skill astro
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- Skill 名称:`astro`
|
||||
- 中文类目:静态技术文档站搭建与部署
|
||||
- 上游仓库:`sickn33__antigravity-awesome-skills`
|
||||
- 上游路径:`plugins/antigravity-awesome-skills-claude/skills/astro/SKILL.md`
|
||||
- 上游链接:https://github.com/sickn33/antigravity-awesome-skills/blob/HEAD/plugins/antigravity-awesome-skills-claude/skills/astro/SKILL.md
|
||||
- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理
|
||||
- 原作者、版权和许可证信息以上游仓库为准
|
||||
@@ -0,0 +1,363 @@
|
||||
---
|
||||
name: astro
|
||||
description: "使用 Astro 构建以内容为中心的网站 —— 默认零 JavaScript、岛屿架构、多框架组件,支持 Markdown/MDX。"
|
||||
category: frontend
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-03-18"
|
||||
author: suhaibjanjua
|
||||
tags: [astro, ssg, ssr, islands, content, markdown, mdx, performance]
|
||||
tools: [claude, cursor, gemini]
|
||||
---
|
||||
|
||||
# Astro Web 框架
|
||||
|
||||
## 概述
|
||||
|
||||
Astro 是一个专为内容密集型网站设计的 Web 框架 —— 博客、文档、作品集、营销站点和电子商务。其核心创新在于**岛屿架构**:默认情况下,Astro 不向浏览器发送任何 JavaScript。交互式组件会被选择性地水合为独立的"岛屿"。Astro 支持在同一项目中同时使用 React、Vue、Svelte、Solid 及其他 UI 框架,让你可以为每个组件选择最合适的工具。
|
||||
|
||||
## 何时使用本技能
|
||||
|
||||
- 当构建博客、文档站点、营销页面或作品集时使用
|
||||
- 当性能和 Core Web Vitals 是最高优先级时使用
|
||||
- 当项目包含大量 Markdown 或 MDX 文件时使用
|
||||
- 当用户询问 `.astro` 文件、`Astro.props`、内容集合或 `client:` 指令时使用
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 第 1 步:项目设置
|
||||
|
||||
```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 适配器
|
||||
```
|
||||
|
||||
项目结构:
|
||||
|
||||
```
|
||||
src/
|
||||
pages/ ← 基于文件的路由(.astro、.md、.mdx)
|
||||
layouts/ ← 可复用的页面外壳
|
||||
components/ ← UI 组件(.astro、.tsx、.vue 等)
|
||||
content/ ← 类型安全的内容集合(Markdown/MDX)
|
||||
styles/ ← 全局 CSS
|
||||
public/ ← 静态资源(原样复制)
|
||||
astro.config.mjs ← 框架配置
|
||||
```
|
||||
|
||||
### 第 2 步: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>
|
||||
```
|
||||
|
||||
### 第 3 步:基于文件的页面与路由
|
||||
|
||||
```
|
||||
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 />
|
||||
```
|
||||
|
||||
### 第 4 步:内容集合
|
||||
|
||||
内容集合让你可以对 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 };
|
||||
```
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
### 第 5 步:岛屿 —— 选择性水合
|
||||
|
||||
默认情况下,UI 框架组件会渲染为不含 JavaScript 的静态 HTML。使用 `client:` 指令进行水合:
|
||||
|
||||
```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)" />
|
||||
```
|
||||
|
||||
### 第 6 步:布局
|
||||
|
||||
```astro
|
||||
---
|
||||
// src/layouts/BaseLayout.astro
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
const { title, description = '我的 Astro 站点' } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
</head>
|
||||
<body>
|
||||
<nav>...</nav>
|
||||
<main>
|
||||
<slot /> <!-- 页面内容在此渲染 -->
|
||||
</main>
|
||||
<footer>...</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```astro
|
||||
---
|
||||
// src/pages/about.astro
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="关于我们">
|
||||
<h1>关于我们</h1>
|
||||
<p>欢迎来到我们的公司...</p>
|
||||
</BaseLayout>
|
||||
```
|
||||
|
||||
### 第 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(),
|
||||
});
|
||||
```
|
||||
|
||||
使用 `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: '我的博客',
|
||||
description: '最新文章',
|
||||
site: context.site,
|
||||
items: posts.map(post => ({
|
||||
title: post.data.title,
|
||||
pubDate: post.data.date,
|
||||
link: `/blog/${post.slug}/`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 示例 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: '需要提供邮箱' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
await addToNewsletter(email);
|
||||
return new Response(JSON.stringify({ success: true }), { status: 200 });
|
||||
};
|
||||
```
|
||||
|
||||
### 示例 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">搜索</button>
|
||||
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```astro
|
||||
---
|
||||
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 的性能优势
|
||||
- ❌ 不要在面向客户端的模板中使用的 `.astro` 文件围栏中放入密钥
|
||||
- ❌ 在静态模式下不要跳过动态路由的 `getStaticPaths` —— 否则构建将失败
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
- `.astro` 文件中的围栏代码仅在服务端运行,绝不会暴露给浏览器。
|
||||
- 仅对非敏感值使用 `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` —— 无自动补全
|
||||
**解决方法:** 在围栏中定义 `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 功能
|
||||
|
||||
## 限制说明
|
||||
- 仅当任务明确符合上述范围时才使用本技能。
|
||||
- 请勿将本技能的输出视为替代特定环境的验证、测试或专家审查。
|
||||
- 如果缺少必要的输入、权限、安全边界或成功标准,请停下来询问澄清。
|
||||
Reference in New Issue
Block a user