Astro 是什么?一次 JavaScript 框架之旅

更新于 2026-01-22

Alvin BryanDavid Fateh 2024-09-27

Astro 是一个开源的 Web 框架,专注于性能和内容密集型网站,例如落地页、博客、技术文档等。与 Next.js、SvelteKit、Nuxt.js 和 SolidStart 类似,Astro 也有自己的单文件组件(利用了“岛屿架构”,稍后会详细介绍),并为你处理构建过程。

本文将带你全面了解 Astro 及其功能特性。我们将以一个虚构的会议网站为基础,进行一个小型教程。一如既往,你可以在 GitHub 上找到完整的代码。本文内容 100% 兼容 Astro v3。

准备好了吗?让我们开始吧!

为什么选择 Astro 框架?

Astro 自称是一个“一体化”的 Web 框架,提供创建网站所需的一切功能,并且还提供一系列额外集成,供你在需要时进一步自定义项目。它对组件无偏好(component agnostic),适用于静态站点生成(SSG),同时足够灵活,能够支持单页应用(SPA)和多页应用(MPA)。

Astro 官方文档中明确提出了五大核心设计原则,这些原则或许能帮助你判断它是否适合你的项目:

  • 以内容为中心(Content-driven):Astro 围绕展示内容而设计,因此非常适合需要快速触达受众的内容密集型网站。
  • 服务端优先(Server-first):Astro 优先采用服务端渲染(SSR),而非浏览器中的客户端渲染,以提升网站运行速度。
  • 默认快速(Fast by default):通过预渲染为静态 HTML,Astro 实现了快速加载时间和增强的 SEO 效果,在注重用户参与度和转化率的场景中尤为有用。
  • 易于使用(Easy-to-use):Astro 力求对所有开发者友好,“无论技能水平或过往 Web 开发经验如何”。它的 UI 语言是 HTML 的超集,但融合了 JSX、React、Svelte 和 Vue 等其他 JavaScript 框架语言的特性。
  • 面向开发者(Developer-focused):作为一个开源项目,Astro 投入了大量资源开发开发者工具,并自豪地宣称其社区支持的文档已翻译成 14 种语言。

Astro 的独特之处在于将速度与灵活性集于一身——它是如何做到的呢?接下来,我们将深入探讨 Astro 最引人注目的几项能力。

Astro 默认不输出任何 JavaScript

与其他一些框架不同,Astro 是“HTML 优先”的,默认情况下不输出任何 JavaScript,并同时支持静态站点生成(SSG)和服务端渲染(SSR)。Astro 利用这种轻盈和速度来优化内容加载时间。

Astro 组件与 Svelte 类似:JavaScript、HTML 和 CSS 被清晰地分离开来。它不使用 JSX。例如:

---
// ./index.astro
// 示例 Astro 组件。你可以在 --- 块内编写 JavaScript
// 这里的所有代码都会在服务端运行
export let name = 'Astro';
---

<h1 class="title">Hello {name}</h1>

<style>
  .title {
    font-family: "Lato", sans-serif;
  }
</style>

“万能框架”(The Framework of All Frameworks)

Astro 不仅仅是一个高级的静态站点生成器。我喜欢把它称为“万能框架”——并非指它凌驾于其他框架之上,而是指它的兼容性。Astro 在 JavaScript 框架中独树一帜,因为它支持其他 UI 框架。

没错,你可以直接在 Astro 中导入用 React、Preact、Svelte、Vue、Lit 或 Solid 编写的组件,甚至可以在同一个文件中混合使用它们。

---
// 从不同框架导入组件
import SvelteNavbar from './components/SvelteNavbar.svelte';
import ReactPostList from './components/ReactPostList.jsx';
import VueFooter from './components/VueFooter.vue';
---

<article>
  <header>
    <SvelteNavbar />
  </header>

  <main>
    <ReactPostList />
  </main>

  <footer>
    <VueFooter />
  </footer>
</article>

是的,Astro 支持来自多个框架的组件,这简直不可思议。这一切只需运行以下命令即可实现:

npx astro add @astrojs/react @astrojs/svelte @astrojs/vue

而且,由于 Astro 默认不输出任何 JavaScript,因此每引入一个新框架并不会增加打包体积。每个组件都会被服务端渲染为静态 HTML,因此你无需担心不同语言之间的互操作性问题。

交互性与“岛屿” 🏝️

如果你需要交互性,只需添加 client:load 指令,框架的运行时就会在客户端加载。这是“岛屿架构”(island architecture)的一种实现。

岛屿架构的核心思想是:首先构建以静态 HTML 为主的网站,然后将交互性(及其关联的 JavaScript)限制在特定区域。这些区域是相互隔离的“岛屿”,会在主静态内容加载完成后才加载。通过这种方式,Astro 既能作为静态站点生成器,又能为整个网站提供强大的交互选项。

Astro 组件

如前所述,除了可以复用 React、Svelte 等框架编写的组件外,Astro 还拥有自己的组件系统。下面我们深入了解一下 Astro 组件的工作原理。

以下是一个用于展示会议演讲的基本组件示例:

---
// TalkCard.astro
const { title = 'Learn Astro', time = '2023-09-23T13:30:00' } = Astro.props;
---

<div>
  <h2>{title}</h2>
  <p>{date}</p>
</div>

组件的前置代码块(front matter)包含服务端将要运行的 JavaScript,然后将结果数据传递给 HTML。

我们的组件期望接收 nametime 两个属性,我们通过 Astro.props 设置了默认值("Learn Astro" 和 "2023-09-23T13:30:00")。然后,我们在 HTML 中使用 {} 来引用这些值,就像在 Svelte 或其他模板文件中一样。这只是一个示例。

传递值(Props)

我们可以像这样从父组件向子组件传递值/属性:

---
// ./src/component/TalkGrid
// 使用标准 import 语法导入 TalkCard 组件
import TalkCard from '../components/TalkCard.astro';

// 演讲列表
let talks = [
  { title: 'Learn Astro', date: '2023-09-23T13:30:00' },
  { title: 'Learn Vue.js', date: '2023-09-23T14:30:00' },
];
---

<section>
  {talks.map( (talk) => {
    return <TalkCard title={talk.title} date={talk.date} />
  })}
</section>

基于文件的路由(File-based Routing)

与其他框架一样,src/pages 目录中的文件结构会直接映射为网站的页面。以下是我们 Astro 网站的一个示例目录结构:

.
└── src
    ├── components
        └── TalkCard.astro
    ├── layout
    └── pages
        ├── about.md
        └── index.astro

我们之前提到的 index.astro 文件将成为网站的首页。那么那个 about.md 文件是什么呢?我们将在下一节看到。但在那之前,先聊聊动态路由。

动态路由(Dynamic Routing)

在很多场景下,你并不希望为网站的每个页面都手动创建单独的文件。你的内容可能来自无头 CMS 或内容平台(比如 Contentful)的 API,或者你可能使用了某种动态数据。

这就是所谓的“动态路由”,Astro 通过在文件名中使用 [] 语法来支持这一功能。动态路由参数使 Astro 能够自动创建多个页面——当你需要大量同类型的页面时(例如产品规格页、作者简介页,或者在我们的会议示例中,演讲者简介页),这非常有用。要了解更多关于动态路由的信息,请查阅 Astro 官方文档。

Markdown、MDX 与布局(Layouts)

还记得我们之前看过的目录结构吗?Astro 允许你直接将 Markdown 和 MDX 文件转换为网站页面。你只需在前置元数据(front matter)中指定一个 layout 值即可。

一个关于页面的 Markdown 文件如下所示:

---
title: About
layout: ../layouts/MarkdownPage.astro
---

We're a series of developer conferences that happen all around the world.

那么这个 layout 属性是什么意思呢?

布局(Layouts)

在 Astro 中(与 Svelte 和 Qwik 类似),你可以使用 <slot> 元素来指定子组件应插入的位置。

例如,通常你会有一个包含基础 HTML、导航栏和页脚的组件:

// 示例文件
// ./layout/Main.astro
import Footer from '../components/Footer.astro'

const {title} = Astro.site;
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link
      rel="icon"
      href="https://contentful.com/favicon-32x32.png"
      type="image/png"
    />
    <meta name="generator" content={Astro.generator} />
    <title>{title}</title>

    <meta name="title" content={title} />
    <meta name="description" content={description} />
  </head>
  <body>
    <slot /> <!-- 网站其余内容将插入此处 -->
    <Footer />
  </body>
</html>

布局组件在技术上与其他组件并无区别,但它专为容纳其他组件而设计。

MDX

Astro 通过集成支持 MDX。MDX 被称为“组件时代的 Markdown”,允许你在 Markdown 文档中插入组件。

import Chart from './Chart.astro'

# Hello

This is _markdown_ and **bold text** and everything. Except we can add components.

<Chart />

More text...

在 Astro 中,上面的 <Chart /> 组件将是一个 Astro 组件。

你可以通过以下命令安装 MDX 集成来启用它:

npx astro add mdx

MDX 还有更多功能。同样,你可以查阅 Astro 文档了解更多。

内容集合(Content Collections)

让我们继续以一个假设的会议网站 Astro 项目为例。

你为每场演讲创建了一个 Markdown 文件,内容如下:

title: Learn Astro
date: 2023-09-23T13:30:00
---

Astro is fun!

现在,将此扩展到 20 场演讲。如果其中一个文件中你把 title 错打成了 titlr 会怎样?你的构建过程可能会失败,或者某人的演讲标题可能无法显示。

如果框架能提前警告你这些问题就好了,对吧?这正是 Astro 的“内容集合”(Content Collections)功能所解决的问题。

内容集合为 Markdown 文件增加了类型检查、必填属性等特性,大大降低了出错概率。

下面,我们将示例转换为内容集合。

1. 设置内容文件夹

src 目录下创建一个名为 content 的新文件夹,如下所示:

.
└── src
    ├── content # src/content 文件夹是保留目录
    ├── components
    ├── layouts
    └── pages
        ├── about.md
        └── index.astro

在该文件夹中添加你的 Markdown 文件。

2. 配置集合

现在,在 ./src/content/config.js 中创建一个配置文件。(也可以是 TypeScript 文件。)

// ./src/content/config.js
// 1. 从 `astro:content` 导入所需库
import { z, defineCollection } from "astro:content";

// 2. 定义你的集合
const talk = defineCollection({
  schema: z.object({
    title: z.string(),
    date: z.date(),
  }),
});

// 3. 导出 `collections` 对象
export const collections = {
  talk,
};

现在,我们的演讲已被定义为一个集合,接下来需要稍微调整代码以在应用中显示它们。

3. 更新代码

更新你的组件代码,使其如下所示:

---
import { getCollection } from "astro:content";
import TalkCard from "../components/TalkCard.astro";

let talks = await getCollection("talk");
---

<ul role="list" class="link-card-grid">
  {
    talks.map(({ data }) => { // 信息现在存储在 "data" 属性中
      return (
        <TalkCard
          title={data.title}
          date={data.date}
          description={data.description}
        />
      );
    })
  }
</ul>

引用(References)

在处理 Markdown 文件时,引用一直是个老大难问题。以我们的会议网站为例,如果我们有一系列演讲,每场都关联一位演讲者的名字,那么 Markdown 前置元数据看起来会是这样:

---
title: Learn Astro
date: 2023-09-23T13:30:00
speaker: Alvin
---

但如果一位演讲者有两场不同的演讲呢?或者如果你想为每位演讲者创建一个包含详细信息的专属页面呢?这与博客中作者信息面临的问题相同。解决方案要么是重复信息(比如两次写演讲者名字),要么是为演讲者创建某种 ID,在一个文件中引用它,并在另一个文件中编码实现关联。

Astro 通过内容集合为你解决了这个问题。

首先,让我们为演讲者创建第二个集合,使配置文件如下所示:

// 1. 从 `astro:content` 导入所需库
import { z, defineCollection, reference } from "astro:content";

// 2. 定义你的集合
const talk = defineCollection({
  schema: z.object({
    title: z.string(),
    date: z.date(),
  }),
});

// 添加 `speaker` 集合
const speaker = defineCollection({
  schema: z.object({
    name: z.string(),
    title: z.string(),
    twitter: z.string().optional(),
  }),
});

// 3. 导出 `collections` 对象
export const collections = {
  talk,
  speaker,
};

./src/content/speaker 中至少创建一个演讲者的 Markdown 文件。

src/content/
├── config.js
├── speaker
│   ├── alvin.md
│   └── harshil.md
└── talk
    ├── learn-astro.md
    └── learn-vue.md

然后,在配置文件中为演讲集合添加一个引用。

const talk = defineCollection({
  schema: z.object({
    title: z.string(),
    date: z.date(),
    // ✨ 这就是神奇之处
    // 通过文件名引用 `speaker` 集合中的演讲者
    // 别忘了从 "astro:content" 导入 { reference }
    speaker: reference("speaker"),
  }),
});

现在,如果你忘记在演讲的 Markdown 文件中添加演讲者,Astro 会告诉你,并显示一条(很有帮助的!)错误信息:

引用(References)

Markdoc

内容集合还允许你使用 Markdoc,它有点像 MDX。

Markdoc 是 Astro 的另一个集成,允许你使用简码(shortcode)语法在 Markdown 中添加 Astro 组件。

自定义组件被包裹在类似 {% tags %} 的标签中。

以下是在 Markdoc 中使用 Highlight 组件的示例:

# Welcome to Markdoc 👋

{% highlight color="yellow" %}

This text will be *highlighted* in your docs.

{% /highlight %}

混合渲染(Hybrid Rendering)

最初,我们的会议网站只是一堆零散的 Markdown 文件。接着,我们使用内容集合为其赋予了结构。然后,我们通过处理“引用”问题增加了复杂性。

现在,让我们再增加一层复杂性。如果你需要一个依赖 API 的动态页面怎么办?如果你想显示会议举办地的实时天气预报呢?你不可能每次天气变化就重建整个网站吧?(尤其是在北欧生活的话)。

这时,“混合渲染”(Hybrid Rendering)就派上用场了。

通过混合渲染,你可以指定哪些路由应为静态,哪些应在服务器上动态渲染。

1. 启用混合渲染

你可以在全局 Astro 配置文件 ./astro.config.mjs(注意不是之前创建的 ./content/config.js)中添加 output: "hybrid" 来启用混合渲染。

// ./astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  output: "hybrid",
});

在生产环境中,你需要使用一个 SSR 适配器,可以是 Vercel、Deno,或是 Astro 团队提供的其他适配器。

启用后,一切应照常工作,因为静态站点渲染仍是默认行为。

2. 创建新页面

让我们为会议创建一个新的“场地”(venue)页面,并为其请求一些动态数据。

---
// ./src/pages/venue.astro
let name = "Starry Skies Outdoor Theater";
let location = "123 Park Ave, CityTown";
let capacity = 100;
let facilities = ["Close to public transports", "Wheelchair accessible", "Public toilets", "Free Wifi"];
---

<section>

<div class="theater">
  <h2>{name}</h2>
  <p>{location}</p>
  <p>Capacity: {capacity}</p>

  <div class="details" style="display: none;">
    <h3>Facilities</h3>
    <ul>
      {facilities.map((facility) => (
        <li>{facility}</li>
      ))}
    </ul>
  </div>
</div>

3. 添加动态数据

现在,让我们调用一个 API 来获取假设场地的日出日落时间。这是一个简单且人为构造的例子,只是为了展示这项技术的潜力。

以下是最终文件:

---
// ./src/pages/venue.astro
import Layout from "../layouts/Layout.astro";

// 禁用静态输出
export const prerender = false;

// 调用外部 API 获取动态数据
let res = await fetch(
  "https://api.sunrise-sunset.org/json?lat=36.7201600&lng=-4.4203400"
);

let { results } = await res.json();
let { sunrise, sunset } = results;

let name = "Starry Skies Outdoor Theater";
let location = "123 Park Ave, CityTown";
let capacity = 100;
let facilities = [
  "Close to public transports",
  "Wheelchair accessible",
  "Public toilets",
  "Free Wifi",
];
---

<Layout title="Venue | Fake Astro Conf">
  <section>
    <h2>Venue</h2>

    <p>This year, our conference takes place at the magnificent {name}</p>
    <p>Luckily for us, it's summer, the days are super long!</p>
    <p>The sun rises at <b>{sunrise}</b> and sets at <b>{sunset}</b></p>
  </section>
  <hr />

  <div class="theater">
    <h2>{name}</h2>
    <p>{location}</p>
    <p>Capacity: {capacity}</p>

    <div class="details">
      <h3>Facilities</h3>
      <ul>
        {facilities.map((facility) => <li>{facility}</li>)}
      </ul>
    </div>
  </div>
</Layout>

理解 Astro 的优势

我们已经看到了 Astro 能为网站构建带来的一些最有趣的功能,以及如何实现它们。显然,这个框架远不止是一个静态站点生成器。那么,如果你选择 Astro JS,一旦项目上线运行,你能为你的项目和整个网站带来哪些好处呢?

  • SEO 价值:由于 Astro 以静态 HTML 为主,其页面比重度依赖 JavaScript 的页面更容易被搜索引擎抓取,因此通常能凭借更高的自然流量排名获得优势。
  • 开发民主化:只要你能写 HTML,就能用 Astro 开发网站。该框架还支持 TypeScript,配备多功能 CLI,并可通过各种插件和集成进行扩展,让你在需要时逐步增加复杂性。作为一个开源语言,你还可以进一步推动 Astro 以满足定制化需求。
  • 功能深度:Astro 开发者开箱即用地访问大量额外功能,包括多语言内容展示和自动图像优化等能力。
  • 简洁与灵活并存:Astro 让你能够快速、大规模地发布内容,而不会被细节或复杂的配置难题绊倒。同时,当你需要调整页面或在静态 HTML 上叠加交互性时,它也提供了充足的定制潜力。
  • 加载优先级:Astro 能够控制“岛屿架构”的加载顺序。这意味着更重要的页面组件(如页眉和图片标题)可以优先于次要组件(如侧边栏、页脚内容或评论区)加载。

归根结底,Astro JS 适合那些需要兼顾速度与动态性的 Web 开发者,同时又不想牺牲所要展示内容的深度与丰富性。该框架让你能够以简单的资源和专业知识起步,并在需要时逐步为静态站点叠加复杂性,包括交互式组件。

另一方面,如果你是一位经验更丰富的 Web 开发者,对开发静态 HTML 网站不感兴趣,或者你的项目已知具有极高的交互性或复杂的显示需求,那么 Astro 可能不是适合你的 Web 框架。在这种情况下,值得考虑 Next.js、Vue.js 或 React 等替代方案。