首页 / Astro 教程 / .astro 组件结构:frontmatter 与模板

Astro 教程

.astro 组件结构:frontmatter 与模板

本教程共 56 篇 · 第 6 篇 · 更新于 2026-08-07 · 约 10 分钟阅读

AstroAstro 教程.astro 组件frontmatter组件脚本组件模板群岛架构

本节目标:搞清楚一个 .astro 文件长什么样,它由哪两块组成,以及它最终怎么变成你浏览器里看到的网页。

写 Astro 网站,你打交道最多的是一种后缀叫 .astro 的文件。官方管它叫 Astro 组件(Astro component)。它既是页面,也是可复用的 UI 零件,还是布局的外壳,全靠这种文件来写。

本章先不管它怎么用,只把它的“身体结构”拆开看明白。一个 .astro 文件,从头到尾就由两大部分拼成。

什么是 Astro 组件

直白点说,Astro 组件就是一个“带一点点逻辑的 HTML 模板”。它本身不依赖任何前端框架,也不需要浏览器里的运行时。你写一个 .astro 文件,它可以小到只是几行 HTML,比如一组通用的 <meta> 标签,方便做 SEO;也可以大到一个完整的页头、一张资料卡片;甚至可以当作整页布局,或者放进 src/pages/ 文件夹里直接变成一整个页面。

它最关键的脾气是:组件不在浏览器里运行。它在“构建期”(你打包网站时)或“按需渲染”时,在服务器一侧把 HTML 算好,再发给用户。你写在组件里的 JavaScript,最后会被 Astro 从发给浏览器的页面里“剥掉”。结果就是:网站更快,而且默认零 JavaScript 负担。

Note

这正是 Astro 引以为傲的 群岛架构(Islands Architecture) 的底层逻辑——默认不发 JS,只在需要交互的地方用 岛屿(island) 单独加载。组件本身是安静的 HTML,这是后话。

当组件确实需要一点交互,比如点一下按钮有反应,你有两种办法:在模板里加标准的 HTML <script> 标签(第 10 章讲),或者把 React / Vue 之类的框架组件当作“客户端岛屿”塞进来。

组件的两大块:脚本与模板

每个 .astro 文件,从上到下分成两块:组件脚本(Component Script)组件模板(Component Template)。它们分工不同,但合在一起就够你搭出任何想要的东西。

最基本的空组件长这样:

---
// 组件脚本(写 JavaScript)
---

<!-- 组件模板(写 HTML + JS 表达式) -->

中间那对 --- 叫“代码围栏(code fence)”。上面是脚本区,下面是模板区。下面我们一块一块看。

组件脚本:写在围栏里的逻辑

Astro 用一对 --- 把“组件脚本”框起来。如果你写过 Markdown,可能对 frontmatter 这个概念不陌生——Astro 的组件脚本正是从它那儿得到的灵感。所以本书里把这块叫 frontmatter(不翻译),记牢这个词的拼写。

在脚本区,你可以写任何用来“算模板”的 JavaScript:

  1. 引入别的 Astro 组件;
  2. 引入别的框架组件,比如 React 写的一个 .jsx
  3. 引入数据,比如一个本地的 JSON 文件;
  4. 从接口或数据库取数据;
  5. 创建变量,待会儿在模板里用。

看一个具体例子:

---
import SomeAstroComponent from '../components/SomeAstroComponent.astro';
import SomeReactComponent from '../components/SomeReactComponent.jsx';
import someData from '../data/pokemon.json';

// 拿到外面传进来的 props,比如 <X title="Hello, World" />
const { title } = Astro.props;

// 连私有接口或数据库也能在这里请求
const data = await fetch('SOME_SECRET_API_URL/users').then(r => r.json());
---

<!-- 你的模板写在这里 -->

这里有两个初学者容易忽略的点:

第一,脚本跑在服务器。 你在围栏里写的代码,是在构建或请求时于“服务器一侧”执行的,最终不会进到浏览器。所以你可以放心写耗性能或敏感的逻辑,比如直连私有数据库——它绝不会落到用户手里。

第二,围栏是“隔离栏”。 它的设计目的就是保证你写的 JS 被关在里面,不逃到前端、不落进用户浏览器。这也正是 Astro 默认零 JS 的底气。

Tip

因为脚本区的代码不会进浏览器,所以别指望在这里写的东西能直接在 <script> 里用。想从脚本传值给浏览器端,要用 data-* 属性中转,第 10 章会专门讲。

组件模板:决定最终 HTML

围栏下面那块就是模板区,它决定了组件输出什么 HTML。你在这里写普通 HTML,组件被别处引入使用时,就会原样渲染这些 HTML。

但模板不只是死 HTML。Astro 的模板语法还支持四类东西:

  • JavaScript 表达式:像 {title} 这样把脚本里的变量插进来;
  • <style><script> 标签:组件级的样式与脚本(第 10、11 章展开);
  • 引入进来的组件:直接写 <MyComponent /> 就能用;
  • Astro 特殊指令:比如 client:* 这种客户端指令(后文讲岛屿时会遇到)。

脚本里定义的变量和值,在模板里都能用来拼出动态 HTML。比如:

---
import Banner from '../components/Banner.astro';
import Avatar from '../components/Avatar.astro';
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';

const myFavoritePokemon = [/* ... */];
const { title } = Astro.props;
---

<!-- HTML 注释支持 -->
{/* JS 注释语法也有效 */}

<Banner />
<h1>Hello, world!</h1>

<!-- 用脚本里的变量 -->
<p>{title}</p>

<!-- 用服务端岛屿延迟渲染,并提供加载占位 -->
<Avatar server:defer>
  <svg slot="fallback" class="generic-avatar" transition:name="avatar">...</svg>
</Avatar>

<!-- 给框架组件加客户端指令来水合 -->
<ReactPokemonComponent client:visible />

<!-- 像 JSX 那样把 HTML 和 JS 表达式混写 -->
<ul>
  {myFavoritePokemon.map((data) => <li>{data.name}</li>)}
</ul>

<!-- 用指令从字符串甚至对象拼出 class -->
<p class:list={["add", "dynamic", { classNames: true }]} />

这一段把模板能做的几件事都点到了:插变量、延迟渲染(服务端岛屿,server:defer)、客户端水合(client:visible)、循环生成列表、动态拼 class。

Note

上面出现的 server:defer 属于 服务端岛屿 的用法,client:visible 属于 客户端指令(让岛屿在可见时才 水合)。这些是 Astro 交互能力的核心,本章先混个眼熟,后面章节会逐个讲透。

组件可以层层嵌套

组件天生是“可复用、可组合”的。你可以在一个组件里用另一个组件,一层层搭出复杂界面。举个简单的:用一个 Button 组件,再拼出一个 ButtonGroup

---
import Button from './Button.astro';
---

<div>
  <Button title="Button 1" />
  <Button title="Button 2" />
  <Button title="Button 3" />
</div>

这就是 Astro 写界面的基本节奏:小块拼大块,大块再拼页面。理解了“脚本算数据、模板出 HTML”这个二分结构,后面所有章节都是在这两块里添枝加叶。

一个能直接跑的完整组件

把前面说的「脚本 + 模板」合起来,一个真实可用的页头组件大概长这样:

---
// Header.astro
const navItems = [
  { label: "首页", href: "/" },
  { label: "博客", href: "/blog" },
  { label: "关于", href: "/about" },
];
interface Props {
  siteTitle: string;
}
const { siteTitle } = Astro.props;
---

<header>
  <strong>{siteTitle}</strong>
  <nav>
    {navItems.map((item) => (
      <a href={item.href}>{item.label}</a>
    ))}
  </nav>
</header>

<style>
  header { display: flex; gap: 1rem; align-items: center; }
  nav a { margin-right: 0.75rem; }
</style>

脚本里准备了导航数据和从外部传进来的站点名,模板把它们拼成 <header>。下面那块 <style> 是组件级样式,Astro 会自动给它「加作用域」,不会污染别的组件(第 11 章细讲)。

在别的页面里这样用就行:

---
import Header from '../components/Header.astro';
---

<Header siteTitle="码上学的博客" />

组件文件怎么命名

约定上,Astro 组件文件名用「大驼峰」(PascalCase),比如 Header.astroButton.astroPostCard.astro。这不是强制,但社区都这么写,你照着来,别人一眼就知道这是个组件。页面文件(放 src/pages/)则通常用小写或中划线,因为它们同时是网址。

小结

一个 .astro 文件 = 组件脚本(frontmatter,跑在服务器,默认不进浏览器)+ 组件模板(输出 HTML,支持表达式、引入组件、样式脚本与特殊指令)。它不在客户端运行,所以默认零 JS;需要交互时,再用 <script> 或框架岛屿来补。下一章我们专门看模板里的“表达式与动态属性”怎么写。