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

{title}

{description}

``` ### 第 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(); ---

{post.data.title}

``` ### 第 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()); --- ``` ### 第 5 步:岛屿 —— 选择性水合 默认情况下,UI 框架组件会渲染为不含 JavaScript 的静态 HTML。使用 `client:` 指令进行水合: ```astro --- import Counter from '../components/Counter.tsx'; // React 组件 import VideoPlayer from '../components/VideoPlayer.svelte'; --- ``` ### 第 6 步:布局 ```astro --- // src/layouts/BaseLayout.astro interface Props { title: string; description?: string; } const { title, description = '我的 Astro 站点' } = Astro.props; --- {title}
...
``` ```astro --- // src/pages/about.astro import BaseLayout from '../layouts/BaseLayout.astro'; ---

关于我们

欢迎来到我们的公司...

``` ### 第 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 (
setQuery(e.target.value)} />
    {results.map(r =>
  • {r.title}
  • )}
); } ``` ```astro --- import SearchBox from '../components/SearchBox.tsx'; --- ``` ## 最佳实践 - ✅ 尽可能将大多数组件保留为静态 `.astro` 文件 —— 仅对必须交互的部分进行水合 - ✅ 对所有 Markdown/MDX 内容使用内容集合 —— 可获得类型安全和自动校验 - ✅ 对首屏以下的组件优先使用 `client:visible` 而非 `client:load`,以减少初始 JavaScript 体积 - ✅ 使用 `import.meta.env` 管理环境变量 —— 公开变量以 `PUBLIC_` 为前缀 - ✅ 使用 `astro:transitions` 中的 `` 实现流畅的页面导航,无需完整的 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` 的 `