← 返回文章
FrontendAstroTypeScript 2026-05-13 10:00 10 min read

Astro v5 实践指南

以一个真实静态站为样本,拆解 Astro v5 的核心概念:文件路由、组件 Frontmatter、布局复用、TypeScript 数据层与 Tailwind CSS v4 集成。

官方文档: docs.astro.build

1. 什么是 Astro

Astro 是一个面向内容的前端框架,默认输出零 JavaScript 的纯静态 HTML。它的核心设计理念是 Islands Architecture(孤岛架构)——页面大部分是静态 HTML,只有明确标记为 client:* 的组件才会在浏览器中注水(hydrate)。

对于以内容为主的站点(文章、作品集、文档、小工具集),Astro 是极佳的选择:构建产物体积小、 首屏快、SEO 友好,同时又能按需接入 React / Vue / Svelte 等 UI 框架。

本站(一个收纳小游戏、工具和互动实验的个人创意网站)完整使用 Astro v5 构建, 搭配 Tailwind CSS v4 和 TypeScript strict 模式,全程纯静态输出,无任何服务端运行时。

安装方式:

npm create astro@latest
npm run dev      # 启动开发服务器,默认 http://localhost:4321
npm run build    # 构建静态文件到 dist/

2. 项目配置文件

项目根目录下的 astro.config.mjs 是唯一的构建配置入口。本站的配置极简:设置站点根 URL,并通过 @tailwindcss/vite Vite 插件接入 Tailwind CSS v4(不再需要 PostCSS 配置)。

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

export default defineConfig({
  site: "https://example.com",
  vite: {
    plugins: [tailwindcss()]   // Tailwind CSS v4 通过 Vite 插件接入
  }
});

Tailwind v4 与 v3 的集成方式完全不同:不再有 tailwind.config.*, 所有设计令牌改为在 CSS 文件里用 @theme 声明。

3. 文件路由

Astro 采用 文件即路由 的约定:src/pages/ 目录下每一个 .astro 文件 直接对应一条 URL,无需任何路由配置。

src/
└── pages/
    ├── index.astro          → /
    ├── about.astro          → /about/
    ├── articles/
    │   ├── index.astro      → /articles/
    │   └── my-post.astro    → /articles/my-post/
    ├── games/
    │   ├── index.astro      → /games/
    │   └── snake.astro      → /games/snake/
    └── 404.astro            → 404 页面

本站的结构就是这样组织的:src/pages/games/snake.astro 对应 /games/snake/src/pages/articles/index.astro 对应 /articles/。 新增页面只需在对应目录新建文件,零配置。

4. 组件与 Frontmatter

每个 .astro 文件由两部分组成:

  • Frontmatter(组件脚本):夹在 --- 之间,运行在构建时(或 SSR 时的服务端),不打包进客户端 JS。可以任意 import、fetch、做数据处理。
  • 模板部分:Frontmatter 下方,写法类似 JSX 的 HTML,用 {} 插值,支持 map / 条件渲染等。
---
// 1. 导入依赖
import BaseLayout from "../../layouts/BaseLayout.astro";
import { articles } from "../../data/articles";

// 2. 运行时逻辑(在服务器/构建时执行,不打包进客户端 JS)
const article = articles.find((a) => a.href === "/articles/my-post/")!;
const latestArticles = articles
  .toSorted((a, b) => b.date.localeCompare(a.date))
  .slice(0, 3);
---

<!-- 3. 模板部分 -->
<BaseLayout title={article.title}>
  <h1>{article.title}</h1>
  <p>{article.description}</p>
</BaseLayout>

本站所有文章页面都遵循这一模式:Frontmatter 里从数据层 find 当前文章元数据,模板里渲染标题、标签、正文。

5. 布局(Layouts)

Astro 没有特殊的 "Layout API"——布局就是普通组件,惯例放在 src/layouts/ 目录。 布局通过 <slot /> 接收子页面传入的内容,等同于 Vue 的 slot 或 React 的 children

本站只有一个全站布局 BaseLayout.astro, 负责输出完整的 HTML 骨架、导航栏和页脚,所有页面共用:

---
// src/layouts/BaseLayout.astro
import "../styles/global.css";

// 接收外部传入的 props,并提供默认值
const {
  title = "我的网站",
  description = "一句话简介",
  showChrome = true     // 控制是否渲染导航和页脚
} = Astro.props;

// 通过 Astro.url 获取当前路径,用于高亮导航
const currentPath = Astro.url.pathname;
const isCurrent = (href: string) =>
  href === "/" ? currentPath === "/" : currentPath.startsWith(href);
---

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>{title}</title>
    <meta name="description" content={description} />
  </head>
  <body>
    {showChrome && <header><!-- 导航 --></header>}
    <main>
      <slot />   <!-- 子页面内容插入这里 -->
    </main>
    {showChrome && <footer><!-- 页脚 --></footer>}
  </body>
</html>

在子页面中使用:

---
// src/pages/games/snake.astro
import BaseLayout from "../../layouts/BaseLayout.astro";
---

<BaseLayout title="贪吃蛇 | 我的网站" description="方向键控制的经典小游戏">
  <h1>贪吃蛇</h1>
  <!-- 页面正文内容 -->
</BaseLayout>

注意两个常用的内置对象:

  • Astro.props:父组件传入的 props,TypeScript 可标注类型。
  • Astro.url:当前请求的 URL 对象,可读取 .pathname 等属性,常用于高亮当前导航项。

6. 组件(Components)

组件惯例放在 src/components/, 写法与页面完全一致,只是不直接对应路由。 本站的核心组件有:

  • SiteItemList.astro——接收 SiteItem[] 数据,渲染统一的图标列表。
  • AppIcon.astro——接收 name prop,输出对应的内联 SVG 图标。
  • PageHero.astro——接收 eyebrow / title / copy,渲染统一的页面标题区。
---
// src/components/SiteItemList.astro
import type { SiteItem } from "../data/siteItems";
import AppIcon from "./AppIcon.astro";

// Astro.props 接收父组件传入的数据
const { items } = Astro.props as { items: SiteItem[] };
---

<table>
  <tbody>
    {items.map((item) => (
      <tr>
        <td>
          <a href={item.href}>
            <AppIcon name={item.visual} class="size-5" />
            <strong>{item.title}</strong>
            <span>{item.description}</span>
          </a>
        </td>
        <td>{item.label}</td>
      </tr>
    ))}
  </tbody>
</table>

在父页面中使用:

---
import SiteItemList from "../components/SiteItemList.astro";
import { siteItems } from "../data/siteItems";

const games = siteItems.filter((item) => item.section === "games");
---

<SiteItemList items={games} />

7. TypeScript 数据层

Astro 原生支持 TypeScript,不需要额外配置。本站将所有内容数据集中放在 src/data/

  • siteItems.ts——游戏 / 工具 / 实验的卡片数据,包含 TypeScript 类型约束。
  • articles.ts——文章列表元数据(标题、描述、日期、标签、阅读时长)。

这种模式的优势在于:数据结构有类型保护,任何页面直接 import 使用, 新增内容只改一处数据文件,首页、列表页、详情页全部自动更新。

// src/data/siteItems.ts
export type SiteItem = {
  title: string;
  description: string;
  href: string;
  section: "games" | "tools" | "experiments";
  label: string;
  visual: "snake" | "ring" | "pinball" | "search" | "tomato";
};

export const siteItems: SiteItem[] = [
  {
    title: "贪吃蛇",
    description: "方向键、食物、分数和一点手速。",
    href: "/games/snake/",
    section: "games",
    label: "经典",
    visual: "snake"
  },
  // ...更多条目
];

在页面中使用:

---
// 在任意 .astro 文件中直接 import,TypeScript 类型全程保护
import { siteItems } from "../data/siteItems";
import { articles } from "../data/articles";

const games       = siteItems.filter((i) => i.section === "games");
const latestPost  = articles.toSorted((a, b) => b.date.localeCompare(a.date))[0];
---

8. Tailwind CSS v4 集成

Tailwind v4 不再使用 tailwind.config.*, 改为在 CSS 文件中用原生 CSS 自定义属性管理设计令牌。全站样式入口是 src/styles/global.css, 并在 BaseLayout.astro 中 import,一次引入全站生效。

/* src/styles/global.css */
@import "tailwindcss";   /* Tailwind CSS v4 入口 */

/* 用 @theme 扩展 / 覆盖设计令牌 */
@theme {
  --font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
}

/* CSS 自定义属性:品牌色、纸张层次、阴影等 */
:root {
  --accent:        #0758ff;
  --ink:           #08111a;
  --muted:         #667085;
  --paper:         #fbfcff;
  --paper-elevated: rgba(255, 255, 255, 0.84);
  --line:          #dfe5ee;
}

/* 可复用的工具类(配合 Tailwind 的 class:list 使用)*/
.brand-shell {
  border: 1px solid var(--line-strong);
  background: var(--paper-elevated);
  backdrop-filter: blur(20px);
}

在模板中,Tailwind 工具类与 CSS 变量无缝混用:

<!-- 混合使用 Tailwind 工具类和自定义 CSS 变量 -->
<div class="brand-shell rounded-lg p-6 hover:shadow-[0_22px_60px_rgba(7,88,255,0.13)]">
  <h2 class="text-xl font-semibold text-[color:var(--ink)]">标题</h2>
  <p  class="mt-2 text-sm text-[color:var(--muted)]">描述</p>
</div>

本站定义了几个高复用的工具类(.brand-shell.brand-chip.surface-* 系列), 封装了带毛玻璃效果的卡片、标签和面板的通用外观。

9. Astro 内置组件

Astro 提供了少量开箱即用的内置组件,通过 astro:components 导入。 本站文章页大量使用了 <Code />—— 它基于 Shiki 实现语法高亮,支持 100+ 语言和所有主流 VS Code 主题,零运行时开销(构建时渲染为 HTML):

---
import { Code } from "astro:components";   // Astro 内置代码高亮组件
---

<Code
  code={`npm install astro`}
  lang="bash"
  theme="github-dark"
  class="article-code-block p-5 text-sm leading-7"
/>

常用参数:code(代码字符串)、 lang(语言)、 theme(主题), 以及普通的 class 可直接附加在容器元素上。

10. 客户端脚本

Astro 组件的 Frontmatter 运行在构建时,不能操作 DOM。需要在浏览器执行的逻辑, 直接在模板里写 <script> 即可,Astro 会自动打包并注入到页面。

<!-- 在 .astro 模板底部内联 <script>,自动打包进客户端 -->
<script>
  const header = document.getElementById("site-header");
  if (!header) return;
  let lastY = window.scrollY;

  window.addEventListener("scroll", () => {
    const y = window.scrollY;
    // 向下滚超过 80px 时隐藏导航栏
    header.style.transform = y > lastY && y > 80
      ? "translateY(-100%)"
      : "translateY(0)";
    lastY = y;
  }, { passive: true });
</script>

本站导航栏的「向下滚动自动隐藏」交互就是这样实现的:Frontmatter 构建导航 HTML, <script> 在浏览器里监听 scroll 事件, 两者各司其职、互不干扰。

11. class:list 指令

Astro 内置 class:list 指令, 用于优雅地拼接条件类名,避免手写字符串拼接。接受字符串、对象({key: bool}) 和数组的混合形式:

---
const isActive = true;
---

<!-- class:list 指令:优雅地拼接条件类名 -->
<a
  class:list={[
    "px-4 py-2 rounded-md text-sm font-medium",
    isActive
      ? "text-slate-950 after:absolute after:bg-blue-600"   // 激活样式
      : "text-slate-600 hover:text-slate-950"                // 默认样式
  ]}
>
  导航链接
</a>

本站导航栏的「当前页高亮」正是用 class:list 结合 isCurrent(href) 实现的, 代码干净,不需要任何运行时状态。

12. 总结:核心心智模型

概念 位置 一句话
页面 src/pages/ 文件即路由,零配置
布局 src/layouts/ 带 <slot /> 的普通组件,复用 HTML 骨架
组件 src/components/ 可接受 props,构建时渲染,无运行时开销
数据 src/data/ TypeScript 文件,直接 import,类型安全
样式 src/styles/ Tailwind v4 + CSS 变量,global.css 全站生效
交互 <script> in .astro 模板中内联脚本,Astro 自动打包注入

Astro 的设计哲学一致贯穿以上所有层次:默认零 JS,按需加载,构建时做尽量多的工作。 对于内容驱动的网站,这套心智模型极为高效——数据在 TypeScript 文件里, 页面在 pages/ 里,样式在 global.css 里,三条线各自清晰,互不耦合。