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——接收nameprop,输出对应的内联 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 里,三条线各自清晰,互不耦合。