Skip to content

Astro 入门指南

Astro 是什么?#

🙋 想象一下这样一个场景:你需要搭建一个技术博客,内容主要是 Markdown 文章,偶尔需要一些交互组件(比如代码高亮、目录导航)。用 React/Vue 全家桶?太重了,一个简单的博客页面要加载几百 KB 的 JavaScript。用纯静态 HTML?又失去了组件化开发的便利。

Astro 就是为解决这个痛点而生的。它是一个内容驱动的 Web 框架,核心理念是:

默认零 JavaScript,按需加载交互

这意味着 Astro 生成的页面默认只有 HTML 和 CSS,不会打包任何 JavaScript 到客户端。只有当你明确标记某个组件需要交互时,才会加载对应的 JS 代码。

岛屿架构(Islands Architecture)#

🎯 这是 Astro 最核心的设计理念。传统 SPA 框架把整个页面当作一个 JavaScript 应用,而 Astro 把页面看作一片”HTML 海洋”,其中散落着几个需要交互的”岛屿”。

┌─────────────────────────────────────────┐
│ Header (静态 HTML) │
├─────────────────────────────────────────┤
│ │
│ ┌──────────┐ 文章内容 │
│ │ 搜索组件 │ (静态 HTML/Markdown) │
│ │ (交互岛) │ │
│ └──────────┘ │
│ │
│ ┌──────────────┐ │
│ │ 评论区 │ │
│ │ (交互岛) │ │
│ └──────────────┘ │
├─────────────────────────────────────────┤
│ Footer (静态 HTML) │
└─────────────────────────────────────────┘

这种架构的好处是:

与主流框架的对比#

特性AstroNext.jsNuxt
默认输出静态 HTMLJS BundleJS Bundle
客户端 JS按需加载全量加载全量加载
框架绑定无(可混用)ReactVue
适用场景内容站点Web 应用Web 应用
学习曲线

🤔 什么时候选择 Astro?

什么时候选择 Astro?

为什么选择 Astro?#

零 JavaScript 默认输出#

Astro 构建的页面默认不包含任何 JavaScript。这不是噱头,而是实实在在的性能收益。以当前博客为例,一篇普通文章页面的资源加载:

Terminal window
# 典型 Astro 博客页面
index.html 15 KB (gzip)
styles.css 8 KB (gzip)
# JavaScript 只在需要时加载

对比一个类似的 Next.js 博客:

Terminal window
# 典型 Next.js 博客页面
index.html 5 KB
_app.js 150 KB (gzip)
framework.js 45 KB (gzip)
page.js 30 KB (gzip)

多框架组件支持#

Astro 不绑定任何 UI 框架,你可以在同一个项目中使用:

---
// 同一个 Astro 页面中混用多个框架
import ReactCounter from '../components/ReactCounter.jsx'
import VueTimer from '../components/VueTimer.vue'
import SvelteForm from '../components/SvelteForm.svelte'
---
<main>
<ReactCounter client:load />
<VueTimer client:idle />
<SvelteForm client:visible />
</main>

这在迁移老项目或团队有不同技术背景时特别有用。

内容集合与类型安全#

Astro 5 引入了 Content Layer API,为 Markdown/MDX 内容提供完整的类型支持:

src/content.config.ts
import { defineCollection, z } from 'astro:content'
import { glob } from 'astro/loaders'
const blog = defineCollection({
loader: glob({ base: './src/content/blog', pattern: '**/*.mdx' }),
schema: z.object({
title: z.string().max(60),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
})

定义好 Schema 后,编辑器会自动提示 Frontmatter 字段,拼写错误也会在构建时被捕获。

快速开始#

环境要求#

Terminal window
# 检查 Node 版本
node -v # 应该输出 v18.x 或 v20.x 以上
# 推荐使用 pnpm(当前博客使用的包管理器)
npm install -g pnpm

创建项目#

Astro 提供了交互式脚手架,几条命令就能创建项目:

Terminal window
# 使用 pnpm 创建(推荐)
pnpm create astro@latest
# 或者使用 npm
npm create astro@latest
# 或者使用 yarn
yarn create astro

脚手架会询问几个问题:

┌ astro v5.1.0
◆ Where should we create your new project?
│ ./my-astro-blog
◆ How would you like to start your new project?
│ ● Use blog template (推荐初学者)
│ ○ Include sample files
│ ○ Empty
◆ Do you plan to write TypeScript?
│ ● Yes (推荐)
│ ○ No
◆ How strict should TypeScript be?
│ ● Strict (推荐)
│ ○ Strictest
│ ○ Relaxed
◆ Install dependencies?
│ ● Yes
│ ○ No
◆ Initialize a new git repository?
│ ● Yes
│ ○ No

项目结构详解#

创建完成后,你会得到这样的目录结构:

my-astro-blog/
├── public/ # 静态资源(原样复制到输出目录)
│ └── favicon.svg
├── src/
│ ├── components/ # 组件目录
│ │ └── Card.astro
│ ├── content/ # 内容集合目录
│ │ └── blog/
│ │ └── first-post.md
│ ├── layouts/ # 布局组件
│ │ └── Layout.astro
│ ├── pages/ # 页面路由(文件即路由)
│ │ └── index.astro
│ ├── styles/ # 全局样式
│ └── content.config.ts # 内容集合配置
├── astro.config.mjs # Astro 配置
├── package.json
└── tsconfig.json # TypeScript 配置

🎯 几个关键目录的作用:

第一个页面#

.astro 文件基本语法#

Astro 组件使用 .astro 扩展名,文件结构分为两部分:

---
// 这里是「组件脚本」(Frontmatter)
// 在服务端执行,不会发送到客户端
import Layout from '../layouts/Layout.astro'
const title = 'Hello Astro'
const items = ['苹果', '香蕉', '橙子']
---
<!-- 这里是「组件模板」 --><!-- 类似 JSX,但有一些差异 -->
<Layout title={title}>
<h1>{title}</h1>
<ul>
{items.map((item) => <li>{item}</li>)}
</ul>
</Layout>
<style>
/* 组件样式(默认 scoped) */
h1 {
color: purple;
}
</style>

🔶 注意: Frontmatter 使用三个短横线 --- 包裹,这和 Markdown 的 Frontmatter 语法一致,但作用不同——Astro 的 Frontmatter 是 JavaScript/TypeScript 代码。

Frontmatter 脚本区域#

Frontmatter 中可以做的事情:

---
// 1. 导入组件和模块
import Header from '../components/Header.astro'
import { formatDate } from '../utils/date'
// 2. 获取组件 Props
interface Props {
title: string
date: Date
}
const { title, date } = Astro.props
// 3. 获取 URL 参数
const { slug } = Astro.params
// 4. 请求外部数据(在构建时执行)
const response = await fetch('https://api.example.com/posts')
const posts = await response.json()
// 5. 定义局部变量
const formattedDate = formatDate(date)
const isProduction = import.meta.env.PROD
---

🤔 一个常见疑问:Frontmatter 中的代码何时执行?

模板表达式语法#

模板部分支持类似 JSX 的表达式:

---
const name = 'Astro'
const isLoggedIn = true
const items = [1, 2, 3]
---
<!-- 变量插值 -->
<h1>Hello, {name}!</h1>
<!-- 条件渲染 -->
{isLoggedIn && <p>欢迎回来!</p>}
{isLoggedIn ? <Dashboard /> : <LoginForm />}
<!-- 列表渲染 -->
<ul>
{items.map((item) => <li>Item {item}</li>)}
</ul>
<!-- 动态属性 -->
<div class={isLoggedIn ? 'user' : 'guest'}>
<img src={`/avatars/${name}.png`} alt={name} />
</div>
<!-- 展开属性 -->
{/* 将对象所有属性展开为元素属性 */}
<Component {...props} />

🔶 与 JSX 的主要差异:

JSXAstro
classNameclass
htmlForfor
{/* 注释 */}{/* 注释 */}<!-- HTML注释 -->
必须有根元素可以有多个根元素

开发工作流#

常用命令#

Terminal window
# 启动开发服务器(热更新)
pnpm dev
# 构建生产版本
pnpm build
# 预览构建结果
pnpm preview
# 类型检查
pnpm astro check
# 同步内容集合类型
pnpm astro sync

热更新机制#

Astro 的开发服务器基于 Vite,提供极速的热更新体验:

Terminal window
# 开发服务器默认运行在 4321 端口
pnpm dev
# Local: http://localhost:4321/

TypeScript 配置#

Astro 内置 TypeScript 支持,推荐使用 strict 模式:

tsconfig.json
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"] // 路径别名
}
}
}

路径别名 ~/* 让导入更简洁:

---
// 不用写相对路径
import Header from '~/components/Header.astro'
import { SITE } from '~/config'
// 而不是
// import Header from '../../components/Header.astro'
---

实战:理解当前博客项目结构#

让我们看看这个博客项目(基于 AntfuStyle 主题)是如何组织的。

项目目录概览#

blog/
├── plugins/ # Remark/Rehype 插件
├── public/ # 静态资源
├── scripts/ # 构建脚本
├── src/
│ ├── assets/ # 图片等资源
│ ├── components/ # UI 组件
│ │ ├── base/ # 基础组件 (Head, Footer, Link)
│ │ ├── backgrounds/ # 动画背景组件
│ │ ├── nav/ # 导航组件
│ │ ├── views/ # 视图组件 (渲染文章等)
│ │ └── widgets/ # 小部件 (返回顶部、返回等)
│ ├── content/ # 内容集合
│ │ ├── blog/ # 博客文章
│ │ ├── changelog/ # 更新日志
│ │ ├── projects/ # 项目数据
│ │ └── schema.ts # Schema 定义
│ ├── layouts/ # 布局组件
│ ├── pages/ # 页面路由
│ ├── styles/ # 全局样式
│ ├── utils/ # 工具函数
│ ├── config.ts # 站点配置
│ └── content.config.ts # 内容集合配置
├── astro.config.ts # Astro 配置
├── uno.config.ts # UnoCSS 配置
└── package.json

关键配置文件分析#

astro.config.ts - Astro 核心配置:

astro.config.ts
import { defineConfig } from 'astro/config'
import sitemap from '@astrojs/sitemap'
import unocss from 'unocss/astro'
import astroExpressiveCode from 'astro-expressive-code'
import mdx from '@astrojs/mdx'
export default defineConfig({
site: 'https://wangshengliang.cn/', // 站点 URL
integrations: [
sitemap(), // 自动生成站点地图
unocss(), // 原子化 CSS
astroExpressiveCode(), // 代码块增强
mdx(), // MDX 支持
],
markdown: {
remarkPlugins, // Markdown 处理插件
rehypePlugins,
},
})

src/config.ts - 站点配置:

src/config.ts
export const SITE = {
website: 'https://wangshengliang.cn/',
title: 'wangshengliang',
description: "wangshengliang's blog",
author: 'wangshengliang',
lang: 'zh-Hans',
}
export const FEATURES = {
toc: [true, { minHeadingLevel: 2, maxHeadingLevel: 4 }],
giscus: [
true,
{
/* Giscus 评论配置 */
},
],
search: [true, { includes: ['blog', 'changelog'] }],
}

路径别名的使用#

项目配置了 ~/* 别名指向 src/ 目录:

// tsconfig.json
{
"compilerOptions": {
"paths": {
"~/*": ["./src/*"]
}
}
}

这样在任何地方都可以用绝对路径导入:

---
// 无论文件在哪个层级,都可以这样导入
import { SITE } from '~/config'
import Header from '~/components/base/Header.astro'
import { formatDate } from '~/utils/date'
---

下一步#

🎯 恭喜你完成了 Astro 入门!现在你应该已经理解了:

下一篇文章,我们将深入学习 Astro 的路由系统,包括:

参考资料#